Guida Completa Passo-Passo

Crea la Tua Prima Web App con Google Apps Script

Dalla prima riga di codice all’URL pubblico condivisibile con il team. Zero server, zero costi, zero installazioni.

🎯
Cosa costruiremo

Una web app completa con: una pagina HTML, un backend che legge/scrive su Google Sheets, un URL pubblico accessibile da chiunque. Tempo stimato: 20-30 minuti.

Prima di iniziare

  • Account Google (Gmail o Google Workspace)
  • Browser Chrome o Firefox aggiornato
  • Un Google Sheet vuoto già creato (opzionale ma consigliato)
  • 10 minuti di concentrazione — si fa tutto online, nessuna installazione
1

Apri Google Apps Script Setup

Vai su script.google.com con il tuo account Google. Vedrai la dashboard con i tuoi progetti. Se è la prima volta, la vedrai vuota.

Clicca il pulsante “Nuovo Progetto” in alto a sinistra. Si apre l’editor online con un file Codice.gs già creato.

Rinomina il progetto: clicca su “Progetto senza titolo” in alto a sinistra e dai un nome significativo, es. “La Mia Prima WebApp”.
2

Scrivi il Backend — Code.gs Backend

Il file Code.gs è il cervello dell’app. Contiene il codice JavaScript che gira sui server di Google. La funzione più importante è doGet(e): viene chiamata ogni volta che qualcuno apre l’URL della tua app.

Cancella tutto il contenuto del file e incolla questo codice:

Code.gs — Backend

// ============ CONFIGURAZIONE ============
const CONFIG = {
  SHEET_ID: 'INCOLLA-QUI-ID-DEL-TUO-SHEET', // vedi Step 4
  SHEET_NAME: 'Dati'
};

