{
  "name": "Vanessa \u2014 Nimara AI Agent v3",
  "nodes": [
    {
      "id": "node-chat-trigger",
      "name": "Chat Trigger",
      "type": "@n8n/n8n-nodes-langchain.chatTrigger",
      "typeVersion": 1.1,
      "position": [
        180,
        400
      ],
      "parameters": {
        "public": true,
        "initialMessages": "Bonjour ! Je suis Vanessa, votre conseill\u00e8re Nimara. \ud83c\udf1f\n\nCuisine indienne cuisin\u00e9e ce matin, livr\u00e9e chaude dans votre bureau \u00e0 midi.\n\nQu'est-ce qui vous ferait plaisir ?",
        "options": {
          "title": "Vanessa \u2014 Nimara",
          "subtitle": "Votre guide gourmand \u00b7 En ligne",
          "inputPlaceholder": "\u00c9crivez votre message..."
        }
      }
    },
    {
      "id": "node-http-menu",
      "name": "HTTP Menu du jour",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        200
      ],
      "parameters": {
        "method": "GET",
        "url": "https://docs.google.com/spreadsheets/d/1E9KPWt7yUcKwKYCvg6dRzMe4ph8np2XR/export?format=csv&sheet=MENU_DU_JOUR",
        "options": {
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        }
      }
    },
    {
      "id": "node-http-allergenes",
      "name": "HTTP Allergenes",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        400
      ],
      "parameters": {
        "method": "GET",
        "url": "https://docs.google.com/spreadsheets/d/1E9KPWt7yUcKwKYCvg6dRzMe4ph8np2XR/export?format=csv&sheet=ALLERGENES",
        "options": {
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        }
      }
    },
    {
      "id": "node-http-offres",
      "name": "HTTP Offres actives",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        600
      ],
      "parameters": {
        "method": "GET",
        "url": "https://docs.google.com/spreadsheets/d/1E9KPWt7yUcKwKYCvg6dRzMe4ph8np2XR/export?format=csv&sheet=OFFRES_ACTIVES",
        "options": {
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        }
      }
    },
    {
      "id": "node-merge",
      "name": "Merge CSV",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        680,
        400
      ],
      "parameters": {
        "mode": "append"
      }
    },
    {
      "id": "node-code",
      "name": "Formate le contexte",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        900,
        400
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "function parseCSV(text) {\n  if (!text) return [];\n  const lines = text.trim().split('\\n');\n  if (lines.length < 2) return [];\n  const headers = lines[0].split(',').map(h => h.trim().replace(/^\"|\"$/g, ''));\n  return lines.slice(1).map(line => {\n    const fields = [];\n    let cur = '', inQ = false;\n    for (let i = 0; i < line.length; i++) {\n      if (line[i] === '\"') { inQ = !inQ; }\n      else if (line[i] === ',' && !inQ) { fields.push(cur.trim()); cur = ''; }\n      else { cur += line[i]; }\n    }\n    fields.push(cur.trim());\n    const obj = {};\n    headers.forEach((h, i) => { obj[h] = (fields[i] || '').replace(/^\"|\"$/g, '').trim(); });\n    return obj;\n  }).filter(r => Object.values(r).some(v => v !== ''));\n}\n\nconst allItems = $input.all();\nconst menuCSV = allItems[0]?.json?.data || '';\nconst allergenesCSV = allItems[1]?.json?.data || '';\nconst offresCSV = allItems[2]?.json?.data || '';\n\nconst menu = parseCSV(menuCSV);\nconst allergens = parseCSV(allergenesCSV);\nconst offres = parseCSV(offresCSV);\n\nconst today = new Date().toLocaleDateString('fr-CH',{day:'2-digit',month:'2-digit',year:'numeric'}).replace(/\\//g,'.');\n\nlet menuText = `## MENU DU JOUR (${today})\\n`;\nconst cats = ['PLAT','RIZ','ENTREE','ENTR\u00c9E','NAAN','DESSERT','BOISSON'];\nfor (const cat of cats) {\n  const items = menu.filter(i => i.cat\u00e9gorie?.toUpperCase().replace('\u00c9','E') === cat.replace('\u00c9','E') && i.disponible?.toUpperCase() === 'OUI');\n  if (!items.length) continue;\n  menuText += `\\n### ${cat}\\n`;\n  for (const d of items) {\n    const tags = [];\n    if (d.sans_gluten?.toUpperCase() === 'OUI') tags.push('SANS GLUTEN');\n    if (Object.entries(d).find(([k,v]) => k.includes('t') && k.includes('arien') && v?.toUpperCase()==='OUI')) tags.push('V\u00c9G\u00c9TARIEN');\n    if (d.halal?.toUpperCase() === 'OUI') tags.push('HALAL');\n    menuText += `- **${d.plat}**: ${d.description}${tags.length?' ['+tags.join(' | ')+']':''}\\n`;\n    if (d.notes_chef) menuText += `  \u21b3 ${d.notes_chef}\\n`;\n  }\n}\nif (!menu.length) menuText += 'Menu non encore mis \u00e0 jour \u2014 r\u00e9f\u00e9rer aux plats habituels.\\n';\n\nlet allerText = '\\n## ALLERG\u00c8NES \u2014 R\u00c8GLE ABSOLUE: ne jamais proposer un produit contenant l\\'allerg\u00e8ne mentionn\u00e9\\n\\n';\nfor (const a of allergens) {\n  const produit = a.produit || a.Produit || '';\n  if (!produit) continue;\n  const al = [];\n  if (a.gluten==='\u2713') al.push('GLUTEN');\n  if (a.lait==='\u2713') al.push('LAIT');\n  if (a.oeufs==='\u2713') al.push('OEUFS');\n  const fk = Object.keys(a).find(k=>k.includes('coque')||k.includes('noix'));\n  if (fk && a[fk]==='\u2713') al.push('FRUITS \u00c0 COQUE');\n  const sk = Object.keys(a).find(k=>k.includes('same')||k.includes('s\u00e9same'));\n  if (sk && a[sk]==='\u2713') al.push('S\u00c9SAME');\n  const comp = [];\n  const vk = Object.keys(a).find(k=>k.includes('tarien'));\n  if (vk && a[vk]==='\u2713') comp.push('v\u00e9g\u00e9tarien');\n  if (a.vegan==='\u2713') comp.push('vegan');\n  if (a.halal==='\u2713') comp.push('halal');\n  if (a.sans_gluten==='\u2713') comp.push('sans gluten');\n  allerText += `- **${produit}**: ${al.length?'\u26a0\ufe0f Contient: '+al.join(', '):'\u2705 Sans allerg\u00e8ne majeur'}${comp.length?' | OK: '+comp.join(', '):''}\\n`;\n}\n\nlet offresText = '\\n## OFFRES ACTIVES\\n';\nconst actives = offres.filter(o => { const k=Object.keys(o).find(k=>k.includes('active')); return k && o[k]?.toUpperCase()==='OUI'; });\nif (!actives.length) { offresText += 'Aucune offre active.\\n'; }\nelse { for (const o of actives) { offresText += `- **${o.code||''}**: ${o.description||''} | Condition: ${o.condition_d\u00e9clenchement||o.condition||''} | Valeur: ${o.valeur_r\u00e9duction||o.valeur||''}\\n`; } }\n\nconst dynamicContext = '\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n# DONN\u00c9ES TEMPS R\u00c9EL\\n' + menuText + allerText + offresText + '\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501';\n\nreturn [{ json: { dynamicContext } }];"
      }
    },
    {
      "id": "node-agent",
      "name": "Vanessa AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 1.7,
      "position": [
        1140,
        400
      ],
      "parameters": {
        "sessionIdType": "fromInput",
        "sessionKey": "sessionId",
        "text": "={{ $('Chat Trigger').item.json.chatInput }}",
        "options": {
          "systemMessage": "# QUI TU ES\nTu es Vanessa, conseill\u00e8re commerciale de Nimara \u00e0 Gen\u00e8ve.\nChaleureuse, directe, complice. Tu parles comme une vraie personne \u2014 jamais comme un robot.\nTu AIMES la cuisine. Tu fais saliver avant de vendre.\nLangue : FR par d\u00e9faut, EN/DE si le client \u00e9crit dans cette langue.\n\n# OBJECTIF UNIQUE\nGuider chaque visiteur jusqu'\u00e0 un engagement clair, puis WhatsApp.\nEngagement = \"je prends X\" ou \"je veux tester\". WhatsApp seulement apr\u00e8s ce moment.\n\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\n# OUVERTURE \u2014 ATMOSPH\u00c8RE D'ABORD\nMatin : \"Bonjour ! \u2600\ufe0f Vous pensez d\u00e9j\u00e0 au repas de midi ? On y pense depuis 6h. Qu'est-ce qui vous ferait plaisir ?\"\nMidi : \"Bonjour ! Pendant que vous lisez \u00e7a, nos plats sont en route \ud83d\ude04 On pr\u00e9pare quelque chose pour demain ?\"\nLundi : \"Nouvelle semaine, nouveau menu \ud83c\udf5b Votre \u00e9quipe mange comment en ce moment ?\"\n\n# FORMULES\n\ud83e\udd61 Solo CHF 15 \u2014 plat + riz + verre\n\u2b50 Midi CHF 22 \u2014 entr\u00e9e + plat + naan + boisson + verre (POPULAIRE)\n\ud83c\udf89 Festive CHF 27 \u2014 entr\u00e9e + plat + naan + boisson + dessert + verre\n\nExtras : Naan nature CHF 3 / ail CHF 3.50 / fromage CHF 4\nEntr\u00e9es : Samosa l\u00e9gumes CHF 5 / b\u0153uf CHF 6 / Pakora CHF 5\nDesserts : Bircher mangue CHF 5 / Cheesecake mangue CHF 5.50 / Brookie CHF 4.50 / Brownie CHF 4\nBoissons : Gingembre CHF 4 / Lassi mangue CHF 4 / Eau CHF 2 / P\u00e9tillante CHF 2\n\nVerre consign\u00e9 : r\u00e9cup\u00e9r\u00e9 \u00e0 chaque passage. Pas de caution. Cass\u00e9 = CHF 8/mois.\nCommande avant 10h. Livraison 12h. Menu WhatsApp \u00e0 8h30.\n\n# SERVEUSE QUI SURVEILLE SA TABLE\n- Nombre de personnes \u2192 calcul imm\u00e9diat + upgrade propos\u00e9\n- 10+ personnes \u2192 offre groupe -10% automatique\n- Allergie mentionn\u00e9e \u2192 FILTRE IMM\u00c9DIAT ET PERMANENT (v\u00e9rifier les donn\u00e9es temps r\u00e9el)\n- 3 \u00e9changes sans d\u00e9cision \u2192 propose test gratuit\n- \"Je regarde\" \u2192 \"Qu'est-ce qui vous a amen\u00e9 ici ?\"\n\n# TACTIQUES\nAncrage : commence par Festive CHF 27, descends vers Solo\nCalcul : \"10 \u00d7 Midi = CHF 220. Avec -10% groupe \u2192 CHF 198 \ud83c\udf81\"\nStorytelling : \"Imaginez demain 12h : box en verre, curry fumant, naan sorti du four ce matin \ud83c\udf5b\"\nTest gratuit : \"Vous payez seulement si vous aimez. \u00c7a marche ?\"\nClosing : \"[X] personnes \u00d7 [formule] = CHF [total]. Livraison [adresse] demain 12h. Je vous mets en contact ? \ud83d\udc47\"\n\nObjections :\n- \"Trop cher\" \u2192 \"Par rapport \u00e0 un sandwich CHF 14 ? Nous c'est CHF 15 chaud livr\u00e9 \u00e0 votre bureau.\"\n- \"On a un traiteur\" \u2192 \"Est-ce qu'il livre en verre consign\u00e9 ? Un test a suffi pour beaucoup. Je vous en offre un ?\"\n- \"Je r\u00e9fl\u00e9chis\" \u2192 \"Qu'est-ce qui vous fait h\u00e9siter \u2014 prix, menu, logistique ?\"\n\n# R\u00c8GLES ABSOLUES\n\u2705 1 question par r\u00e9ponse | Calcul imm\u00e9diat si nombre mentionn\u00e9 | Max 6 lignes | M\u00e9morise tout\n\u274c Jamais proposer un produit avec l'allerg\u00e8ne mentionn\u00e9 | Jamais WhatsApp avant engagement | Jamais r\u00e9p\u00e9ter\n\u274c Jamais \"points de retrait\" | Jamais \"Oh Martine\" | Jamais \"je ne sais pas\"\n\nLiens : nimara.io/carte | nimara.io/dejeuner | wa.me/41225576020\n\n{{ $('Formate le contexte').first().json.dynamicContext }}"
        }
      }
    },
    {
      "id": "node-claude",
      "name": "Claude claude-sonnet-4-6",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "typeVersion": 1.3,
      "position": [
        1140,
        620
      ],
      "parameters": {
        "model": "claude-sonnet-4-6",
        "options": {
          "maxTokens": 1024,
          "temperature": 0.7
        }
      }
    },
    {
      "id": "node-memory",
      "name": "Memoire conversation",
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "typeVersion": 1.3,
      "position": [
        1140,
        780
      ],
      "parameters": {
        "sessionIdType": "fromInput",
        "sessionKey": "sessionId",
        "contextWindowLength": 20
      }
    }
  ],
  "connections": {
    "Chat Trigger": {
      "main": [
        [
          {
            "node": "HTTP Menu du jour",
            "type": "main",
            "index": 0
          },
          {
            "node": "HTTP Allergenes",
            "type": "main",
            "index": 0
          },
          {
            "node": "HTTP Offres actives",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Menu du jour": {
      "main": [
        [
          {
            "node": "Merge CSV",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Allergenes": {
      "main": [
        [
          {
            "node": "Merge CSV",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "HTTP Offres actives": {
      "main": [
        [
          {
            "node": "Merge CSV",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Merge CSV": {
      "main": [
        [
          {
            "node": "Formate le contexte",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Formate le contexte": {
      "main": [
        [
          {
            "node": "Vanessa AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude claude-sonnet-4-6": {
      "ai_languageModel": [
        [
          {
            "node": "Vanessa AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Memoire conversation": {
      "ai_memory": [
        [
          {
            "node": "Vanessa AI Agent",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true
  }
}