{
  "id": "smart-business-assistant-v1",
  "name": "Smart Business Assistant",
  "nodes": [
    {
      "id": "sba-n-00",
      "name": "Setup Instructions",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        80,
        80
      ],
      "parameters": {
        "color": 7,
        "width": 700,
        "height": 320,
        "content": "## Smart Business Assistant — Setup Checklist\n\n1. Open the **AI Agent** node and edit the system prompt: replace `[Your Business Name]` and `[describe your business...]` with your real business name and category. Adjust tone, hours, and intent-detection keywords as needed.\n2. Open **Confidence Gate** — default escalation threshold is 0.6. Lower it (0.4) so the AI handles more itself, raise it (0.8) to escalate more often.\n3. Connect credentials: **Nashir API** (used everywhere), **OpenAI** (Chat Model + Lead Scoring + Embeddings), **Gemini API Key** (Voice / Vision / Document), **Supabase** (Vector Store).\n4. In Supabase: enable pgvector, create the `documents` table, upload your knowledge base (see setup guide).\n5. In nashir.ai: Settings → Webhooks → add this workflow's webhook URL, enable `message.received`.\n6. Pick your **WhatsApp Account** in the Send WhatsApp Reply, Send Handoff Acknowledgment, and Pause AI nodes.\n7. Pick your **Telegram Account** in the Telegram Handoff Alert node.\n8. Activate the workflow.\n\nFull guide: nashir.ai/docs/smart-business-assistant"
      },
      "typeVersion": 1
    },
    {
      "id": "sba-n-02",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        500,
        460
      ],
      "webhookId": "sba-webhook-nashir-001",
      "parameters": {
        "path": "smart-business-assistant",
        "options": {
          "responseCode": 200,
          "responseData": "json",
          "noResponseBody": false
        },
        "httpMethod": "POST",
        "responseMode": "onReceived"
      },
      "typeVersion": 2
    },
    {
      "id": "sba-n-03",
      "name": "Filter Event",
      "type": "n8n-nodes-base.if",
      "position": [
        740,
        460
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "filter-event-01",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $('Webhook').item.json.body.event }}",
              "rightValue": "message.received"
            }
          ]
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "sba-n-04",
      "name": "Get AI Status",
      "type": "n8n-nodes-nashir.nashirWhatsApp",
      "position": [
        980,
        460
      ],
      "parameters": {
        "phone": "={{ $('Webhook').item.json.body.data.phone }}",
        "operation": "getAiStatus"
      },
      "credentials": {
        "nashirApi": {
          "id": "",
          "name": "Nashir API"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "sba-n-05",
      "name": "Check AI Enabled",
      "type": "n8n-nodes-base.if",
      "position": [
        1220,
        460
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-ai-01",
              "operator": {
                "name": "filter.operator.notEquals",
                "type": "boolean",
                "operation": "notEquals"
              },
              "leftValue": "={{ $json.ai_enabled }}",
              "rightValue": false
            }
          ]
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "sba-n-06",
      "name": "Switch Media Type",
      "type": "n8n-nodes-base.switch",
      "position": [
        1460,
        460
      ],
      "parameters": {
        "mode": "rules",
        "rules": {
          "values": [
            {
              "outputKey": "text",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "sw-text-01",
                    "operator": {
                      "type": "string",
                      "operation": "notEquals"
                    },
                    "leftValue": "={{ $('Webhook').item.json.body.data.media_type }}",
                    "rightValue": "audio"
                  },
                  {
                    "id": "sw-text-02",
                    "operator": {
                      "type": "string",
                      "operation": "notEquals"
                    },
                    "leftValue": "={{ $('Webhook').item.json.body.data.media_type }}",
                    "rightValue": "image"
                  },
                  {
                    "id": "sw-text-03",
                    "operator": {
                      "type": "string",
                      "operation": "notEquals"
                    },
                    "leftValue": "={{ $('Webhook').item.json.body.data.media_type }}",
                    "rightValue": "document"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "audio",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "sw-voice-01",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('Webhook').item.json.body.data.media_type }}",
                    "rightValue": "audio"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "image",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "sw-image-01",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('Webhook').item.json.body.data.media_type }}",
                    "rightValue": "image"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "document",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "sw-doc-01",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('Webhook').item.json.body.data.media_type }}",
                    "rightValue": "document"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.4
    },
    {
      "id": "sba-n-07a",
      "name": "Text Pass-through",
      "type": "n8n-nodes-base.set",
      "position": [
        1700,
        260
      ],
      "parameters": {
        "mode": "manual",
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "tp-01",
              "name": "message_content",
              "type": "string",
              "value": "={{ $('Webhook').item.json.body.data.content }}"
            }
          ]
        },
        "duplicateItem": false,
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "sba-n-07b1",
      "name": "Download Voice Note",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Downloads the WhatsApp voice note binary via nashir.ai's media proxy. The proxy uses your Nashir API credential and handles Meta auth server-side, so you don't need to manage WhatsApp tokens in n8n. Binary is exposed on the `data` property for the next node.",
      "position": [
        1700,
        400
      ],
      "parameters": {
        "url": "=https://nashir.ai/api/v1/media/{{ $('Webhook').item.json.body.data.media_id }}",
        "method": "GET",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file",
              "outputPropertyName": "data"
            }
          }
        },
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "nashirApi"
      },
      "credentials": {
        "nashirApi": {
          "id": "",
          "name": "Nashir API"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "sba-n-07b1c",
      "name": "Audio to Base64",
      "type": "n8n-nodes-base.code",
      "notes": "Pulls the downloaded binary out of $input.item.binary.data and exposes its base64 string + mime type as plain $json fields. Worked around n8n bug where $binary.data.data resolves to '' inside HTTP Request jsonBody expressions for some Gemini calls.",
      "position": [
        1860,
        400
      ],
      "parameters": {
        "jsCode": "const dataBuffer = await this.helpers.getBinaryDataBuffer(0, 'data');\nconst binary = $input.item.binary.data;\nreturn [{\n  json: {\n    base64_data: dataBuffer.toString('base64'),\n    mime_type: binary.mimeType\n  }\n}];",
        "language": "javaScript"
      },
      "typeVersion": 2
    },
    {
      "id": "sba-n-07b2",
      "name": "Gemini Voice Transcribe",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Reads `$json.mime_type` and `$json.base64_data` produced by the preceding *to Base64* Code node. Response shape: candidates[0].content.parts[0].text — Format History picks this up via the merge-data fallback chain.",
      "position": [
        2020,
        400
      ],
      "parameters": {
        "url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ { contents: [{ parts: [{ inline_data: { mime_type: $json.mime_type, data: $json.base64_data } }, { text: 'Transcribe this voice message exactly as spoken. Return only the transcription text, no additional commentary. If the language is Arabic, return Arabic text. If English, return English.' }] }] } }}",
        "sendBody": true,
        "sendQuery": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpQueryAuth",
        "queryParameters": {
          "parameters": [
            {
              "name": "key",
              "value": "={{ $credentials.httpQueryAuth.value }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "sba-n-07c0",
      "name": "Download Image",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Downloads the WhatsApp image binary via nashir.ai's media proxy. Same setup as Download Voice Note — uses your Nashir API credential.",
      "position": [
        1700,
        540
      ],
      "parameters": {
        "url": "=https://nashir.ai/api/v1/media/{{ $('Webhook').item.json.body.data.media_id }}",
        "method": "GET",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file",
              "outputPropertyName": "data"
            }
          }
        },
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "nashirApi"
      },
      "credentials": {
        "nashirApi": {
          "id": "",
          "name": "Nashir API"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "sba-n-07c0c",
      "name": "Image to Base64",
      "type": "n8n-nodes-base.code",
      "notes": "Pulls the downloaded binary out of $input.item.binary.data and exposes its base64 string + mime type as plain $json fields. Worked around n8n bug where $binary.data.data resolves to '' inside HTTP Request jsonBody expressions for some Gemini calls.",
      "position": [
        1860,
        540
      ],
      "parameters": {
        "jsCode": "const dataBuffer = await this.helpers.getBinaryDataBuffer(0, 'data');\nconst binary = $input.item.binary.data;\nreturn [{\n  json: {\n    base64_data: dataBuffer.toString('base64'),\n    mime_type: binary.mimeType\n  }\n}];",
        "language": "javaScript"
      },
      "typeVersion": 2
    },
    {
      "id": "sba-n-07c",
      "name": "Gemini Vision",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Reads `$json.mime_type` and `$json.base64_data` produced by the preceding *to Base64* Code node. Response shape: candidates[0].content.parts[0].text — Format History picks this up via the merge-data fallback chain.",
      "position": [
        2020,
        540
      ],
      "parameters": {
        "url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ { contents: [{ parts: [{ inline_data: { mime_type: $json.mime_type, data: $json.base64_data } }, { text: 'Describe this image. Extract all visible text. Identify any products, prices, or services shown.' }] }] } }}",
        "sendBody": true,
        "sendQuery": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpQueryAuth",
        "queryParameters": {
          "parameters": [
            {
              "name": "key",
              "value": "={{ $credentials.httpQueryAuth.value }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "sba-n-07d0",
      "name": "Download Document",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Downloads the WhatsApp document binary via nashir.ai's media proxy. Same setup as Download Voice Note — uses your Nashir API credential.",
      "position": [
        1700,
        680
      ],
      "parameters": {
        "url": "=https://nashir.ai/api/v1/media/{{ $('Webhook').item.json.body.data.media_id }}",
        "method": "GET",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file",
              "outputPropertyName": "data"
            }
          }
        },
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "nashirApi"
      },
      "credentials": {
        "nashirApi": {
          "id": "",
          "name": "Nashir API"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "sba-n-07d0c",
      "name": "Document to Base64",
      "type": "n8n-nodes-base.code",
      "notes": "Pulls the downloaded binary out of $input.item.binary.data and exposes its base64 string + mime type as plain $json fields. Worked around n8n bug where $binary.data.data resolves to '' inside HTTP Request jsonBody expressions for some Gemini calls.",
      "position": [
        1860,
        680
      ],
      "parameters": {
        "jsCode": "const dataBuffer = await this.helpers.getBinaryDataBuffer(0, 'data');\nconst binary = $input.item.binary.data;\nreturn [{\n  json: {\n    base64_data: dataBuffer.toString('base64'),\n    mime_type: binary.mimeType\n  }\n}];",
        "language": "javaScript"
      },
      "typeVersion": 2
    },
    {
      "id": "sba-n-07d",
      "name": "Gemini Document",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Reads `$json.mime_type` and `$json.base64_data` produced by the preceding *to Base64* Code node. Response shape: candidates[0].content.parts[0].text — Format History picks this up via the merge-data fallback chain.",
      "position": [
        2020,
        680
      ],
      "parameters": {
        "url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ { contents: [{ parts: [{ inline_data: { mime_type: $json.mime_type, data: $json.base64_data } }, { text: 'Extract and summarize all text content from this document. List any products, services, prices, or important information found.' }] }] } }}",
        "sendBody": true,
        "sendQuery": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpQueryAuth",
        "queryParameters": {
          "parameters": [
            {
              "name": "key",
              "value": "={{ $credentials.httpQueryAuth.value }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "sba-n-08",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        2200,
        460
      ],
      "parameters": {
        "mode": "append",
        "options": {},
        "numberInputs": 4
      },
      "typeVersion": 3
    },
    {
      "id": "sba-n-09",
      "name": "Get Conversation History",
      "type": "n8n-nodes-nashir.nashirWhatsApp",
      "position": [
        2200,
        460
      ],
      "parameters": {
        "limit": 20,
        "phone": "={{ $('Webhook').item.json.body.data.phone }}",
        "operation": "getConversationHistory"
      },
      "credentials": {
        "nashirApi": {
          "id": "",
          "name": "Nashir API"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "sba-n-10",
      "name": "Format History",
      "type": "n8n-nodes-base.code",
      "position": [
        2440,
        460
      ],
      "parameters": {
        "jsCode": "const historyData = $input.all()[0].json;\nconst webhookData = $('Webhook').item.json.body.data || {};\nconst mergeData = $('Merge').item.json || {};\n\nlet messages = [];\nif (Array.isArray(historyData)) {\n  messages = historyData;\n} else if (Array.isArray(historyData.messages)) {\n  messages = historyData.messages;\n} else if (Array.isArray(historyData.data)) {\n  messages = historyData.data;\n}\n\nlet conversation_context = 'No previous conversation.';\nif (messages.length > 0) {\n  const lines = messages.map(msg => {\n    const role = msg.role === 'user' ? 'Customer' : 'Assistant';\n    if (msg.media_type && msg.media_type !== 'text' && msg.media_type !== '') {\n      return role + ': [sent ' + msg.media_type + ']';\n    }\n    return role + ': ' + (msg.content || '');\n  });\n  conversation_context = lines.join('\\n');\n}\n\nconst message_content =\n  mergeData.message_content ||\n  mergeData.text ||\n  (mergeData.candidates && mergeData.candidates[0] && mergeData.candidates[0].content && mergeData.candidates[0].content.parts && mergeData.candidates[0].content.parts[0] && mergeData.candidates[0].content.parts[0].text) ||\n  webhookData.content ||\n  '';\n\nreturn [{\n  json: {\n    data: webhookData,\n    message_content,\n    conversation_context\n  }\n}];",
        "language": "javaScript"
      },
      "typeVersion": 2
    },
    {
      "id": "sba-n-11",
      "name": "AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2680,
        460
      ],
      "parameters": {
        "text": "={{ $json.message_content }}",
        "options": {
          "maxIterations": 5,
          "systemMessage": "=You are a helpful WhatsApp assistant for [Your Business Name] — a [describe your business, e.g., restaurant, clinic, boutique, online store].\n\nTone: Be warm and friendly. Adapt to the customer's language (Arabic or English). Keep replies concise and under 150 words.\n\nBusiness hours: 9am to 9pm, 7 days a week. If customers message outside hours, acknowledge their message and let them know you'll respond first thing in the morning.\n\nYour goals:\n- Answer product/service questions helpfully\n- Qualify leads: if the customer expresses buying intent, gather their name, what they're interested in, and preferred contact time\n- Recognize buying-intent language in Arabic (سعر، كم، أريد، متوفر، توصيل، أشتري، اطلب) and English (price, want, available, delivery, buy, order)\n\nTools available:\n- business_knowledge_base: search this whenever the customer asks about specific products, prices, hours, location, or policies. Never invent information not in the knowledge base.\n\nEscalate to human when:\n- Customer expresses frustration or asks to speak with a person\n- You don't know the answer to a specific question about your business\n- The customer has a complaint or refund request\n\nWhen escalating, set needs_human=true in your response. Otherwise keep it false.\n\nCONVERSATION HISTORY (oldest first):\n${$json.conversation_context || 'No previous conversation.'}\n\nOUTPUT FORMAT — return strict JSON only, no other text:\n{\"reply\": \"<message to send to customer>\", \"confidence\": <0.0-1.0>, \"language_detected\": \"ar\" or \"en\", \"needs_human\": <true or false>, \"reasoning\": \"<brief reason>\"}"
        },
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "sba-n-11a",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        2680,
        700
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "gpt-4o-mini"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "id": "",
          "name": "OpenAI"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "sba-n-11b",
      "name": "Supabase Vector Store",
      "type": "@n8n/n8n-nodes-langchain.vectorStoreSupabase",
      "position": [
        2920,
        700
      ],
      "parameters": {
        "mode": "retrieve-as-tool",
        "options": {
          "queryName": "match_documents"
        },
        "tableName": {
          "__rl": true,
          "mode": "list",
          "value": "documents",
          "cachedResultName": "documents"
        },
        "toolDescription": "Search the business knowledge base for specific information about products, services, prices, hours, location, policies, or FAQs. Use this whenever the customer asks something specific."
      },
      "credentials": {
        "supabaseApi": {
          "id": "",
          "name": "Supabase"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "sba-n-11c",
      "name": "OpenAI Embeddings",
      "type": "@n8n/n8n-nodes-langchain.embeddingsOpenAi",
      "position": [
        2920,
        920
      ],
      "parameters": {
        "model": "text-embedding-3-small",
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "id": "",
          "name": "OpenAI"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "sba-n-12",
      "name": "Parse AI Response",
      "type": "n8n-nodes-base.code",
      "position": [
        2920,
        460
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nconst agentOut = items[0].json;\nconst fh = $('Format History').item.json || {};\n\ntry {\n  const raw = agentOut.output || agentOut.text || JSON.stringify(agentOut);\n  const clean = raw.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```$/, '').trim();\n  const parsed = JSON.parse(clean);\n\n  return [{\n    json: {\n      ...fh,\n      reply: parsed.reply || 'سأعود إليك قريباً. / I will get back to you soon.',\n      confidence: typeof parsed.confidence === 'number' ? parsed.confidence : 0.5,\n      language_detected: parsed.language_detected || 'ar',\n      needs_human: Boolean(parsed.needs_human),\n      reasoning: parsed.reasoning || ''\n    }\n  }];\n} catch (e) {\n  return [{\n    json: {\n      ...fh,\n      reply: 'سأعود إليك قريباً. / I will get back to you soon.',\n      confidence: 0,\n      language_detected: 'ar',\n      needs_human: true,\n      reasoning: 'JSON parse error — routing to human: ' + e.message\n    }\n  }];\n}",
        "language": "javaScript"
      },
      "typeVersion": 2
    },
    {
      "id": "sba-n-13",
      "name": "Confidence Gate",
      "type": "n8n-nodes-base.if",
      "notes": "Threshold for escalating to human: 0.6\n- Lower (e.g., 0.4) = AI handles more questions itself\n- Higher (e.g., 0.8) = AI escalates more often\nAdjust based on how confident you want the AI to be before replying directly.",
      "position": [
        3160,
        460
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "cg-01",
              "operator": {
                "name": "filter.operator.equals",
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.needs_human }}",
              "rightValue": true
            },
            {
              "id": "cg-02",
              "operator": {
                "name": "filter.operator.lt",
                "type": "number",
                "operation": "lt"
              },
              "leftValue": "={{ $json.confidence }}",
              "rightValue": 0.6
            }
          ]
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "sba-n-14",
      "name": "Send WhatsApp Reply",
      "type": "n8n-nodes-nashir.nashirWhatsApp",
      "notes": "Select your WhatsApp account from the Account dropdown before activating.",
      "position": [
        3400,
        280
      ],
      "parameters": {
        "to": "={{ $('Webhook').item.json.body.data.phone }}",
        "account": "",
        "content": "={{ $json.reply }}",
        "operation": "sendMessage",
        "messageType": "text"
      },
      "credentials": {
        "nashirApi": {
          "id": "",
          "name": "Nashir API"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "sba-n-13a",
      "name": "Send Handoff Acknowledgment",
      "type": "n8n-nodes-nashir.nashirWhatsApp",
      "notes": "Sent on the handoff branch so the customer doesn't see silence while the team is alerted. Edit the message text directly here to customise.",
      "position": [
        3400,
        640
      ],
      "parameters": {
        "to": "={{ $('Webhook').item.json.body.data.phone }}",
        "account": "",
        "content": "Thanks for your message! Let me connect you with a team member who can help — they'll get back to you shortly.\nشكراً لرسالتك! سأحوّلك إلى أحد أعضاء فريقنا للرد عليك في أقرب وقت.",
        "operation": "sendMessage",
        "messageType": "text"
      },
      "credentials": {
        "nashirApi": {
          "id": "",
          "name": "Nashir API"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "sba-n-13b",
      "name": "Pause AI (1h)",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Pauses AI for 3600 s (1 hour) for this contact via the nashir.ai pause API. Customer or agent activity inside the pause window slides ai_auto_resume_at forward by another 2 h, so an active conversation never gets the AI butting back in. Uses your existing Nashir API credential.",
      "position": [
        3640,
        640
      ],
      "parameters": {
        "url": "https://nashir.ai/api/v1/ai/pause",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ { phone: $('Webhook').item.json.body.data.phone, duration_seconds: 3600, reason: 'handoff' } }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "nashirApi"
      },
      "credentials": {
        "nashirApi": {
          "id": "",
          "name": "Nashir API"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "sba-n-13c",
      "name": "Telegram Handoff Alert",
      "type": "n8n-nodes-nashir.nashirTelegram",
      "notes": "Sends a one-off Telegram message via your nashir.ai-connected Telegram account (DM, group, or channel). Pick the destination from the Account dropdown — no BotFather setup, no chat-id discovery. Requires the n8n-nodes-nashir package at v0.3.1+ and a Nashir API credential.",
      "position": [
        3880,
        640
      ],
      "parameters": {
        "text": "=🚨 *Handoff Needed*\n\n👤 Customer: `{{ $('Webhook').item.json.body.data.phone }}`\n💬 Message: {{ $('Webhook').item.json.body.data.content }}\n\n⏰ AI paused for 1 hour\n📱 Reply in nashir.ai inbox to take over.",
        "account": "",
        "operation": "sendNotification",
        "parseMode": "Markdown",
        "disableNotification": false
      },
      "credentials": {
        "nashirApi": {
          "id": "",
          "name": "Nashir API"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "sba-n-15",
      "name": "Lead Scoring",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3400,
        460
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/chat/completions",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ {model:'gpt-4o-mini',temperature:0,response_format:{type:'json_object'},messages:[{role:'system',content:'You are a lead scoring expert. Analyze the customer interaction and return ONLY valid JSON with these fields: intent_score (integer 0-10), extracted (object: name or null, phone or null, email or null, budget_signal or null, product_interest or null, urgency as low/medium/high, location or null), summary (1 sentence string), recommended_action (string), lead_stage (cold/warm/hot/ready_to_close). Scoring guide: 0-2 greeting only; 3-4 general info; 5-6 comparing or specific questions; 7-8 asking price or serious intent; 9-10 explicit buy signal.'},{role:'user',content:'CONVERSATION HISTORY:\\n'+$json.conversation_context+'\\n\\nCURRENT MESSAGE: '+$json.message_content}]} }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openAiApi"
      },
      "credentials": {
        "openAiApi": {
          "id": "",
          "name": "OpenAI"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "sba-n-16",
      "name": "Keyword Detection",
      "type": "n8n-nodes-base.code",
      "position": [
        3640,
        460
      ],
      "parameters": {
        "jsCode": "const item = $input.all()[0].json;\nconst parseAI = $('Parse AI Response').item.json || {};\n\nlet leadData = {};\ntry {\n  const raw = item.choices?.[0]?.message?.content || item.output || '{}';\n  leadData = JSON.parse(raw);\n} catch (e) {\n  leadData = { intent_score: 0, lead_stage: 'cold', extracted: {}, summary: '', recommended_action: '' };\n}\n\n// Hardcoded buying-intent keywords. Edit this list to tune lead detection.\nconst KEYWORDS_AR = ['سعر','كم','أريد','متوفر','توصيل','أشتري','اطلب'];\nconst KEYWORDS_EN = ['price','want','available','delivery','buy','order','quote'];\n\nconst message_content = parseAI.message_content || '';\nconst lowerMsg = message_content.toLowerCase();\nconst keyword_match = [...KEYWORDS_AR, ...KEYWORDS_EN].some(k => lowerMsg.includes(k.toLowerCase()));\n\nreturn [{\n  json: {\n    ...parseAI,\n    intent_score: leadData.intent_score || 0,\n    extracted: leadData.extracted || {},\n    summary: leadData.summary || '',\n    recommended_action: leadData.recommended_action || '',\n    lead_stage: leadData.lead_stage || 'cold',\n    keyword_match\n  }\n}];",
        "language": "javaScript"
      },
      "typeVersion": 2
    },
    {
      "id": "sba-n-18",
      "name": "Tag Contact",
      "type": "n8n-nodes-nashir.nashirContact",
      "position": [
        4120,
        460
      ],
      "parameters": {
        "tags": "={{ $json.needs_human ? 'handoff' : $json.lead_stage === 'ready_to_close' ? 'hot_lead,ready_to_close' : $json.lead_stage === 'hot' ? 'hot_lead' : $json.lead_stage === 'warm' ? 'warm_lead' : 'cold_lead' }}",
        "phone": "={{ $('Webhook').item.json.body.data.phone }}",
        "operation": "updateTags"
      },
      "credentials": {
        "nashirApi": {
          "id": "",
          "name": "Nashir API"
        }
      },
      "typeVersion": 1
    }
  ],
  "pinData": {},
  "settings": {
    "callerPolicy": "workflowsFromSameOwner",
    "errorWorkflow": "",
    "executionOrder": "v1",
    "saveManualExecutions": true
  },
  "connections": {
    "Merge": {
      "main": [
        [
          {
            "node": "Get Conversation History",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook": {
      "main": [
        [
          {
            "node": "Filter Event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent": {
      "main": [
        [
          {
            "node": "Parse AI Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Event": {
      "main": [
        [
          {
            "node": "Get AI Status",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Lead Scoring": {
      "main": [
        [
          {
            "node": "Keyword Detection",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini Vision": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Get AI Status": {
      "main": [
        [
          {
            "node": "Check AI Enabled",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pause AI (1h)": {
      "main": [
        [
          {
            "node": "Telegram Handoff Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Image": {
      "main": [
        [
          {
            "node": "Image to Base64",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format History": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Audio to Base64": {
      "main": [
        [
          {
            "node": "Gemini Voice Transcribe",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Confidence Gate": {
      "main": [
        [
          {
            "node": "Send Handoff Acknowledgment",
            "type": "main",
            "index": 0
          },
          {
            "node": "Lead Scoring",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send WhatsApp Reply",
            "type": "main",
            "index": 0
          },
          {
            "node": "Lead Scoring",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini Document": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "Image to Base64": {
      "main": [
        [
          {
            "node": "Gemini Vision",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check AI Enabled": {
      "main": [
        [
          {
            "node": "Switch Media Type",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Download Document": {
      "main": [
        [
          {
            "node": "Document to Base64",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Keyword Detection": {
      "main": [
        [
          {
            "node": "Tag Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Embeddings": {
      "ai_embedding": [
        [
          {
            "node": "Supabase Vector Store",
            "type": "ai_embedding",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI Response": {
      "main": [
        [
          {
            "node": "Confidence Gate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch Media Type": {
      "main": [
        [
          {
            "node": "Text Pass-through",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Download Voice Note",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Download Image",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Download Document",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Text Pass-through": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Document to Base64": {
      "main": [
        [
          {
            "node": "Gemini Document",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Voice Note": {
      "main": [
        [
          {
            "node": "Audio to Base64",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Supabase Vector Store": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Telegram Handoff Alert": {
      "main": [
        [
          {
            "node": "Tag Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini Voice Transcribe": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Get Conversation History": {
      "main": [
        [
          {
            "node": "Format History",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Handoff Acknowledgment": {
      "main": [
        [
          {
            "node": "Pause AI (1h)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}