{
  "nodes": [
    {
      "id": "9c865c94-70ef-4322-978e-22b28a3d7a2d",
      "name": "Code",
      "type": "n8n-nodes-base.code",
      "position": [
        2384,
        1552
      ],
      "parameters": {
        "jsCode": "/**\n * n8n – Code node\n * Mode : Run Once for All Items\n * Objectif :\n *   1. Repère **gras** ou *gras*\n *   2. Convertit le contenu en lettres \"Unicode Bold\"\n *   3. Supprime tous les astérisques\n */\n\nconst FIELD = 'output'; // ← mets ici le nom exact du champ texte\n\n// --- petite fonction de conversion ASCII → Unicode gras\nfunction toUnicodeBold(str) {\n  const upperOffset = 0x1D400 - 0x41;      // A → 𝐀\n  const lowerOffset = 0x1D41A - 0x61;      // a → 𝐚\n  const digitOffset = 0x1D7CE - 0x30;      // 0 → 𝟎\n\n  return str.split('').map(ch => {\n    const code = ch.charCodeAt(0);\n    if (code >= 0x41 && code <= 0x5A) return String.fromCodePoint(code + upperOffset); // A-Z\n    if (code >= 0x61 && code <= 0x7A) return String.fromCodePoint(code + lowerOffset); // a-z\n    if (code >= 0x30 && code <= 0x39) return String.fromCodePoint(code + digitOffset); // 0-9\n    return ch; // ponctuation, espaces…\n  }).join('');\n}\n\nfor (const item of $input.all()) {\n  if (item.json[FIELD]) {\n    item.json[FIELD] = item.json[FIELD]\n      // ► 1. **…**  ou *…*  → conversion + suppression d’astérisques\n      .replace(/\\*{1,2}([^*]+?)\\*{1,2}/gs, (_, txt) => toUnicodeBold(txt))\n\n      // ► 2. S’il reste des astérisques isolés → on les retire\n      .replace(/\\*/g, '');\n  }\n}\n\nreturn $input.all();"
      },
      "typeVersion": 2
    },
    {
      "id": "97c32dd0-65a0-4e9b-94dd-03b9a8b75c7b",
      "name": "Split out message1",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        -48,
        1552
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "messages"
      },
      "typeVersion": 1
    },
    {
      "id": "ade79aa0-86ca-4742-9177-0f23ac3d87ed",
      "name": "Identify and ReRoute Message Types1",
      "type": "n8n-nodes-base.switch",
      "position": [
        256,
        1536
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "Audio Message",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "584171c6-5842-41a4-ae70-b901457d1b43",
                    "operator": {
                      "type": "boolean",
                      "operation": "true",
                      "singleValue": true
                    },
                    "leftValue": "={{$json.type == 'audio' && Boolean($json.audio)}}",
                    "rightValue": ""
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Image Message",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "b12ff3af-0aae-4874-9c16-37f0337bf214",
                    "operator": {
                      "type": "boolean",
                      "operation": "true",
                      "singleValue": true
                    },
                    "leftValue": "={{$json.type == 'image' && Boolean($json.image)}}",
                    "rightValue": ""
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra",
          "renameFallbackOutput": "Text Message"
        }
      },
      "typeVersion": 3.2
    },
    {
      "id": "0fc5684c-cfc4-413a-ab59-045eaa336de5",
      "name": "Send message",
      "type": "n8n-nodes-base.whatsApp",
      "position": [
        2656,
        1552
      ],
      "webhookId": "891ef88f-6c8a-4701-bf44-51d59793da2f",
      "parameters": {
        "textBody": "={{ $json.output }}",
        "operation": "send",
        "phoneNumberId": "={{ $('WhatsApp Trigger').item.json.metadata.phone_number_id }}",
        "additionalFields": {},
        "recipientPhoneNumber": "={{ $('WhatsApp Trigger').item.json.messages[0].from }}"
      },
      "credentials": {
        "whatsAppApi": {
          "id": "credential-id",
          "name": "whatsAppApi Credential"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "8c5bb334-0319-4eeb-8a51-f9bc04e96fd5",
      "name": "Audio Transcriber",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        1120,
        1360
      ],
      "parameters": {
        "options": {},
        "resource": "audio",
        "operation": "transcribe"
      },
      "credentials": {
        "openAiApi": {
          "id": "credential-id",
          "name": "openAiApi Credential"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1.8
    },
    {
      "id": "90a2ffc0-4ef1-4175-974d-eae721b50dd2",
      "name": "Analyze image",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        1120,
        1552
      ],
      "parameters": {
        "text": "Here is an image sent by the user. Describe the image and transcribe any text visible in the image. Put in as much detail as possible. But only and always in French Language.",
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o",
          "cachedResultName": "GPT-4O"
        },
        "options": {},
        "resource": "image",
        "inputType": "base64",
        "operation": "analyze"
      },
      "credentials": {
        "openAiApi": {
          "id": "credential-id",
          "name": "openAiApi Credential"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "a7ca13a9-66a6-42b5-af96-7983bc55a059",
      "name": "Get Audio URL",
      "type": "n8n-nodes-base.whatsApp",
      "position": [
        592,
        1360
      ],
      "webhookId": "f0d2c81a-eb7d-4286-ab49-5e86c1a034eb",
      "parameters": {
        "resource": "media",
        "operation": "mediaUrlGet",
        "mediaGetId": "={{ $json.audio.id }}"
      },
      "credentials": {
        "whatsAppApi": {
          "id": "credential-id",
          "name": "whatsAppApi Credential"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "ce1f62be-7f03-4401-8b08-59d44eb5d3d9",
      "name": "Get Image URL",
      "type": "n8n-nodes-base.whatsApp",
      "position": [
        592,
        1552
      ],
      "webhookId": "0cbdca1d-832a-4a6b-bf4b-a8da3a55b7f2",
      "parameters": {
        "resource": "media",
        "operation": "mediaUrlGet",
        "mediaGetId": "={{ $json.image.id }}"
      },
      "credentials": {
        "whatsAppApi": {
          "id": "credential-id",
          "name": "whatsAppApi Credential"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "da5d655b-66bf-4281-934a-93524540372e",
      "name": "Download Image",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        864,
        1552
      ],
      "parameters": {
        "url": "={{ $json.url }}",
        "options": {},
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "whatsAppApi"
      },
      "credentials": {
        "whatsAppApi": {
          "id": "credential-id",
          "name": "whatsAppApi Credential"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "f220b256-48d7-4647-941f-1d73bbc0c0af",
      "name": "Download Audio",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        864,
        1360
      ],
      "parameters": {
        "url": "={{ $json.url }}",
        "options": {},
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "whatsAppApi"
      },
      "credentials": {
        "whatsAppApi": {
          "id": "credential-id",
          "name": "whatsAppApi Credential"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "284674ca-6c30-4ceb-8ece-18d8815832d4",
      "name": "AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1984,
        1552
      ],
      "parameters": {
        "text": "={{ $json.user_prompt }}",
        "options": {
          "systemMessage": "=# Nutrition Office Assistant\n\n## 📅 CURRENT DATE AND TIME\n**Today:** {{ DateTime.now().setZone('Europe/Paris').toFormat('EEEE dd MMMM yyyy', {locale: 'fr'}) }}  \n**Time:** {{ DateTime.now().setZone('Europe/Paris').toFormat('HH:mm:ss') }}  \n**ISO Format:** {{ DateTime.now().setZone('Europe/Paris').toFormat('yyyy-MM-dd') }}\n\n## BASIC INFORMATION\n- **Location:** 8 Allée du Progrès, 92170 Vanves  \n- **Hours:** Mon-Fri 08:00-18:00 | Sat 09:00-15:00  \n- **Timezone:** Europe/Paris (currently UTC+1)  \n- **Date display:** DD-MM-YYYY and HHhMM (never technical format)\n\n## LANGUAGE POLICY\n- The assistant must **speak primarily in French at all times**.  \n- The assistant may **switch to English or Spanish** only if the client initiates the conversation in that language.  \n- When switching languages, continue the full conversation in the client’s chosen language.  \n- In all other cases, **default to French** for clarity, professionalism, and consistency.\n\n## CRITICAL RULES\n\n### 1. TIME CONVERSION (INVISIBLE TO CLIENT)\n- **API → Client:** Add 1h (UTC to Paris) - Example: API says \"12:00Z\" = say \"13h00\"  \n- **Client → API:** Subtract 1h (Paris to UTC) - Example: client says \"8h30\" = send \"07:30Z\"  \n- **NEVER mention** UTC, conversion, or timezone to the client\n\n### 2. DATE CALCULATIONS\n- **Use the current date** above for all calculations  \n- **Always verify:** if today = Monday, Nov 3, then “Tuesday” = Nov 4 (not 5!)  \n- **Count days precisely:** Monday +1 day = Tuesday, Monday +7 days = next Monday\n\n### 3. BOOKING (book_appointment)\n**Single flow:**\n1. Ask: name, email, phone (+33...), reason  \n2. Make ONE summary: \"Je réserve le [date] à [heure] pour [motif]. Nom : [X]. C’est bon ?\"  \n3. Client confirms → **Immediately call book_appointment**  \n4. Announce ONLY if successful: \"C’est réservé pour le [date] à [heure], au cabinet : 8 Allée du Progrès, 92170 Vanves.\"\n\n**FORBIDDEN:**\n- ❌ Saying “it’s booked” without calling the tool  \n- ❌ Making 2 summaries  \n- ❌ Announcing if the tool failed\n\n### 4. CANCELLATION (cancel_booking)\n**Mandatory sequence:**\n1. `find_booking` using email → get bookingUid  \n2. Filter: keep ONLY future appointments (not past ones)  \n3. If multiple: ask which one  \n4. Summarize and ask for confirmation  \n5. `cancel_booking` using bookingUid  \n6. Announce ONLY if successful  \n\n**FORBIDDEN:**\n- ❌ Calling cancel_booking without find_booking first  \n- ❌ Using an email as bookingUid  \n- ❌ Showing past appointments to the client\n\n### 5. RESCHEDULING (reschedule_booking)\n**Mandatory sequence:**\n1. `find_booking` using email → get bookingUid  \n2. Filter: keep ONLY future appointments  \n3. If multiple: ask which one  \n4. `check_availability` → propose 3–4 time slots  \n5. Client chooses → ask for confirmation  \n6. `reschedule_booking` with bookingUid + new UTC slot  \n7. Announce ONLY if successful  \n\n**Format for new slot:**\n- Parameter: `newStartUtc` (not newEventDateTimeUtc)  \n- Format: `YYYY-MM-DDTHH:MM:SSZ` in UTC  \n- Conversion: client says \"14h\" = send \"13:00Z\"\n\n## AVAILABLE TOOLS\n\n**check_availability:** Check available slots  \n**book_appointment:** Create a booking (after client confirmation)  \n**find_booking:** Find existing appointments (MANDATORY before cancel/reschedule)  \n**cancel_booking:** Cancel (with bookingUid from find_booking)  \n**reschedule_booking:** Reschedule (with bookingUid from find_booking)  \n**retrieve_files:** Internal FAQ\n\n## TONE AND STYLE\n- Warm, concise, efficient  \n- No visible technical jargon  \n- Offer 3–4 slots maximum  \n- Always mention the address in confirmations  \n- Communicate in **natural, polite French**, unless the client uses another language  \n\n## FINAL REMINDERS\n1. **Current date:** Always use the date above for calculations  \n2. **Time conversion:** Always +1h from API, -1h to API (silently)  \n3. **Past appointments:** Always filter after find_booking  \n4. **Confirmation:** Announce “c’est fait” ONLY after successful tool execution  \n5. **Find-then-Act:** ALWAYS find_booking before cancel/reschedule  \n6. **Default language:** Always French unless the client starts in English or Spanish"
        },
        "promptType": "define"
      },
      "retryOnFail": true,
      "typeVersion": 2.2
    },
    {
      "id": "08886616-2555-4668-9591-c75e609ef83c",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        1616,
        1920
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini",
          "cachedResultName": "gpt-4.1-mini"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "id": "credential-id",
          "name": "openAiApi Credential"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "3fbb24d7-e5a8-4bd6-8136-b5a4f205183a",
      "name": "Simple Memory",
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "position": [
        1792,
        1920
      ],
      "parameters": {
        "sessionKey": "={{ $json.from ?? $('Split out message1').item.json.from ?? $('WhatsApp Trigger').item.json.messages[0].from }}",
        "sessionIdType": "customKey",
        "contextWindowLength": 10
      },
      "typeVersion": 1.3
    },
    {
      "id": "1a0f6899-84ab-4e03-b51c-a7ab98b434f7",
      "name": "check_availability",
      "type": "n8n-nodes-base.httpRequestTool",
      "position": [
        1984,
        1920
      ],
      "parameters": {
        "url": "https://api.cal.com/v2/slots",
        "options": {},
        "sendQuery": true,
        "sendHeaders": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "eventTypeId"
            },
            {
              "name": "timeZone",
              "value": "Europe/Paris"
            },
            {
              "name": "start",
              "value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('parameters2_Value', `YYYY-MM-DD`, 'string') }}"
            },
            {
              "name": "end",
              "value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('parameters3_Value', `YYYY-MM-DD`, 'string') }}"
            }
          ]
        },
        "toolDescription": "Use this tool to retrieves the right slot",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_TOKEN_HERE"
            },
            {
              "name": "cal-api-version",
              "value": "2024-09-04"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "13c8a972-f126-4630-a6cc-919fd40eade5",
      "name": "book_appointment",
      "type": "@n8n/n8n-nodes-langchain.toolHttpRequest",
      "position": [
        2176,
        1920
      ],
      "parameters": {
        "url": "https://api.cal.com/v2/bookings",
        "method": "POST",
        "jsonBody": "{\n  \"start\": \"{eventDateTimeUtc}\",\n  \"eventTypeId\": XXXXXXX,\n  \"attendee\": {\n    \"name\": \"{name}\",\n    \"email\": \"{email}\",\n    \"timeZone\": \"Europe/Paris\",\n    \"phoneNumber\": \"{phoneE164}\",\n    \"language\": \"fr\"\n  },\n  \"bookingFieldsResponses\": {\n    \"title\": \"{title}\"\n  }\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "toolDescription": "Create a booking for the selected slot.\nInputs: eventTypeId (required), startUtc (ISO 8601 UTC, required), attendeeEmail (required), attendeeName (optional), notes (optional), timeZone (always pass “Europe/Paris” for the attendee).\nBehavior: If the user provides a local time (Europe/Paris), convert it to startUtc before booking. Confirm a short summary with the user (doctor, date/time in Europe/Paris, attendee email) before creating the booking. On conflicts or validation errors, propose the nearest 2–3 alternative slots from check_availability.\nOutput: Return the booking object including uid, start, and a human summary.",
        "parametersHeaders": {
          "values": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_TOKEN_HERE",
              "valueProvider": "fieldValue"
            },
            {
              "name": "cal-api-version",
              "value": "2024-08-13",
              "valueProvider": "fieldValue"
            },
            {
              "name": "Content-Type",
              "value": "application/json",
              "valueProvider": "fieldValue"
            }
          ]
        },
        "placeholderDefinitions": {
          "values": [
            {
              "name": "eventDateTimeUtc",
              "type": "string",
              "description": "ex. 2025-08-27T10:30:00Z (UTC avec Z)"
            },
            {
              "name": "name",
              "type": "string",
              "description": "ex : Jean Dupond"
            },
            {
              "name": "email",
              "type": "string",
              "description": "user@example.com"
            },
            {
              "name": "phoneE164",
              "type": "string",
              "description": "ex : +1234567890"
            },
            {
              "name": "title",
              "type": "string",
              "description": "ex : 1ère consultation"
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "2605d75d-a709-41e1-9e98-c3a00bf8f6b6",
      "name": "find_booking",
      "type": "@n8n/n8n-nodes-langchain.toolHttpRequest",
      "position": [
        2352,
        1920
      ],
      "parameters": {
        "url": "https://api.cal.com/v2/bookings",
        "sendQuery": true,
        "sendHeaders": true,
        "parametersQuery": {
          "values": [
            {
              "name": "attendeeEmail",
              "value": "{email}",
              "valueProvider": "fieldValue"
            }
          ]
        },
        "toolDescription": "Find a user’s booking to obtain its uid.\nInputs: \n- email (required), \n- dateHint (optional, e.g., \"2025-10-17 14:30 Europe/Paris\"), \n- eventTypeId=2957420 (implicit).\n\nBehavior:\n- Call GET /v2/bookings with attendeeEmail=email (and eventTypeId if needed).\n- If dateHint is provided, select the booking whose start is the closest match.\n- If multiple plausible matches remain or no dateHint, list 2–3 options (date/time/title in Europe/Paris) and ask the user to choose BEFORE continuing.\n- Return a compact object exposing at least:\n  { bookingUid: <uid>, start: <ISO>, title: <string>, eventTypeId: 2957420 }.\n\nNever return an email as bookingUid. The value bookingUid MUST be the API field \"uid\".",
        "parametersHeaders": {
          "values": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_TOKEN_HERE",
              "valueProvider": "fieldValue"
            },
            {
              "name": "cal-api-version",
              "value": "2024-08-13",
              "valueProvider": "fieldValue"
            },
            {
              "name": "Content-Type",
              "value": "application/json",
              "valueProvider": "fieldValue"
            }
          ]
        },
        "placeholderDefinitions": {
          "values": [
            {
              "name": "email",
              "type": "string",
              "description": "ex: “user@example.com”"
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "6aa95c18-ca31-47cc-b56d-fcb100b00b28",
      "name": "cancel_booking",
      "type": "@n8n/n8n-nodes-langchain.toolHttpRequest",
      "position": [
        2528,
        1920
      ],
      "parameters": {
        "url": "=https://api.cal.com/v2/bookings/{bookingUid}/cancel",
        "method": "POST",
        "jsonBody": "{\n  \"cancellationReason\": \"{reason}\",\n  \"cancelSubsequentBookings\": {cancelAll}\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "toolDescription": "Cancel a booking by uid.\nInputs: bookingUid (required), reason (string), cancelAll (boolean).\n\nCritical rule:\n- If bookingUid is missing or looks like an email, FIRST call find_booking using the user’s email (and date/time if given). \n- If multiple bookings exist, ask the user to pick the exact one (date/time in Europe/Paris). \n- Only after you have a valid uid, call POST /v2/bookings/{bookingUid}/cancel.\n- On policy/validation issues (e.g., booking in the past), explain briefly and propose alternatives (reschedule or contact support).\n\nOutput: Return the API response and a one-line human confirmation.",
        "parametersHeaders": {
          "values": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_TOKEN_HERE",
              "valueProvider": "fieldValue"
            },
            {
              "name": "cal-api-version",
              "value": "2024-08-13",
              "valueProvider": "fieldValue"
            },
            {
              "name": "Content-Type",
              "value": "application/json",
              "valueProvider": "fieldValue"
            }
          ]
        },
        "placeholderDefinitions": {
          "values": [
            {
              "name": "reason",
              "type": "string",
              "description": "ex: “Patient indisponible”"
            },
            {
              "name": "cancelAll",
              "type": "boolean"
            },
            {
              "name": "bookingUid",
              "type": "string"
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "bbd3b15c-715b-4fca-a867-0d00069d8e32",
      "name": "reschedule_booking",
      "type": "@n8n/n8n-nodes-langchain.toolHttpRequest",
      "position": [
        2704,
        1920
      ],
      "parameters": {
        "url": "=https://api.cal.com/v2/bookings/{bookingUid}/reschedule",
        "method": "POST",
        "jsonBody": "{\n  \"start\": \"{newStartUtc}\",\n  \"rescheduledBy\": \"{actorEmail}\",\n  \"reschedulingReason\": \"{reason}\"\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "toolDescription": "Reschedule a booking to a new time.\nInputs: bookingUid (required), newStartUtc (required, ISO 8601 UTC), actorEmail (required), reschedulingReason (optional).\nBehavior: If bookingUid is missing, call find_booking first and confirm the current slot with the user. If the user gives a local time (Europe/Paris), convert to newStartUtc before calling the API. If the requested time is unavailable, show the nearest 2–3 alternatives from check_availability and ask the user to pick one. Duration is defined by the event type and doesn’t change on reschedule.\nOutput: Return the updated booking object and a short human summary.\n",
        "parametersHeaders": {
          "values": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_TOKEN_HERE",
              "valueProvider": "fieldValue"
            },
            {
              "name": "cal-api-version",
              "value": "2024-08-13",
              "valueProvider": "fieldValue"
            },
            {
              "name": "Content-Type",
              "value": "application/json",
              "valueProvider": "fieldValue"
            }
          ]
        },
        "placeholderDefinitions": {
          "values": [
            {
              "name": "bookingUid",
              "type": "string"
            },
            {
              "name": "newStartUtc",
              "type": "string",
              "description": "ex. 2025-09-14T13:30:00Z"
            },
            {
              "name": "actorEmail",
              "type": "string"
            },
            {
              "name": "reason",
              "type": "string"
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "7e17794b-e460-4e80-a63e-1c2aa0d2fd7a",
      "name": "WhatsApp Trigger",
      "type": "n8n-nodes-base.whatsAppTrigger",
      "position": [
        -336,
        1552
      ],
      "webhookId": "72992693-7c18-4104-826e-345d33fde3af",
      "parameters": {
        "options": {},
        "updates": [
          "messages"
        ]
      },
      "credentials": {
        "whatsAppTriggerApi": {
          "id": "credential-id",
          "name": "whatsAppTriggerApi Credential"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "22d8b33c-335e-4139-b424-d9120e3ae749",
      "name": "Edit Fields 1",
      "type": "n8n-nodes-base.set",
      "position": [
        1360,
        1776
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "5a5d4ec5-61d5-471c-bab7-135528008e6c",
              "name": "user_prompt",
              "type": "string",
              "value": "={{ $json.text.body }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "bb5d4712-8536-4e17-b0f2-12bb33a26340",
      "name": "Edit Fields 2",
      "type": "n8n-nodes-base.set",
      "position": [
        1360,
        1552
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "5a5d4ec5-61d5-471c-bab7-135528008e6c",
              "name": "user_prompt",
              "type": "string",
              "value": "={{ $json.content }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "2f41f9fa-07ea-411d-9d64-33f038eb58c6",
      "name": "Edit Fields 3",
      "type": "n8n-nodes-base.set",
      "position": [
        1360,
        1360
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "5a5d4ec5-61d5-471c-bab7-135528008e6c",
              "name": "user_prompt",
              "type": "string",
              "value": "={{ $json.text }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "9c997b0a-3169-4235-9398-35fb69016801",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -416,
        1264
      ],
      "parameters": {
        "color": 4,
        "width": 896,
        "height": 496,
        "content": "# WhatsApp Trigger → Message Router\n\n**Receives incoming messages** from WhatsApp Business API via webhook.\n\n**Process:**\n- WhatsApp Trigger → Detects incoming messages\n- Split Out Messages → Processes each message individually  \n- Identify Message Type → Routes based on type (Text/Audio/Image)\n\n**Outputs:** 3 branches for different message types"
      },
      "typeVersion": 1
    },
    {
      "id": "9de6353a-2aa9-4835-a4fa-bccd6e63f6b0",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        528,
        928
      ],
      "parameters": {
        "color": 6,
        "width": 992,
        "height": 1008,
        "content": "# Multi-Modal Processing Pipeline\n\n**Processes all message types** and prepares them for the AI Agent.\n\n### Audio Branch:\n- Get Audio URL → Download Audio → Audio Transcriber (Whisper) → Edit Fields 3\n\n### Image Branch:\n- Get Image URL → Download Image → Analyze Image (GPT-4 Vision) → Edit Fields 2\n\n### Text Branch:\n- Direct text message → Edit Fields 1\n\n**Output:** All three branches convert their input into `user_prompt` field for the AI Agent"
      },
      "typeVersion": 1
    },
    {
      "id": "0a969050-7193-463a-89c6-3aa11f46c517",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1568,
        928
      ],
      "parameters": {
        "color": 5,
        "width": 1280,
        "height": 1136,
        "content": "# AI Agent: Smart Appointment Manager + Response Delivery\n\n**Uses GPT-4** to understand requests in natural language and execute appropriate actions.\n\n### Core Components:\n- **OpenAI Chat Model** - GPT-4 for advanced reasoning\n- **Simple Memory** - Maintains conversation context\n- **AI Agent** - Orchestrates tools based on user intent\n\n### Cal.com Tools (5):\n✅ **check_availability** - Checks available time slots\n✅ **book_appointment** - Creates new appointments\n✅ **find_booking** - Retrieves existing bookings\n✅ **cancel_booking** - Cancels appointments by ID\n✅ **reschedule_booking** - Changes appointment date/time\n\n### Output Processing:\n- **Code Node** - Converts markdown **bold** to Unicode 𝐛𝐨𝐥𝐝 for better WhatsApp display\n- **Send Message** - Sends formatted response via WhatsApp Graph API\n\n**The agent automatically selects the right tool based on the conversation and delivers a professional, well-formatted response.**"
      },
      "typeVersion": 1
    },
    {
      "id": "6e57c084-87f1-473f-b7d2-bb16e00f4a25",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1120,
        928
      ],
      "parameters": {
        "width": 624,
        "height": 1120,
        "content": "# WhatsApp AI Appointment Agent with Cal.com\n\n### Automate your appointment booking 24/7 via WhatsApp\n\n**What it does:**\n- Understands text, audio, and image messages\n- Checks real-time availability in Cal.com\n- Books, reschedules, and cancels appointments autonomously\n- Responds in natural language\n- Maintains conversation context\n\n**Quick Setup:**\n1. Create Meta App at developers.facebook.com\n2. Add WhatsApp product to your app\n3. Connect credentials (WhatsApp, Cal.com, OpenAI)\n4. Paste webhook URL in Meta Console\n5. Customize AI Agent personality\n6. Test & deploy!\n\n---\n\n## ⚙️ Configuration Required\n\n### 🔐 Credentials:\n- **WhatsApp Business API** - phone_number_id + access_token from Meta\n- **Cal.com API Key** - Get from cal.com/settings/developer\n- **OpenAI API Key** - For GPT-4, Whisper, and Vision\n\n### 📝 Customization:\n- Edit AI Agent **System Message** to define personality and context\n- Configure Cal.com **event types** (duration, pricing, availability)\n- Set **timezone** (default: Europe/Paris)\n- Adjust response **language** in System Message\n\n### 🚀 Deployment:\nTest with different message types (text/audio/image) before activating in production.\n\n💬 Need help? Contact: **[LinkedIn](https://www.linkedin.com/in/st%C3%A9phane-bordas-3439b4179/)** "
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Code": {
      "main": [
        [
          {
            "node": "Send message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent": {
      "main": [
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "find_booking": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Analyze image": {
      "main": [
        [
          {
            "node": "Edit Fields 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields 1": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields 2": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields 3": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Audio URL": {
      "main": [
        [
          {
            "node": "Download Audio",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Image URL": {
      "main": [
        [
          {
            "node": "Download Image",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Simple Memory": {
      "ai_memory": [
        [
          {
            "node": "AI Agent",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Download Audio": {
      "main": [
        [
          {
            "node": "Audio Transcriber",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Image": {
      "main": [
        [
          {
            "node": "Analyze image",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "cancel_booking": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "WhatsApp Trigger": {
      "main": [
        [
          {
            "node": "Split out message1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "book_appointment": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Audio Transcriber": {
      "main": [
        [
          {
            "node": "Edit Fields 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Split out message1": {
      "main": [
        [
          {
            "node": "Identify and ReRoute Message Types1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "check_availability": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "reschedule_booking": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Identify and ReRoute Message Types1": {
      "main": [
        [
          {
            "node": "Get Audio URL",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Get Image URL",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Edit Fields 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}