diff --git a/n8n-workflow.json b/n8n-workflow.json index b7d6163..6ce548c 100644 --- a/n8n-workflow.json +++ b/n8n-workflow.json @@ -2066,6 +2066,19 @@ 600 ] }, + { + "parameters": { + "jsCode": "// Build Claude API request body\nconst userMessage = $json.message?.text || '';\n\nconst systemPrompt = `You are a Docker container management assistant. Parse user requests and return ONLY valid JSON.\n\nValid actions:\n- view_logs: User wants to see container logs\n- query_stats: User asks about resource usage (memory, CPU)\n- container_action: User wants to start/stop/restart/update a container\n- container_status: User asks about container status\n- list_containers: User wants to see all containers\n- unknown: Cannot determine intent\n\nRespond with JSON: {\"action\": \"\", \"container\": \"\", \"parameters\": {}}\n\nExamples:\n- \"show me plex logs\" -> {\"action\": \"view_logs\", \"container\": \"plex\", \"parameters\": {\"lines\": 50}}\n- \"what's using the most memory?\" -> {\"action\": \"query_stats\", \"container\": null, \"parameters\": {\"metric\": \"memory\", \"sort\": \"desc\"}}\n- \"restart nginx\" -> {\"action\": \"container_action\", \"container\": \"nginx\", \"parameters\": {\"action\": \"restart\"}}\n- \"how's sonarr doing?\" -> {\"action\": \"container_status\", \"container\": \"sonarr\", \"parameters\": {}}\n- \"hello\" -> {\"action\": \"unknown\", \"container\": null, \"parameters\": {\"message\": \"I can help with Docker containers. Try: 'show logs', 'restart plex', or 'what's using memory?'\"}}`;\n\nconst body = {\n model: 'claude-sonnet-4-5-20250929',\n max_tokens: 256,\n system: [{\n type: 'text',\n text: systemPrompt,\n cache_control: { type: 'ephemeral' }\n }],\n messages: [{\n role: 'user',\n content: userMessage\n }]\n};\n\nreturn {\n claudeBody: JSON.stringify(body),\n message: $json.message\n};" + }, + "id": "code-prepare-claude-body", + "name": "Prepare Claude Request", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 900, + 300 + ] + }, { "parameters": { "method": "POST", @@ -2082,8 +2095,8 @@ ] }, "sendBody": true, - "contentType": "json", - "jsonBody": "={{ JSON.stringify({\n \"model\": \"claude-sonnet-4-5-20250929\",\n \"max_tokens\": 256,\n \"system\": [{\n \"type\": \"text\",\n \"text\": \"You are a Docker container management assistant. Parse user requests and return ONLY valid JSON.\\n\\nValid actions:\\n- view_logs: User wants to see container logs\\n- query_stats: User asks about resource usage (memory, CPU)\\n- container_action: User wants to start/stop/restart/update a container\\n- container_status: User asks about container status\\n- list_containers: User wants to see all containers\\n- unknown: Cannot determine intent\\n\\nRespond with JSON: {\\\"action\\\": \\\"\\\", \\\"container\\\": \\\"\\\", \\\"parameters\\\": {}}\\n\\nExamples:\\n- \\\"show me plex logs\\\" -> {\\\"action\\\": \\\"view_logs\\\", \\\"container\\\": \\\"plex\\\", \\\"parameters\\\": {\\\"lines\\\": 50}}\\n- \\\"what's using the most memory?\\\" -> {\\\"action\\\": \\\"query_stats\\\", \\\"container\\\": null, \\\"parameters\\\": {\\\"metric\\\": \\\"memory\\\", \\\"sort\\\": \\\"desc\\\"}}\\n- \\\"restart nginx\\\" -> {\\\"action\\\": \\\"container_action\\\", \\\"container\\\": \\\"nginx\\\", \\\"parameters\\\": {\\\"action\\\": \\\"restart\\\"}}\\n- \\\"how's sonarr doing?\\\" -> {\\\"action\\\": \\\"container_status\\\", \\\"container\\\": \\\"sonarr\\\", \\\"parameters\\\": {}}\\n- \\\"hello\\\" -> {\\\"action\\\": \\\"unknown\\\", \\\"container\\\": null, \\\"parameters\\\": {\\\"message\\\": \\\"I can help with Docker containers. Try: 'show logs', 'restart plex', or 'what's using memory?'\\\"}}\",\n \"cache_control\": {\"type\": \"ephemeral\"}\n }],\n \"messages\": [{\n \"role\": \"user\",\n \"content\": $json.message.text\n }]\n}) }}", + "specifyBody": "json", + "jsonBody": "={{ $json.claudeBody }}", "options": { "timeout": 30000, "retry": { @@ -2097,7 +2110,7 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ - 900, + 1010, 300 ], "credentials": { @@ -2109,7 +2122,7 @@ }, { "parameters": { - "jsCode": "// Parse and validate Claude's intent response\nconst response = $input.item.json;\n\n// Claude response structure: { content: [{ type: \"text\", text: \"...\" }] }\nlet intentText = '';\ntry {\n intentText = response.content[0].text;\n} catch (e) {\n return {\n action: 'error',\n error: 'Invalid Claude response structure',\n raw: JSON.stringify(response)\n };\n}\n\n// Parse JSON from Claude's response\nlet intent;\ntry {\n // Claude might wrap JSON in markdown code blocks, strip them\n const cleaned = intentText.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n intent = JSON.parse(cleaned);\n} catch (e) {\n return {\n action: 'error',\n error: 'Could not parse intent JSON',\n raw: intentText\n };\n}\n\n// Validate required fields\nconst validActions = ['view_logs', 'query_stats', 'container_action', 'container_status', 'list_containers', 'unknown'];\nif (!intent.action || !validActions.includes(intent.action)) {\n return {\n action: 'unknown',\n error: 'Invalid or missing action',\n parameters: { message: 'I didn\\'t understand that. Try: \"show logs plex\" or \"restart nginx\"' }\n };\n}\n\n// Normalize container name if present\nif (intent.container) {\n intent.container = intent.container.toLowerCase().trim();\n}\n\n// Set defaults for parameters\nintent.parameters = intent.parameters || {};\n\n// Preserve original message for fallback\nintent.original_message = $input.item.json.message || {};\n\nreturn intent;" + "jsCode": "// Parse and validate Claude's intent response\nconst response = $input.item.json;\n\n// Claude response structure: { content: [{ type: \"text\", text: \"...\" }] }\nlet intentText = '';\ntry {\n intentText = response.content[0].text;\n} catch (e) {\n return {\n action: 'error',\n error: 'Invalid Claude response structure',\n raw: JSON.stringify(response)\n };\n}\n\n// Parse JSON from Claude's response\nlet intent;\ntry {\n // Claude might wrap JSON in markdown code blocks, strip them\n const cleaned = intentText.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n intent = JSON.parse(cleaned);\n} catch (e) {\n return {\n action: 'error',\n error: 'Could not parse intent JSON',\n raw: intentText\n };\n}\n\n// Validate required fields\nconst validActions = ['view_logs', 'query_stats', 'container_action', 'container_status', 'list_containers', 'unknown'];\nif (!intent.action || !validActions.includes(intent.action)) {\n return {\n action: 'unknown',\n error: 'Invalid or missing action',\n parameters: { message: 'I didn\\'t understand that. Try: \"show logs plex\" or \"restart nginx\"' }\n };\n}\n\n// Normalize container name if present\nif (intent.container) {\n intent.container = intent.container.toLowerCase().trim();\n}\n\n// Set defaults for parameters\nintent.parameters = intent.parameters || {};\n\n// Preserve original message for fallback - get from Prepare Claude Request node\nintent.original_message = $('Prepare Claude Request').item.json.message || {};\n\nreturn intent;" }, "id": "code-parse-intent", "name": "Parse Intent", @@ -2411,7 +2424,7 @@ "main": [ [ { - "node": "Claude Intent Parser", + "node": "Prepare Claude Request", "type": "main", "index": 0 } @@ -2419,6 +2432,17 @@ [] ] }, + "Prepare Claude Request": { + "main": [ + [ + { + "node": "Claude Intent Parser", + "type": "main", + "index": 0 + } + ] + ] + }, "IF Callback Authenticated": { "main": [ [