From c5b607a253790f93785b689702976c25a5d11a42 Mon Sep 17 00:00:00 2001 From: denshooter Date: Fri, 9 Jan 2026 18:10:51 +0100 Subject: [PATCH] fix: Improve n8n chat response parsing - Add comprehensive parsing for various n8n response formats - Check multiple field names (reply, message, response, text, content, answer, output, result) - Handle array responses and nested structures (data, json, items) - Add recursive search for string values in complex objects - Improve logging to show full n8n response structure - Only use fallback if truly no response found --- app/api/n8n/chat/route.ts | 123 ++++++++++++++++++++++++++++++-------- 1 file changed, 98 insertions(+), 25 deletions(-) diff --git a/app/api/n8n/chat/route.ts b/app/api/n8n/chat/route.ts index abc1d36..0ebb56e 100644 --- a/app/api/n8n/chat/route.ts +++ b/app/api/n8n/chat/route.ts @@ -73,35 +73,108 @@ export async function POST(request: NextRequest) { const data = await response.json(); - console.log("n8n response data:", data); + console.log("n8n response data (full):", JSON.stringify(data, null, 2)); + console.log("n8n response data type:", typeof data); + console.log("n8n response is array:", Array.isArray(data)); - const reply = - data.reply || - data.message || - data.response || - data.text || - data.content || - (Array.isArray(data) && data[0]?.reply); + // Try multiple ways to extract the reply + let reply: string | undefined = undefined; - if (!reply) { - console.warn("n8n response missing reply field:", data); - // If n8n returns successfully but without a clear reply field, - // we might want to show the fallback or a generic error, - // but strictly speaking we shouldn't show "Couldn't process". - // Let's try to stringify the whole data if it's small, or use fallback. - if (data && typeof data === "object" && Object.keys(data).length > 0) { - // It returned something, but we don't know what field to use. - // Check for common n8n structure - if (data.output) { - const decoded = decodeHtmlEntitiesServer(String(data.output)); - return NextResponse.json({ reply: decoded }); - } - if (data.data) { - const decoded = decodeHtmlEntitiesServer(String(data.data)); - return NextResponse.json({ reply: decoded }); + // Direct fields + if (data.reply) reply = data.reply; + else if (data.message) reply = data.message; + else if (data.response) reply = data.response; + else if (data.text) reply = data.text; + else if (data.content) reply = data.content; + else if (data.answer) reply = data.answer; + else if (data.output) reply = data.output; + else if (data.result) reply = data.result; + + // Array handling + else if (Array.isArray(data) && data.length > 0) { + const firstItem = data[0]; + if (typeof firstItem === 'string') { + reply = firstItem; + } else if (typeof firstItem === 'object') { + reply = firstItem.reply || firstItem.message || firstItem.response || + firstItem.text || firstItem.content || firstItem.answer || + firstItem.output || firstItem.result; + } + } + + // Nested structures (common in n8n) + else if (data && typeof data === "object") { + // Check nested data field + if (data.data) { + if (typeof data.data === 'string') { + reply = data.data; + } else if (typeof data.data === 'object') { + reply = data.data.reply || data.data.message || data.data.response || + data.data.text || data.data.content || data.data.answer; } } - throw new Error("Invalid response format from n8n"); + + // Check nested json field + if (!reply && data.json) { + if (typeof data.json === 'string') { + reply = data.json; + } else if (typeof data.json === 'object') { + reply = data.json.reply || data.json.message || data.json.response || + data.json.text || data.json.content || data.json.answer; + } + } + + // Check items array (n8n often wraps in items) + if (!reply && Array.isArray(data.items) && data.items.length > 0) { + const firstItem = data.items[0]; + if (typeof firstItem === 'string') { + reply = firstItem; + } else if (typeof firstItem === 'object') { + reply = firstItem.reply || firstItem.message || firstItem.response || + firstItem.text || firstItem.content || firstItem.answer || + firstItem.json?.reply || firstItem.json?.message; + } + } + + // Last resort: if it's a single string value object, try to extract + if (!reply && Object.keys(data).length === 1) { + const value = Object.values(data)[0]; + if (typeof value === 'string') { + reply = value; + } + } + + // If still no reply but data exists, stringify it (for debugging) + if (!reply && Object.keys(data).length > 0) { + console.warn("n8n response structure not recognized, attempting to extract any string value"); + // Try to find any string value in the object + const findStringValue = (obj: unknown): string | undefined => { + if (typeof obj === 'string' && obj.length > 0) return obj; + if (Array.isArray(obj) && obj.length > 0) { + return findStringValue(obj[0]); + } + if (obj && typeof obj === 'object' && obj !== null) { + const objRecord = obj as Record; + for (const key of ['reply', 'message', 'response', 'text', 'content', 'answer', 'output', 'result']) { + if (objRecord[key] && typeof objRecord[key] === 'string') { + return objRecord[key] as string; + } + } + // Recursively search + for (const value of Object.values(objRecord)) { + const found = findStringValue(value); + if (found) return found; + } + } + return undefined; + }; + reply = findStringValue(data); + } + } + + if (!reply) { + console.error("n8n response missing reply field. Full response:", JSON.stringify(data, null, 2)); + throw new Error("Invalid response format from n8n - no reply field found"); } // Decode HTML entities in the reply