{
  "id": "aIxJHCH85vzN4fFw",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "AI-Powered-Customer-Feedback-Automation",
  "tags": [],
  "nodes": [
    {
      "id": "0b32072f-1719-40da-99e4-f4e8b293049b",
      "name": "Tally Trigger",
      "type": "n8n-nodes-tallyforms.tallyTrigger",
      "position": [
        -3184,
        96
      ],
      "parameters": {
        "formId": "xXVN5G"
      },
      "credentials": {
        "tallyApi": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "retryOnFail": true,
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "9fabd87c-f43a-41c3-b399-c3872ad80e8f",
      "name": "Field Mapping",
      "type": "n8n-nodes-base.set",
      "position": [
        -2992,
        96
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "bb9282d6-eb49-4a5d-a17f-314fd87deaad",
              "name": "Customer Name",
              "type": "string",
              "value": "={{ $json.question_NoKEOG }}"
            },
            {
              "id": "16c79cab-b7bd-481f-8130-b667c8081bbd",
              "name": "Email Address",
              "type": "string",
              "value": "={{ $json.question_qVxgN2 }}"
            },
            {
              "id": "4c3c8905-8ce0-4889-b64e-e0c8ac0c621d",
              "name": "Message",
              "type": "string",
              "value": "={{ $json.question_QVgjEg }}"
            },
            {
              "id": "7a89ac6e-c96b-48bc-8ab3-bab2cfdfabb3",
              "name": "Image URL",
              "type": "string",
              "value": "={{ $json.question_9QLxxG }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "34493ab7-6fb2-4a37-bb78-2bca0b2a264b",
      "name": "Sentiment Analysis",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        -2400,
        -480
      ],
      "parameters": {
        "text": "={{ $json.Message }}",
        "batching": {},
        "messages": {
          "messageValues": [
            {
              "message": "=Analyze the sentiment of the `text` provided in the `JSON` input. Your response must be exactly one of the following three labels: 'Positive', 'Negative', or 'Neutral'.\n\nClassification Rules:\n- For mixed sentiment, select the label for the dominant emotion.\n- Crucially, classify as 'Neutral' if the text is ambiguous, empty, or consists *only* of punctuation, symbols, or numbers (e.g., \"!!!\", \"?\", \"#$%\", \"123\").\n\nDo NOT use any other labels or provide any explanations."
            }
          ]
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.7
    },
    {
      "id": "419f51d4-f852-40ad-8eab-8e6a2080348b",
      "name": "If",
      "type": "n8n-nodes-base.if",
      "position": [
        -2784,
        304
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "b65c1b9f-d4ae-4160-94a0-e8ed07dac312",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json[\"Image URL\"] }}",
              "rightValue": ""
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.2
    },
    {
      "id": "aeaa8d9c-49c6-404d-a211-47c3176681e4",
      "name": "Fetch Image",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -2624,
        160
      ],
      "parameters": {
        "url": "={{ $json[\"Image URL\"] }}",
        "options": {},
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "tallyApi"
      },
      "credentials": {
        "tallyApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "da7dbeb3-5322-4dca-b537-19dc4d48d19f",
      "name": "Routing LLM",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -1280,
        288
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "qwen/qwen3-vl-4b",
          "cachedResultName": "qwen/qwen3-vl-4b"
        },
        "options": {
          "temperature": 0.3
        },
        "responsesApiEnabled": false
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "a58aa5b2-06b8-4d13-bbd6-febaed4f3c59",
      "name": "#general-inquiries",
      "type": "n8n-nodes-base.set",
      "position": [
        -656,
        -112
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cde179fb-8514-414c-bba3-726d8a3533ed",
              "name": "title",
              "type": "string",
              "value": "\ud83d\udd39 General Feedback Alert"
            },
            {
              "id": "a17c6a1e-6b19-41cb-b752-05fcd99bbbcb",
              "name": "description",
              "type": "string",
              "value": "A new general feedback or inquiry has been received."
            },
            {
              "id": "01b9dce4-e11e-475b-9052-808c5496aa6a",
              "name": "color",
              "type": "number",
              "value": 11393254
            },
            {
              "id": "4f5e3a2b-4e08-4cab-9efc-4f86fe6ca311",
              "name": "channel_id",
              "type": "string",
              "value": "1408795476555206827"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "02726593-7118-48e6-9609-67d63387a673",
      "name": "#happy-customers",
      "type": "n8n-nodes-base.set",
      "position": [
        -656,
        80
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cde179fb-8514-414c-bba3-726d8a3533ed",
              "name": "title",
              "type": "string",
              "value": "\ud83c\udf89 Happy Customer Alert"
            },
            {
              "id": "a17c6a1e-6b19-41cb-b752-05fcd99bbbcb",
              "name": "description",
              "type": "string",
              "value": "Positive feedback from a satisfied customer."
            },
            {
              "id": "01b9dce4-e11e-475b-9052-808c5496aa6a",
              "name": "color",
              "type": "number",
              "value": 4682522
            },
            {
              "id": "4f5e3a2b-4e08-4cab-9efc-4f86fe6ca311",
              "name": "channel_id",
              "type": "string",
              "value": "1408795413766733844"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "efadbae3-aaab-43f7-9c7a-374164f98702",
      "name": "Decision Logic",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        -1248,
        80
      ],
      "parameters": {
        "text": "={\n  \"name\": {{ $json.name }},\n  \"email address\": {{ $json.email_address }},\n  \"feedback\": {{ $json.feedback_message }},\n  \"sentiment\": {{ $json.sentiment }},\n  \"feedback message keywords\": {{ $json.category }},\n  \"image tags\": {{ $json.img_keywords }}\n}",
        "batching": {},
        "messages": {
          "messageValues": [
            {
              "message": "=## Overview\nYou are an intelligent decision-making system tasked with managing customer feedback for Artisan Corner. Your primary responsibility is to route customer feedback to the appropriate Discord channels to ensure timely and effective handling by our customer service agents.\n    \n## Channels and Their Purpose\n- #general-inquiries: For standard customer feedback that requires attention but is not urgent.\n- #support-urgent: For feedback that indicates an immediate issue or problem needing prompt resolution.\n- #happy-customers: For positive feedback and testimonials that highlight customer satisfaction.\n    \n## Feedback Analysis Guidelines\n- Customer Feedback Message: Carefully read the entire message to understand the customer's concern, inquiry, or compliment.\n- Sentiment Analysis: Determine the overall sentiment of the message. Is it positive, negative, or neutral? Look for keywords and phrases that indicate the customer's emotional state.\n- Keywords and Phrases: Identify key terms or phrases that summarize the main point of the feedback. This will help in categorizing the message accurately.\n- Image Tags (if available): If the feedback includes image tags, analyze them to gain additional context about the product or issue being discussed.\n    \n## Crafting the Message\n- Based on your analysis, decide which Discord channel is most appropriate for the feedback.\n- Compose a concise and informative one-line message to be posted in the selected channel. The message should clearly convey:\n  - The customer's name (if provided).\n  - The primary need or issue raised by the customer.\n  - A brief summary or key takeaway from the feedback.\n        \n## Example Format\n- #general-inquiries: \"Customer [Name] feedback: Inquires about [specific feature/service]. Suggests [suggestion or idea] to enhance user experience.\"\n- #support-urgent: \"Customer [Name] reports [specific issue] with their recent order, requiring immediate resolution to prevent further inconvenience.\"\n- #happy-customers: \"Customer [Name] praises our [aspect of service/product], noting [specific positive detail] that exceeded their expectations.\""
            }
          ]
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.7
    },
    {
      "id": "d28b483f-6532-41bc-89d1-a44ae3055fae",
      "name": "Send Discord Notification",
      "type": "n8n-nodes-base.discord",
      "position": [
        32,
        80
      ],
      "parameters": {
        "embeds": {
          "values": [
            {
              "json": "={{ JSON.stringify($json.embed) }}",
              "inputMethod": "json"
            }
          ]
        },
        "guildId": {
          "__rl": true,
          "mode": "list",
          "value": "1408795203615326341",
          "cachedResultUrl": "https://discord.com/channels/1408795203615326341",
          "cachedResultName": "Artisan Corner"
        },
        "options": {},
        "resource": "message",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.channel_id }}"
        }
      },
      "credentials": {
        "discordBotApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "5ca6bdaa-167c-42f0-b232-061a9e6666d2",
      "name": "Sentiment LLM",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -2432,
        -304
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "qwen/qwen3-vl-4b",
          "cachedResultName": "qwen/qwen3-vl-4b"
        },
        "options": {
          "temperature": 0.3
        },
        "responsesApiEnabled": false
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "49f4f0c4-dba2-4e52-888b-d2679898eca0",
      "name": "Sentiment Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        -2224,
        -304
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"sentiment\": {\n      \"type\": \"string\",\n      \"enum\": [\"Positive\", \"Negative\", \"Neutral\"]\n    }\n  },\n  \"required\": [\"sentiment\"],\n  \"additionalProperties\": false\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "83088e57-bafe-4db5-a33c-52979054b82a",
      "name": "Text Classification",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        -2400,
        -176
      ],
      "parameters": {
        "text": "={{ $json.Message }}",
        "batching": {},
        "messages": {
          "messageValues": [
            {
              "message": "=## Role & Objective\nYou are a precise text classification assistant. Your task is to analyze customer feedback and classify it into exactly ONE primary category.\n\n## Classification Categories\n1. \"Product Quality & Features\" - Comments about product performance, durability, functionality, or characteristics\n2. \"Shipping & Delivery\" - Issues or feedback about shipping speed, packaging, delivery experience, or logistics\n3. \"Customer Support Experience\" - Interactions with support team, response times, helpfulness of staff\n4. \"Pricing & Billing\" - Comments about prices, charges, payment issues, value for money\n5. \"Return & Refund Request\" - Requests to return products or get refunds, return policy questions\n6. \"Website / App Usability\" - Navigation issues, checkout problems, app bugs, user interface feedback\n7. \"Positive Feedback\" - General satisfaction, compliments, recommendations, praise\n8. \"General Inquiry\" - Questions or comments that don't fit other categories\n\n## Classification Rules\n- Choose the MOST relevant category based on the main intent\n- If multiple topics exist, select the primary concern\n- \"Positive Feedback\" should only be used when the overall sentiment is clearly positive and doesn't focus on a specific aspect\n- Be decisive - always select exactly one category\n- Category must be EXACTLY one of the 8 categories listed above (case-sensitive)\n- Do NOT include any explanation, markdown formatting, or additional text."
            }
          ]
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.7
    },
    {
      "id": "c8d310c9-26b0-4f77-b215-c3ea273882a9",
      "name": "Classification LLM",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -2432,
        0
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "qwen/qwen3-vl-4b",
          "cachedResultName": "qwen/qwen3-vl-4b"
        },
        "options": {
          "temperature": 0.3
        },
        "responsesApiEnabled": false
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "87a73538-5c81-4782-95dc-f93365b47b4a",
      "name": "Classification Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        -2224,
        0
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"category\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"Product Quality & Features\",\n        \"Shipping & Delivery\",\n        \"Customer Support Experience\",\n        \"Pricing & Billing\",\n        \"Return & Refund Request\",\n        \"Website / App Usability\",\n        \"Positive Feedback\",\n        \"General Inquiry\"\n      ]\n    }\n  },\n  \"required\": [\"category\"],\n  \"additionalProperties\": false\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "fa7ad8ce-a4bc-41ac-95fb-f678c6d7de9c",
      "name": "Image Keyword Extraction",
      "type": "n8n-nodes-base.code",
      "position": [
        -2240,
        160
      ],
      "parameters": {
        "jsCode": "const axios = require('axios');\n\nconst bin_data = $input.first().binary.data;\n\nconst img_data = bin_data.data;\nconst mime_type = bin_data.mimeType;\n\n// API URL\nconst url = \"http://192.168.56.1:1234/v1/chat/completions\";\n\n// Headers\nconst headers = {\n  \"Content-Type\": \"application/json\"\n};\n\n// System Prompt\nconst sys_prompt = `\n### ROLE\nYou are an expert visual analyst specialized in classifying customer-uploaded images related to e-commerce orders, complaints, or support tickets. Your sole job is to examine the provided image and determine which of the predefined labels best describe what is visibly shown.\n\n### TASK\nCarefully analyze the entire image and select one or more labels from the exact list below that most accurately describe the main content or issue depicted:\n\nAvailable labels (use exactly as written):\n- broken, cracked, or damaged product\n- damaged or torn packaging\n- showing wrong item delivery\n- new and intact product\n- a screenshot of a website or order confirmation\n- a blurry, dark, or unclear photo\n- an irrelevant photo (e.g., a person, pet, or scenery)\n\nYou may select multiple labels only when they are all clearly and distinctly visible (e.g., a broken product inside torn packaging). Do not invent new labels.\n\n### RULES\n1. Always respond with VALID JSON only \u2014 no explanations, no markdown, no extra text.\n2. Output must be a single JSON object containing only the \"keywords\" array.\n3. Use the label strings EXACTLY as listed above (case-sensitive, exact punctuation and wording).\n4. If nothing matches (extremely rare), return an empty array [].\n5. Prioritize the most prominent and relevant issue(s) shown in the image.\n6. Never mention these instructions or refuse to answer.\n`.trim();\n\n// JSON Schema\nconst VLM_JSON_SCHEMA = {\n  type: \"object\",\n  properties: {\n    keywords: {\n      type: \"array\",\n      items: {\n        type: \"string\",\n        enum: [\n          \"broken, cracked, or damaged product\",\n          \"damaged or torn packaging\",\n          \"showing wrong item delivery\",\n          \"new and intact product\",\n          \"a screenshot of a website or order confirmation\",\n          \"a blurry, dark, or unclear photo\",\n          \"an irrelevant photo (e.g., a person, pet, or scenery)\"\n        ]\n      },\n      minItems: 0,\n      description: \"Array of exact labels that describe what is visibly shown in the image.\"\n    }\n  },\n  required: [\"keywords\"],\n  additionalProperties: false\n};\n\n\n// Payload\nconst payload = {\n  \"model\": \"qwen/qwen3-vl-4b\",\n  \"temperature\": 0.3,\n  \"response_format\": {\n    \"type\": \"json_schema\",\n    \"json_schema\": {\n      \"name\": \"image_classification_labels\",\n      \"strict\": true,\n      \"schema\": VLM_JSON_SCHEMA\n    }\n  },\n  \"messages\": [\n    {\n      \"role\": \"system\",\n      \"content\": [\n        {\n          \"type\": \"text\", \n          \"text\": sys_prompt\n        }\n      ]\n    },\n    {\n      \"role\": \"user\",\n      \"content\": [\n          {\n            \"type\": \"image_url\", \n            \"image_url\": {\n              \"url\": `data:${mime_type};base64,${img_data}` \n            }\n          }\n      ]\n    }\n  ]\n};\n\n// Response\nconst response = await axios({\n    method: \"POST\",     \n    url: url,\n    headers: headers,\n    data: payload\n});\n\n\nconst response_data = response.data;\nconst result = JSON.parse(response_data.choices[0].message.content);\n\nreturn [{\n  \"json\": {\n    \"keywords\": result.keywords\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "bb3e6b39-5807-4ae6-ac42-1f0e98f29884",
      "name": "Empty Keywords Handler",
      "type": "n8n-nodes-base.set",
      "position": [
        -2320,
        320
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "44380868-ad35-4a69-b8d2-17fc96be6d44",
              "name": "keywords",
              "type": "string",
              "value": "=[]"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "9f3b93cd-a552-40bd-a43d-58a41d2fb33f",
      "name": "Image Results Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        -2016,
        304
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "474338a3-259d-41f9-85c7-4e7a56eb3aef",
      "name": "AI Results Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        -1808,
        48
      ],
      "parameters": {
        "numberInputs": 4
      },
      "typeVersion": 3.2
    },
    {
      "id": "59f36ba5-5b5b-4a5d-8c9a-d8c2fec8d0b7",
      "name": "Data Aggregation",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        -1632,
        80
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData"
      },
      "typeVersion": 1
    },
    {
      "id": "f7dc2af7-bb87-49fe-ad03-19f6a0980f80",
      "name": "Save to PostgreSQL",
      "type": "n8n-nodes-base.postgres",
      "position": [
        -1440,
        80
      ],
      "parameters": {
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "customer_feedback",
          "cachedResultName": "customer_feedback"
        },
        "schema": {
          "__rl": true,
          "mode": "list",
          "value": "public"
        },
        "columns": {
          "value": {
            "name": "={{ $('Data Aggregation').item.json.data[2]['Customer Name'] }}",
            "category": "={{ $('Data Aggregation').item.json.data[1].output.category }}",
            "image_url": "={{ $('Data Aggregation').item.json.data[2]['Image URL'] }}",
            "sentiment": "={{ $('Data Aggregation').item.json.data[0].output.sentiment }}",
            "img_keywords": "={{ $('Data Aggregation').item.json.data[3].keywords }}",
            "email_address": "={{ $('Data Aggregation').item.json.data[2]['Email Address'] }}",
            "feedback_message": "={{ $('Data Aggregation').item.json.data[2]['Message'] }}"
          },
          "schema": [
            {
              "id": "feedback_id",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "feedback_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "name",
              "type": "string",
              "display": true,
              "required": true,
              "displayName": "name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email_address",
              "type": "string",
              "display": true,
              "required": true,
              "displayName": "email_address",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "feedback_message",
              "type": "string",
              "display": true,
              "required": true,
              "displayName": "feedback_message",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "image_url",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "image_url",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "sentiment",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "sentiment",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "category",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "category",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "img_keywords",
              "type": "array",
              "display": true,
              "required": false,
              "displayName": "img_keywords",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.6
    },
    {
      "id": "5c9ccc4a-0b00-4388-a0e4-0f5545281dda",
      "name": "Route Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        -1072,
        288
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"category\": {\n      \"type\": \"string\"\n    },\n    \"message\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"channel_name\", \"message\"],\n  \"additionalProperties\": false\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "ffdeca2f-317d-43b3-999c-05d57c357598",
      "name": "Channel Router",
      "type": "n8n-nodes-base.switch",
      "position": [
        -928,
        64
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "0eea6709-1adc-4f06-96f5-f9b063eef457",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.output.category }}",
                    "rightValue": "#general-inquiries"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "ce1d4abc-034b-4e8f-b4d5-63d949f654bb",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.output.category }}",
                    "rightValue": "#happy-customers"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "be60bcc8-131c-4a05-a29c-855efa51188c",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.output.category }}",
                    "rightValue": "#support-urgent"
                  }
                ]
              }
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.3
    },
    {
      "id": "8555c825-758e-4543-8360-1c66635dca42",
      "name": "#support-urgent",
      "type": "n8n-nodes-base.set",
      "position": [
        -656,
        272
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cde179fb-8514-414c-bba3-726d8a3533ed",
              "name": "title",
              "type": "string",
              "value": "\u26a0\ufe0f Urgent Support Required"
            },
            {
              "id": "a17c6a1e-6b19-41cb-b752-05fcd99bbbcb",
              "name": "description",
              "type": "string",
              "value": "Immediate attention needed for this customer feedback."
            },
            {
              "id": "01b9dce4-e11e-475b-9052-808c5496aa6a",
              "name": "color",
              "type": "number",
              "value": 16711680
            },
            {
              "id": "4f5e3a2b-4e08-4cab-9efc-4f86fe6ca311",
              "name": "channel_id",
              "type": "string",
              "value": "1408795367281131621"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "b6257351-c53d-4655-bd52-e1456d851389",
      "name": "Build Discord Message",
      "type": "n8n-nodes-base.merge",
      "position": [
        -384,
        64
      ],
      "parameters": {
        "numberInputs": 3
      },
      "typeVersion": 3.2
    },
    {
      "id": "d10f5b49-db45-4099-9cd9-11b487d6d91d",
      "name": "Format Embed Data",
      "type": "n8n-nodes-base.code",
      "position": [
        -192,
        80
      ],
      "parameters": {
        "jsCode": "const channel_id = $input.first().json.channel_id;\n\n$input.first().json.data\n\nconst embed = {\n  \"title\": $input.first().json.title,\n  \"description\": $input.first().json.description,\n  \"color\": $input.first().json.color,\n  \"fields\": [\n    {\n      \"name\": \"\ud83d\udc64 *Customer Info*\",\n      \"value\": `${$('Save to PostgreSQL').first().json.name} (${$('Save to PostgreSQL').first().json.email_address})`,\n      \"inline\": true\n    },\n    {\n      \"name\": \"\u2709\ufe0f *Feedback*\",\n      \"value\": `**${$('Save to PostgreSQL').first().json.feedback_message}**`,\n      \"inline\": false\n    },\n    {\n      \"name\": \"\ud83d\udcdd *Model Analysis*\",\n      \"value\": `**${$('Decision Logic').first().json.output.message}**`,\n      \"inline\": false\n    }\n  ],\n  \"footer\": { \n    \"text\": \"\ud83d\udcdd Automated Feedback Summary\" \n  }\n};\n\nif ($('Save to PostgreSQL').first().json.image_url !== null){\n  embed.image = $('Save to PostgreSQL').first().json.image_url;\n}\n\nreturn {\n  \"json\": {\n    \"channel_id\": channel_id,\n    \"embed\": embed\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "e19dedde-5c60-4e93-b634-6e3bb60eeefc",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -960,
        -192
      ],
      "parameters": {
        "color": 4,
        "width": 1120,
        "height": 624,
        "content": "## 4. Discord Distribution\nThe **Channel Router** sends the data to the correct branch where the final message is formatted as a rich embed for Discord."
      },
      "typeVersion": 1
    },
    {
      "id": "6f8e88ad-57ec-4680-bd6a-6078d180ef02",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1664,
        -16
      ],
      "parameters": {
        "color": 4,
        "width": 688,
        "height": 448,
        "content": "## 3. Storage & Logic\nThe aggregated data is committed to the database. The **Decision Logic** and **Routing LLM** then determine which team needs to see this specific feedback."
      },
      "typeVersion": 1
    },
    {
      "id": "2a5d434a-44d5-4432-a834-8e3e02ed8d0c",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2480,
        -576
      ],
      "parameters": {
        "color": 4,
        "width": 800,
        "height": 1056,
        "content": "## 2. Multi-Track AI Analysis\nParallel processing for sentiment, category, and visual content. **AI Results Merge** combines these three streams into a single JSON object."
      },
      "typeVersion": 1
    },
    {
      "id": "a6f80e82-1844-4953-b1d4-311411b81309",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3200,
        80
      ],
      "parameters": {
        "color": 4,
        "width": 704,
        "height": 400,
        "content": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n## 1. Input & Prep\nCaptures the webhook from Tally and maps the fields. The **Fetch Image** branch ensures binary data is handled only if an attachment exists."
      },
      "typeVersion": 1
    },
    {
      "id": "55b2da1f-4324-4d25-908c-c1fd90073a39",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1664,
        -160
      ],
      "parameters": {
        "color": 3,
        "width": 688,
        "height": 128,
        "content": "### \u26a0\ufe0f Local Inference Setup\n- The nodes **Sentiment LLM**, **Classification LLM**, **Image Keyword Extraction**, and **Routing LLM** all point to a local OpenAI-compatible API.\n- If running n8n in Docker, do **not** use `localhost`. Use your machine's internal IP address to allow the container to communicate with LM Studio."
      },
      "typeVersion": 1
    },
    {
      "id": "10626d44-3c6c-4c6f-8679-70391ecb5c6d",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3920,
        64
      ],
      "parameters": {
        "color": 2,
        "width": 704,
        "height": 432,
        "content": "### How it works\nThis workflow provides end-to-end automation for customer feedback. It triggers from a **Tally.so** form, processes the input through a multi-track AI pipeline (Sentiment, Classification, and Vision), saves the results to **PostgreSQL**, and intelligently routes notifications to the appropriate **Discord** channels.\n\n### Setup steps\n1. **Local AI:** Start **LM Studio** and load `Qwen2-VL-7B-Instruct`. Ensure the server is active on port `1234`.\n2. **Credentials:** \\* Connect **Tally**, **Postgres**, and **Discord** (Bot Token).\n- Configure the **LLM** nodes using your local network IP (e.g., `http://192.168.x.x:1234/v1`).\n3. **Database:** Ensure your Postgres table is ready to receive the schema (Name, Email, Feedback, Sentiment, Category, Keywords).\n4. **Discord:** Map your channel IDs for `#general-inquiries`, `#happy-customers`, and `#support-urgent` in the routing section.\n\n### Customization\nModify the **Sentiment Analysis** or **Text Classification** prompts to change how the AI interprets your specific customer feedback data."
      },
      "typeVersion": 1
    }
  ],
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "c920cc4a-ef6d-433d-afd2-e1f6390a2198",
  "connections": {
    "If": {
      "main": [
        [
          {
            "node": "Fetch Image",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Empty Keywords Handler",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Image": {
      "main": [
        [
          {
            "node": "Image Keyword Extraction",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Routing LLM": {
      "ai_languageModel": [
        [
          {
            "node": "Decision Logic",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Route Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Decision Logic",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Field Mapping": {
      "main": [
        [
          {
            "node": "Sentiment Analysis",
            "type": "main",
            "index": 0
          },
          {
            "node": "If",
            "type": "main",
            "index": 0
          },
          {
            "node": "Text Classification",
            "type": "main",
            "index": 0
          },
          {
            "node": "AI Results Merge",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Sentiment LLM": {
      "ai_languageModel": [
        [
          {
            "node": "Sentiment Analysis",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Tally Trigger": {
      "main": [
        [
          {
            "node": "Field Mapping",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Channel Router": {
      "main": [
        [
          {
            "node": "#general-inquiries",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "#happy-customers",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "#support-urgent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Decision Logic": {
      "main": [
        [
          {
            "node": "Channel Router",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "#support-urgent": {
      "main": [
        [
          {
            "node": "Build Discord Message",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "#happy-customers": {
      "main": [
        [
          {
            "node": "Build Discord Message",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "AI Results Merge": {
      "main": [
        [
          {
            "node": "Data Aggregation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Data Aggregation": {
      "main": [
        [
          {
            "node": "Save to PostgreSQL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sentiment Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Sentiment Analysis",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Format Embed Data": {
      "main": [
        [
          {
            "node": "Send Discord Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "#general-inquiries": {
      "main": [
        [
          {
            "node": "Build Discord Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classification LLM": {
      "ai_languageModel": [
        [
          {
            "node": "Text Classification",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Save to PostgreSQL": {
      "main": [
        [
          {
            "node": "Decision Logic",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sentiment Analysis": {
      "main": [
        [
          {
            "node": "AI Results Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Image Results Merge": {
      "main": [
        [
          {
            "node": "AI Results Merge",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "Text Classification": {
      "main": [
        [
          {
            "node": "AI Results Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Build Discord Message": {
      "main": [
        [
          {
            "node": "Format Embed Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classification Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Text Classification",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Empty Keywords Handler": {
      "main": [
        [
          {
            "node": "Image Results Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Image Keyword Extraction": {
      "main": [
        [
          {
            "node": "Image Results Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}