{ "nodes": [ { "id": "4ea39a4f-d8c1-438f-9738-bfbb906a3d7a", "name": "Sticky Note2", "type": "n8n-nodes-base.stickyNote", "position": [ 1200, 1020 ], "parameters": { "width": 253, "height": 342, "content": "## Customer feedback\nConnect OpenAI and Google Sheets. Configure YOUR_SPREADSHEET_ID, use the Feedback tab and headers documented in README. The form acknowledges receipt; a successful run appends the feedback and sentiment." }, "typeVersion": 1 }, { "id": "6962ea41-7d15-4932-919f-21ac94fa1269", "name": "Sticky Note3", "type": "n8n-nodes-base.stickyNote", "position": [ 1960, 1180 ], "parameters": { "width": 253, "height": 342, "content": "## Customer feedback\nConnect OpenAI and Google Sheets. Configure YOUR_SPREADSHEET_ID, use the Feedback tab and headers documented in README. The form acknowledges receipt; a successful run appends the feedback and sentiment." }, "typeVersion": 1 }, { "id": "4c8a8984-2d8e-4139-866b-6f3536aced07", "name": "Sticky Note4", "type": "n8n-nodes-base.stickyNote", "position": [ 800, 1600 ], "parameters": { "width": 1407, "height": 254, "content": "## Customer feedback\nConnect OpenAI and Google Sheets. Configure YOUR_SPREADSHEET_ID, use the Feedback tab and headers documented in README. The form acknowledges receipt; a successful run appends the feedback and sentiment." }, "typeVersion": 1 }, { "id": "d43a9574-626d-4817-87ba-d99bdd6f41dc", "name": "Sticky Note5", "type": "n8n-nodes-base.stickyNote", "position": [ 800, 1160 ], "parameters": { "width": 253, "height": 342, "content": "## Customer feedback\nConnect OpenAI and Google Sheets. Configure YOUR_SPREADSHEET_ID, use the Feedback tab and headers documented in README. The form acknowledges receipt; a successful run appends the feedback and sentiment." }, "typeVersion": 1 }, { "id": "76dab2dc-935f-416e-91aa-5a1b7017ec1b", "name": "Sticky Note6", "type": "n8n-nodes-base.stickyNote", "position": [ 1600, 1180 ], "parameters": { "width": 253, "height": 342, "content": "## Customer feedback\nConnect OpenAI and Google Sheets. Configure YOUR_SPREADSHEET_ID, use the Feedback tab and headers documented in README. The form acknowledges receipt; a successful run appends the feedback and sentiment." }, "typeVersion": 1 }, { "id": "9772eac1-8df2-4305-9b2c-265d3c5a9a4a", "name": "Add customer feedback to Google Sheets", "type": "n8n-nodes-base.googleSheets", "position": [ 2020, 1320 ], "parameters": { "operation": "append", "documentId": { "__rl": true, "mode": "id", "value": "YOUR_SPREADSHEET_ID" }, "sheetName": { "__rl": true, "mode": "name", "value": "Feedback" }, "columns": { "mappingMode": "defineBelow", "value": { "Timestamp": "={{ $json.values[0][0] }}", "Category": "={{ $json.values[0][1] }}", "Customer Feedback": "={{ $json.values[0][2] }}", "Customer Name": "={{ $json.values[0][3] }}", "Customer contact": "={{ $json.values[0][4] }}", "Entered by": "={{ $json.values[0][5] }}", "Urgent?": "={{ $json.values[0][6] }}", "Sentiment": "={{ $json.values[0][7] }}" }, "matchingColumns": [], "schema": [ { "id": "Timestamp", "displayName": "Timestamp", "type": "string", "display": true, "required": false, "defaultMatch": false, "canBeUsedToMatch": true }, { "id": "Category", "displayName": "Category", "type": "string", "display": true, "required": false, "defaultMatch": false, "canBeUsedToMatch": true }, { "id": "Customer Feedback", "displayName": "Customer Feedback", "type": "string", "display": true, "required": false, "defaultMatch": false, "canBeUsedToMatch": true }, { "id": "Customer Name", "displayName": "Customer Name", "type": "string", "display": true, "required": false, "defaultMatch": false, "canBeUsedToMatch": true }, { "id": "Customer contact", "displayName": "Customer contact", "type": "string", "display": true, "required": false, "defaultMatch": false, "canBeUsedToMatch": true }, { "id": "Entered by", "displayName": "Entered by", "type": "string", "display": true, "required": false, "defaultMatch": false, "canBeUsedToMatch": true }, { "id": "Urgent?", "displayName": "Urgent?", "type": "string", "display": true, "required": false, "defaultMatch": false, "canBeUsedToMatch": true }, { "id": "Sentiment", "displayName": "Sentiment", "type": "string", "display": true, "required": false, "defaultMatch": false, "canBeUsedToMatch": true } ] }, "options": { "cellFormat": "RAW", "useAppend": true } }, "typeVersion": 4.1 }, { "id": "12084971-c81b-4a0e-814e-120867562642", "name": "Merge sentiment with form content", "type": "n8n-nodes-base.code", "position": [ 1680, 1320 ], "parameters": { "mode": "runOnceForAllItems", "jsCode": "const row = function feedbackRow({ name, category, feedback, contact, timestamp, sentiment }) {\n if (typeof sentiment !== 'string' || !sentiment.trim()) throw new Error('The sentiment model returned an empty response.');\n return { values: [[timestamp, category, feedback, name, contact, 'Form', '', sentiment]] };\n}\n\nreturn [{json:row({...$('Validate Feedback').first().json,sentiment:$input.first().json.message.content})}];\n" }, "typeVersion": 2 }, { "id": "235edf5b-7724-4712-8dc5-d8327a0620b8", "name": "Classify feedback with OpenAI", "type": "@n8n/n8n-nodes-langchain.openAi", "position": [ 1280, 1180 ], "parameters": { "modelId": { "__rl": true, "mode": "list", "value": "gpt-4o-mini" }, "messages": { "values": [ { "role": "user", "content": "=Classify the sentiment in the following customer feedback: {{ $json.feedback }}" } ] }, "options": {} }, "typeVersion": 1.8 }, { "id": "af4b22aa-0925-40b1-a9ac-298f9745a98e", "name": "Submit form with customer feedback", "type": "n8n-nodes-base.formTrigger", "position": [ 860, 1340 ], "parameters": { "options": { "formSubmittedText": "Feedback was received." }, "formTitle": "Customer Feedback", "formFields": { "values": [ { "fieldLabel": "Name", "requiredField": true }, { "fieldType": "dropdown", "fieldLabel": "What is your feedback about?", "fieldOptions": { "values": [ { "option": "Product" }, { "option": "Service" }, { "option": "Other" } ] }, "requiredField": true }, { "fieldType": "textarea", "fieldLabel": "Your feedback", "requiredField": true }, { "fieldLabel": "How do we get in touch with you?" } ] }, "formDescription": "Please give feedback about our company orproducts." }, "typeVersion": 1 }, { "id": "prepare-feedback", "name": "Validate Feedback", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1060, 1320 ], "parameters": { "mode": "runOnceForAllItems", "jsCode": "const prepare = function prepareFeedback({ body }) {\n if (!body || typeof body !== 'object' || Array.isArray(body)) throw new Error('Feedback must be a JSON object.');\n const required = ['Name', 'What is your feedback about?', 'Your feedback'];\n for (const name of required) if (typeof body[name] !== 'string' || !body[name].trim()) throw new Error('Required feedback field: ' + name);\n const category = body['What is your feedback about?'];\n if (!['Product', 'Service', 'Other'].includes(category)) throw new Error('Feedback category must be Product, Service, or Other.');\n const contact = body['How do we get in touch with you?'];\n if (contact != null && typeof contact !== 'string') throw new Error('Contact must be text.');\n const timestamp = body.submittedAt || new Date().toISOString();\n if (typeof timestamp !== 'string' || !Number.isFinite(Date.parse(timestamp))) throw new Error('Submission timestamp must be a valid date.');\n return { name: body.Name, category, feedback: body['Your feedback'], contact: contact || '', timestamp };\n}\n\nreturn $input.all().map((item,index)=>({json:prepare({body:item.json}),pairedItem:{item:index}}));\n" } } ], "connections": { "Classify feedback with OpenAI": { "main": [ [ { "node": "Merge sentiment with form content", "type": "main", "index": 0 } ] ] }, "Merge sentiment with form content": { "main": [ [ { "node": "Add customer feedback to Google Sheets", "type": "main", "index": 0 } ] ] }, "Submit form with customer feedback": { "main": [ [ { "node": "Validate Feedback", "type": "main", "index": 0 } ] ] }, "Validate Feedback": { "main": [ [ { "node": "Classify feedback with OpenAI", "type": "main", "index": 0 } ] ] } }, "active": false, "pinData": {} }