// ============ ENTRY POINT ============
function doGet(e) {
  return HtmlService
    .createTemplateFromFile('Index')
    .evaluate()
    .setTitle('La Mia WebApp')
    .addMetaTag('viewport', 'width=device-width, initial-scale=1')
    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

// Helper per includere altri file HTML
function include(filename) {
  return HtmlService.createHtmlOutputFromFile(filename).getContent();
}

// ============ LEGGI DATI DAL FOGLIO ============
function getDati() {
  const sheet = SpreadsheetApp
    .openById(CONFIG.SHEET_ID)
    .getSheetByName(CONFIG.SHEET_NAME);

  const dati = sheet.getDataRange().getValues();
  return JSON.stringify(dati);
}

// ============ SALVA UN RECORD ============
function salvaRiga(nome, valore) {
  const sheet = SpreadsheetApp
    .openById(CONFIG.SHEET_ID)
    .getSheetByName(CONFIG.SHEET_NAME);

  const oggi = new Date().toLocaleDateString('it-IT');
  sheet.appendRow([oggi, nome, valore]);
  return 'OK';
}

⚠️
Importante

Sostituisci 'INCOLLA-QUI-ID-DEL-TUO-SHEET' con l’ID reale del tuo Google Sheet (vedi Step 4 su come trovarlo).

3

Crea il Frontend — Index.html Frontend

Ora aggiungi il file HTML che gli utenti vedranno. Clicca il pulsante “+” accanto a “File” nel pannello di sinistra → scegli “HTML” → chiama il file Index (senza .html, lo aggiunge da solo).

Incolla questo codice nel nuovo file Index.html:

Index.html — Frontend

<!DOCTYPE html>
<html lang="it">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>La Mia WebApp</title>
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
  <style>
    :root { --primary: #2FA3A8; }
    * { margin:0; padding:0; box-sizing:border-box; }
    body { font-family: 'Inter', sans-serif; background: #f8fafc; color: #1e293b; }

    .header {
      background: linear-gradient(135deg, #0d1117, #083A3E);
      color: white; padding: 24px 32px;
      display: flex; align-items: center; gap: 14px;
    }
    .header h1 { font-size: 22px; font-weight: 700; }

    .container { max-width: 700px; margin: 0 auto; padding: 32px 20px; }

    .card {
      background: white; border-radius: 14px;
      padding: 28px; box-shadow: 0 2px 12px rgba(0,0,0,0.07);
      margin-bottom: 24px; border: 1px solid #E6F0F0;
    }
    .card h2 { font-size: 18px; margin-bottom: 20px; color: var(--primary); }

    .form-group { margin-bottom: 18px; }
    label { font-size: 14px; font-weight: 600; color: #374151; display: block; margin-bottom: 6px; }
    input {
      width: 100%; padding: 12px 14px; border: 1px solid #d1d5db;
      border-radius: 8px; font-size: 15px; font-family: inherit;
      transition: border-color .2s;
    }
    input:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(47,163,168,0.15); }

    .btn {
      background: var(--primary); color: white; border: none;
      padding: 13px 32px; border-radius: 8px; font-size: 15px;
      font-weight: 600; cursor: pointer; font-family: inherit;
      transition: all .2s; width: 100%;
    }
    .btn:hover { background: #239e90; transform: translateY(-1px); }
    .btn:disabled { opacity: .6; cursor: not-allowed; transform: none; }

    .status {
      margin-top: 14px; padding: 12px 16px; border-radius: 8px;
      font-size: 14px; display: none;
    }
    .status.ok { background: #ecfdf5; border: 1px solid #6ee7b7; color: #065f46; }
    .status.fail { background: #fef2f2; border: 1px solid #fca5a5; color: #7f1d1d; }

    #lista { }
    .item {
      padding: 12px 16px; border-radius: 8px; margin-bottom: 8px;
      background: #f8fafc; border: 1px solid #E6F0F0;
      display: grid; grid-template-columns: auto 1fr 1fr;
      gap: 16px; align-items: center; font-size: 14px;
    }
    .item .data { color: #93AEAE; font-size: 12px; }
    .item .nome { font-weight: 600; }
    .item .val { color: var(--primary); font-weight: 600; text-align: right; }

    #loading { text-align: center; color: #93AEAE; padding: 24px; }
  </style>
</head>
<body>

  <div class="header">
    <svg width="32" height="32" viewBox="0 0 32 32" fill="none">
      <circle cx="16" cy="16" r="16" fill="rgba(47,163,168,0.3)"/>
      <path d="M10 16l4 4 8-8" stroke="#2FA3A8" stroke-width="2.5" stroke-linecap="round"/>
    </svg>
    <h1>La Mia Prima WebApp</h1>
  </div>

  <div class="container">

    <!-- FORM INSERIMENTO -->
    <div class="card">
      <h2>Inserisci un dato</h2>
      <div class="form-group">
        <label for="nome">Nome</label>
        <input type="text" id="nome" placeholder="Es. Temperatura frigo A">
      </div>
      <div class="form-group">
        <label for="valore">Valore</label>
        <input type="text" id="valore" placeholder="Es. 4°C">
      </div>
      <button class="btn" id="btnSalva" onclick="salva()">Salva sul foglio</button>
      <div class="status" id="status"></div>
    </div>

    <!-- LISTA DATI -->
    <div class="card">
      <h2>Dati salvati</h2>
      <div id="lista"><div id="loading">Caricamento...</div></div>
    </div>

  </div>

  <script>
    // Carica i dati all'avvio
    window.onload = function() { caricaDati(); };

    function caricaDati() {
      google.script.run
        .withSuccessHandler(function(json) {
          const dati = JSON.parse(json);
          const lista = document.getElementById('lista');
          if (dati.length === 0) {
            lista.innerHTML = '<p style="color:#93AEAE;text-align:center;padding:20px">Nessun dato ancora.</p>';
            return;
          }
          lista.innerHTML = dati.slice().reverse().slice(0,20).map(r => `
            <div class="item">
              <span class="data">${r[0]}</span>
              <span class="nome">${r[1]}</span>
              <span class="val">${r[2]}</span>
            </div>`
).join('');
        })
        .withFailureHandler(function(err) {
          document.getElementById('lista').innerHTML = '<p style="color:#ef4444">Errore: ' + err.message + '</p>';
        })
        .getDati();
    }

    function salva() {
      const nome = document.getElementById('nome').value.trim();
      const valore = document.getElementById('valore').value.trim();
      const status = document.getElementById('status');
      const btn = document.getElementById('btnSalva');

      if (!nome || !valore) {
        status.className = 'status fail';
        status.textContent = 'Compila tutti i campi prima di salvare.';
        status.style.display = 'block';
        return;
      }

      btn.disabled = true;
      btn.textContent = 'Salvataggio...';

      google.script.run
        .withSuccessHandler(function() {
          status.className = 'status ok';
          status.textContent = '✓ Dato salvato correttamente!';
          status.style.display = 'block';
          document.getElementById('nome').value = '';
          document.getElementById('valore').value = '';
          btn.disabled = false;
          btn.textContent = 'Salva sul foglio';
          caricaDati();
        })
        .withFailureHandler(function(err) {
          status.className = 'status fail';
          status.textContent = 'Errore: ' + err.message;
          status.style.display = 'block';
          btn.disabled = false;
          btn.textContent = 'Salva sul foglio';
        })
        .salvaRiga(nome, valore);
    }
  </script>
</body>
</html>

4

Collega Google Sheets Dati

La tua app salverà i dati su un Google Sheet. Segui questi passi:

  • Vai su sheets.google.com e crea un nuovo foglio
  • Nella prima riga aggiungi le intestazioni: Data | Nome | Valore
  • Chiama il foglio (tab in basso) Dati (esatto, con la D maiuscola)
  • Copia l’ID dallo URL: è la stringa lunga tra /d/ e /edit
Come trovare l’ID del foglioURL esempio: https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms/edit L’ID è la parte evidenziata: 1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms

Torna su Code.gs e sostituisci 'INCOLLA-QUI-ID-DEL-TUO-SHEET' con il tuo ID.

Alternativa rapida: puoi anche creare il foglio direttamente da GAS con SpreadsheetApp.create('Nome Foglio') — ma per i principianti è più semplice crearlo a mano e copiare l’ID.
5

Configura appsscript.json Configurazione

Questo file definisce come si comporta la tua app quando viene pubblicata. Per vederlo, clicca l’icona Impostazioni (⚙️ ingranaggio) nella barra laterale sinistra e attiva “Mostra file manifest appsscript.json”.

Sostituisci il contenuto con:

appsscript.json

{
  "timeZone": "Europe/Rome",
  "dependencies": {},
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/script.external_request"
  ],
  "webapp": {
    "executeAs": "USER_DEPLOYING",
    "access": "ANYONE_ANONYMOUS"
  }
}

✅
Perché questi valori?

executeAs: USER_DEPLOYING — l’app gira con il tuo account, gli utenti non devono fare login.
access: ANYONE_ANONYMOUS — chiunque può aprire l’URL, anche chi non ha un account Google aziendale.

6

Salva e Fai il Deploy Deploy

Ora pubblichiamo l’app. Segui questa sequenza precisa:

  • Clicca Ctrl+S (o Cmd+S su Mac) per salvare tutti i file
  • Clicca il pulsante “Deploy” in alto a destra → “Nuova distribuzione”
  • Nella finestra che si apre: clicca l’icona ⚙️ accanto a “Seleziona tipo” → scegli “App web”
  • Campo “Descrizione”: scrivi “v1.0 – Prima versione”
  • Campo “Esegui come”: Me (il tuo indirizzo email)
  • Campo “Chi può accedere”: Chiunque
  • Clicca “Esegui il deploy”
⚠️
Autorizzazione necessaria (solo la prima volta)

Google chiederà di autorizzare l’app ad accedere ai tuoi Sheets. Clicca “Autorizza accesso” → scegli il tuo account Google → clicca “Avanzate” → “Vai a [nome progetto] (non sicuro)” → “Consenti”. Questo messaggio appare perché l’app non è verificata da Google, ma è normale per le app personali.

Dopo il deploy, Google ti mostra l’URL dell’app web. Copialo e salvalo — è il link che condividerai con il team. Ha questo formato:
https://script.google.com/macros/s/[ID-LUNGO]/exec
7

Testa la Web App Test

Apri l’URL copiato nel browser. Dovresti vedere la tua web app con il form. Prova a:

  • Inserire un nome e un valore nel form
  • Cliccare “Salva sul foglio”
  • Verificare che il dato appaia nella lista
  • Aprire Google Sheets e controllare che la riga sia stata aggiunta
  • Aprire lo stesso URL da smartphone — deve funzionare!
🎉
Funziona! E adesso?

Hai costruito la tua prima web app con GAS. Da qui puoi espanderla: aggiungi campi al form, crea nuove funzioni backend, aggiungi tabelle o grafici al frontend. Ogni modifica richiede un nuovo deploy.

8

Aggiornare l’App dopo le Modifiche Workflow

Ogni volta che modifichi il codice, devi fare un nuovo deploy per vedere i cambiamenti online:

  • Modifica il codice in Code.gs o Index.html
  • Salva con Ctrl+S
  • Clicca “Deploy” → “Gestisci distribuzioni”
  • Clicca l’icona ✏️ (matita) accanto alla tua distribuzione
  • Cambia la versione da “Nuova versione” a quella desiderata
  • Aggiorna la descrizione (es. “v1.1 – Aggiunto campo email”)
  • Clicca “Esegui il deploy” — stessa URL, nuova versione
Pro tip: tieni sempre la stessa URL di deploy aggiornando la versione. Così chi ha salvato il link non deve fare nulla — vede automaticamente la versione nuova.

Usa l’AI per Scrivere il Codice GAS

Non devi imparare JavaScript. Descrivi cosa vuoi e chiedi a Claude o ChatGPT di scrivere il codice per te. Poi incollalo in GAS e fai il deploy.

Prompt per Claude

Copia e usaSei uno sviluppatore Google Apps Script esperto. Crea una web app GAS con: – Backend Code.gs con doGet() che carica Index.html – Frontend Index.html con form per inserire: [descrivi i tuoi campi] – Funzione backend che salva su Google Sheets con ID: [ID_FOGLIO] – Design moderno, mobile-first, colori: [colori che vuoi] – Mostra un elenco degli ultimi 20 record salvati Fornisci codice completo e funzionante per entrambi i file.

Errori Comuni → Soluzioni

  • “Accesso negato” → rilancia il deploy con accesso “Chiunque”
  • Pagina bianca → controlla che il file si chiami esattamente “Index” (con I maiuscola)
  • Errore Sheet → controlla che l’ID sia corretto e il foglio si chiami “Dati”
  • Modifiche non visibili → hai fatto il nuovo deploy? Controlla “Gestisci distribuzioni”
  • Autorizzazione loop → svuota la cache del browser e riprova

Idee di App da Costruire

  • Registro presenze con QR code
  • Form ordini fornitore con approvazione
  • Dashboard KPI estratta da Sheets
  • Sistema mance con calcolo automatico
  • Checklist apertura/chiusura locale

Funzioni GAS Utili

  • MailApp.sendEmail() — manda email
  • CalendarApp.getEvents() — legge Calendar
  • DriveApp.createFile() — crea file Drive
  • UrlFetchApp.fetch() — chiama API esterne
  • Utilities.formatDate() — formatta date

Trigger Automatici

  • onEdit — si attiva quando modifichi lo sheet
  • onOpen — si attiva all’apertura del file
  • Time-driven — ogni ora/giorno/settimana
  • onFormSubmit — a ogni invio form

Livello Avanzato — Agente AI Personale

Crea il Tuo Agente AI con GAS + Gemini

Prendi la web app GAS che hai già costruito e aggiungi un cervello AI. Con poche righe di codice colleghi Gemini 2.0 Flash e ottieni un agente che risponde, analizza e ragiona — tutto su infrastruttura Google, tutto gratis.

Un agente AI non è solo una chat. È un sistema che ha un ruolo preciso, conosce il tuo contesto aziendale e risponde sempre in modo coerente. Il System Prompt è il suo “carattere” — tu lo definisci, Gemini lo interpreta.

Ottieni la chiave da Google AI Studio

L’API key è la “password” che permette al tuo script GAS di parlare con Gemini. È gratuita, si crea in 1 minuto.

  • Vai su aistudio.google.com con il tuo account Google
  • Clicca “Get API Key” in alto a sinistra → “Create API key”
  • Copia la chiave generata (inizia con AIza...) — salvala in un posto sicuro
  • Ora vai nel tuo progetto GAS → Impostazioni Progetto (icona ingranaggio ⚙️ in basso a sinistra)
  • Scorri fino a “Proprietà dello script” → clicca “Aggiungi proprietà”
  • Nome: GEMINI_API_KEY — Valore: incolla la tua API key → Salva
🔐
Perché salvare la key nelle Proprietà Script?

Non mettere mai l’API key direttamente nel codice — chiunque abbia accesso al progetto potrebbe vederla. Con PropertiesService la key è crittografata e invisibile, anche se condividi il progetto.

Il backend completo dell’agente

Sostituisci il Code.gs della tua app con questo. Contiene tutto: la web app, la chiamata a Gemini e la gestione della cronologia della chat.

Code.gs — Agente AI completo

// ============ CONFIGURAZIONE AGENTE ============
const CONFIG = {
  GEMINI_MODEL: 'gemini-2.0-flash', // oppure 'gemini-2.5-flash'
  GEMINI_URL: 'https://generativelanguage.googleapis.com/v1beta/models/',
  MAX_TOKENS: 1024,
  TEMPERATURE: 0.7,

  // ← Personalizza il carattere del tuo agente qui
  SYSTEM_PROMPT: `Sei un assistente aziendale di Gruppo Ethos.
Conosci i 5 locali: Acqua e Farina, Birrificio di Como, Fabbrica Libera,
Agriturismo Brusignone, Essenza Bistrot.
Rispondi sempre in italiano, in modo professionale ma diretto.
Sei specializzato in: operatività ristorazione, gestione del personale,
food & beverage, procedure HACCP.
Se non conosci qualcosa, dillo chiaramente senza inventare.`

};

// ============ ENTRY POINT WEB APP ============
function doGet(e) {
  return HtmlService
    .createTemplateFromFile('Index')
    .evaluate()
    .setTitle('Agente AI — Gruppo Ethos')
    .addMetaTag('viewport', 'width=device-width, initial-scale=1')
    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

// ============ CHIAMATA A GEMINI ============
function chiediAgente(messaggioUtente, cronologia) {
  const apiKey = PropertiesService.getScriptProperties()
                   .getProperty('GEMINI_API_KEY');

  if (!apiKey) return '⚠️ API key non configurata. Vai in Impostazioni Progetto → Proprietà Script.';

  const url = CONFIG.GEMINI_URL + CONFIG.GEMINI_MODEL + ':generateContent?key=' + apiKey;

  // Costruisci la cronologia conversazione
  const contents = (cronologia || []).map(function(msg) {
    return { role: msg.ruolo, parts: [{ text: msg.testo }] };
  });

  // Aggiungi il messaggio corrente
  contents.push({ role: 'user', parts: [{ text: messaggioUtente }] });

  const payload = {
    system_instruction: { parts: [{ text: CONFIG.SYSTEM_PROMPT }] },
    contents: contents,
    generationConfig: {
      temperature: CONFIG.TEMPERATURE,
      maxOutputTokens: CONFIG.MAX_TOKENS
    }
  };

  const options = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  };

  try {
    const risposta = UrlFetchApp.fetch(url, options);
    const dati = JSON.parse(risposta.getContentText());

    if (dati.error) return 'Errore API: ' + dati.error.message;

    return dati.candidates[0].content.parts[0].text;

  } catch(err) {
    return 'Errore di connessione: ' + err.message;
  }
}

// ============ LOG SU SHEETS (opzionale) ============
function logConversazione(domanda, risposta) {
  try {
    const ss = SpreadsheetApp.getActiveSpreadsheet();
    const sheet = ss.getSheetByName('Log') || ss.insertSheet('Log');
    const ora = new Date().toLocaleString('it-IT');
    sheet.appendRow([ora, domanda, risposta]);
  } catch(e) { /* log opzionale, ignora errori */ }
}

La chat completa del tuo agente

Crea il file Index.html nel progetto GAS e incolla questo codice. Ottieni una chat moderna con cronologia, indicatore di caricamento e invio con Invio.

Index.html — Chat Interface Agente AI

<!DOCTYPE html>
<html lang="it">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Agente AI</title>
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
  <style>
    :root { --ai:#2FA3A8; --user:#3b82f6; --bg:#0d1117; --bg2:#0e1a2a; }
    * { margin:0; padding:0; box-sizing:border-box; }
    body { font-family:'Inter',sans-serif; background:var(--bg); color:#E6F0F0; height:100vh; display:flex; flex-direction:column; }

    /* Header */
    .header {
      background:var(--bg2); border-bottom:1px solid rgba(47,163,168,0.2);
      padding:16px 24px; display:flex; align-items:center; gap:12px; flex-shrink:0;
    }
    .header-dot { width:10px; height:10px; border-radius:50%; background:var(--ai); box-shadow:0 0 8px rgba(47,163,168,.6); animation:pulse 2s infinite; }
    @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.4} }
    .header h1 { font-size:16px; font-weight:700; color:#fff; }
    .header p { font-size:12px; color:#64748b; margin-top:1px; }

    /* Chat area */
    .chat { flex:1; overflow-y:auto; padding:24px 16px; display:flex; flex-direction:column; gap:16px; }

    .msg { display:flex; gap:10px; max-width:80%; }
    .msg.user { align-self:flex-end; flex-direction:row-reverse; }
    .msg.ai { align-self:flex-start; }

    .avatar {
      width:34px; height:34px; border-radius:50%; flex-shrink:0;
      display:flex; align-items:center; justify-content:center; font-size:15px;
    }
    .msg.ai .avatar { background:rgba(47,163,168,0.2); border:1px solid rgba(47,163,168,0.4); }
    .msg.user .avatar { background:rgba(59,130,246,0.2); border:1px solid rgba(59,130,246,0.4); }

    .bubble {
      padding:12px 16px; border-radius:14px;
      font-size:14px; line-height:1.7; white-space:pre-wrap; word-break:break-word;
    }
    .msg.ai .bubble { background:#083A3E; border:1px solid rgba(47,163,168,0.15); border-radius:4px 14px 14px 14px; }
    .msg.user .bubble { background:rgba(59,130,246,0.15); border:1px solid rgba(59,130,246,0.25); border-radius:14px 4px 14px 14px; }

    .typing { display:flex; gap:4px; padding:14px 18px; align-items:center; }
    .typing span { width:7px; height:7px; border-radius:50%; background:var(--ai); animation:bounce .9s infinite; }
    .typing span:nth-child(2){animation-delay:.2s}
    .typing span:nth-child(3){animation-delay:.4s}
    @keyframes bounce { 0%,60%,100%{transform:translateY(0)} 30%{transform:translateY(-8px)} }

    /* Input area */
    .input-area {
      padding:16px 20px; background:var(--bg2);
      border-top:1px solid rgba(47,163,168,0.15); flex-shrink:0;
    }
    .input-wrap { display:flex; gap:10px; max-width:800px; margin:0 auto; }
    textarea {
      flex:1; background:#084247; border:1px solid rgba(47,163,168,0.25);
      color:#E6F0F0; border-radius:10px; padding:12px 16px; font-family:inherit;
      font-size:14px; resize:none; min-height:48px; max-height:140px; line-height:1.6;
      transition:border-color .2s;
    }
    textarea:focus { outline:none; border-color:var(--ai); }
    textarea::placeholder { color:#475569; }
    .send-btn {
      background:linear-gradient(135deg,#1a8a7d,#4FD8DE); color:#fff; border:none;
      width:48px; height:48px; border-radius:10px; cursor:pointer; flex-shrink:0;
      display:flex; align-items:center; justify-content:center; transition:all .2s;
      align-self:flex-end;
    }
    .send-btn:hover:not(:disabled) { transform:scale(1.05); box-shadow:0 4px 16px rgba(79,216,222,0.3); }
    .send-btn:disabled { opacity:.4; cursor:not-allowed; }
    .hint { text-align:center; font-size:11px; color:#334155; margin-top:8px; }
  </style>
</head>
<body>

  <div class="header">
    <div class="header-dot"></div>
    <div>
      <h1>Agente AI — Gruppo Ethos</h1>
      <p>Powered by Gemini 2.0 Flash · Sempre disponibile</p>
    </div>
  </div>

  <div class="chat" id="chat">
    <div class="msg ai">
      <div class="avatar">🤖</div>
      <div class="bubble">Ciao! Sono il tuo assistente AI. Come posso aiutarti oggi?</div>
    </div>
  </div>

  <div class="input-area">
    <div class="input-wrap">
      <textarea id="input" placeholder="Scrivi la tua domanda..." rows="1"></textarea>
      <button class="send-btn" id="btn" onclick="invia()">
        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5">
          <line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/>
        </svg>
      </button>
    </div>
    <p class="hint">Premi Invio per inviare · Shift+Invio per andare a capo</p>
  </div>

  <script>
    // Cronologia conversazione (mantiene il contesto)
    const cronologia = [];

    const chatEl = document.getElementById('chat');
    const inputEl = document.getElementById('input');
    const btnEl = document.getElementById('btn');

    // Auto-resize textarea
    inputEl.addEventListener('input', function() {
      this.style.height = 'auto';
      this.style.height = Math.min(this.scrollHeight, 140) + 'px';
    });

    // Invio con tasto Enter (Shift+Enter va a capo)
    inputEl.addEventListener('keydown', function(e) {
      if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); invia(); }
    });

    function aggiungiMessaggio(testo, tipo) {
      const div = document.createElement('div');
      div.className = 'msg ' + tipo;
      div.innerHTML = `
        <div class="avatar">${tipo === 'ai' ? '🤖' : '👤'}</div>
        <div class="bubble">${testo.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/\n/g,'<br>')}</div>`
;
      chatEl.appendChild(div);
      chatEl.scrollTop = chatEl.scrollHeight;
      return div;
    }

    function mostraTyping() {
      const div = document.createElement('div');
      div.className = 'msg ai'; div.id = 'typing';
      div.innerHTML = '<div class="avatar">🤖</div><div class="bubble"><div class="typing"><span></span><span></span><span></span></div></div>';
      chatEl.appendChild(div);
      chatEl.scrollTop = chatEl.scrollHeight;
    }

    function invia() {
      const testo = inputEl.value.trim();
      if (!testo) return;

      aggiungiMessaggio(testo, 'user');
      cronologia.push({ ruolo: 'user', testo: testo });

      inputEl.value = '';
      inputEl.style.height = 'auto';
      btnEl.disabled = true;
      mostraTyping();

      google.script.run
        .withSuccessHandler(function(risposta) {
          document.getElementById('typing')?.remove();
          aggiungiMessaggio(risposta, 'ai');
          cronologia.push({ ruolo: 'model', testo: risposta });
          btnEl.disabled = false;
          inputEl.focus();
        })
        .withFailureHandler(function(err) {
          document.getElementById('typing')?.remove();
          aggiungiMessaggio('⚠️ Errore: ' + err.message, 'ai');
          btnEl.disabled = false;
        })
        .chiediAgente(testo, cronologia.slice(-10)); // passa max 10 msg di storia
    }
  </script>
</body>
</html>

Cambia il System Prompt → cambia l’agente

Il campo SYSTEM_PROMPT in Code.gs è il carattere dell’agente. Basta modificarlo per creare agenti completamente diversi.

Agente HR — Selezione e Policy

System PromptSei l’assistente HR di Gruppo Ethos. Conosci tutte le procedure di selezione, i contratti CCNL Turismo, le policy aziendali e i benefit offerti. Aiuta i responsabili a: scrivere job description, preparare domande di colloquio, gestire onboarding e richieste del personale. Rispondi in italiano, tono professionale ma umano. Non inventare dati normativi — se non sei sicuro, dillo.

Agente F&B — Cucina e Menu

System PromptSei il consulente F&B di Gruppo Ethos. Conosci i 5 locali: Acqua e Farina, Birrificio di Como, Fabbrica Libera, Agriturismo Brusignone, Essenza Bistrot. Sei esperto di: gestione food cost, ricette, procedure HACCP, gestione allergeni, ottimizzazione menu, rapporti con fornitori. Usa linguaggio da professionista della ristorazione.

Agente FAQ — Assistente Cliente

System PromptSei l’assistente virtuale del ristorante [NOME]. Indirizzo: [INDIRIZZO]. Telefono: [NUMERO]. Orari: [ORARI APERTURA]. Rispondi alle domande dei clienti su: prenotazioni, menu, allergeni, parcheggio, eventi speciali, prezzi medi. Tono: cordiale, veloce, utile. Se non sai qualcosa, suggerisci di chiamare direttamente il locale.

Agente Dati — Analisi KPI

System PromptSei un analista di dati per la ristorazione. Quando ricevi dati (incassi, coperti, food cost, presenze), li analizzi e produci: commento narrativo, trend principali, anomalie da investigare, azioni consigliate. Formato risposta: sempre strutturato con titoli. Usa % e confronti vs periodo precedente quando possibile.

gemini-2.0-flash

Consigliato per iniziare

  • Velocissimo (risposta in 1-2 sec)
  • Gratuito con limiti generosi
  • Ottimo per chat, FAQ, testo
  • Contesto 1M token

gemini-2.5-flash

Per analisi complesse

  • Ragionamento avanzato
  • Migliore con dati e calcoli
  • Thinking mode attivabile
  • Leggermente più lento

Come cambiare modello

Basta modificare una riga in Code.gs:

Code.gs → CONFIGGEMINI_MODEL: ‘gemini-2.0-flash’ // oppure: GEMINI_MODEL: ‘gemini-2.5-flash’
🎯
Il flusso completo in sintesi

1. Ottieni API key su aistudio.google.com → 2. Salvala in Proprietà Script GAS (nome: GEMINI_API_KEY) → 3. Incolla Code.gs e Index.html → 4. Personalizza il SYSTEM_PROMPT → 5. Deploy → il tuo agente è live con URL condivisibile.


Torna alla guida

Metodo, sette strumenti, prompt e missioni stanno nella guida principale.

Guida LAB AI 2026 →

inviaci la tua richiesta