Mission Refactor Big

This commit is contained in:
alma 2026-01-09 12:50:54 +01:00
parent 9fae87e0ab
commit ad3faa194b
2 changed files with 166 additions and 16 deletions

View File

@ -0,0 +1,139 @@
{
"name": "NeahMissionGeneratePlan",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "GeneratePlan",
"responseMode": "lastNode",
"responseData": "allEntries",
"options": {}
},
"name": "Webhook GeneratePlan",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
-1040,
-32
],
"id": "28206383-afc0-472a-81f2-c99dc1e14f24",
"webhookId": "633b32e3-07c3-4e82-8e27-9ea4d6ec28e9"
},
{
"parameters": {
"jsCode": "// Build Project Action Plan Prompt (Senior Project Manager)\n\n// 1. Read input safely\nconst query = $input.item.json.query || \"\";\nconst mission = $input.item.json.mission;\nconst model = $input.item.json.model || \"qwen3:8b\";\n\n// 2. Handle case: no mission provided\nif (!mission) {\n return {\n json: {\n response_prompt: `No mission details were provided.\nPolitely ask the user to supply the mission context required to build a project action plan.`,\n num_predict: 300,\n model: model,\n query: query\n }\n };\n}\n\n// 3. Normalize mission fields (safe defaults)\nconst {\n name = \"Unnamed Mission\",\n oddScope = [],\n niveau = \"B\",\n intention = \"\",\n missionType = \"\",\n donneurDOrdre = \"\",\n projection = \"\",\n services = [],\n profils = []\n} = mission;\n\n// 4. Construct the Senior Project Manager Prompt\nconst prompt = `\nYou are a Senior Project Manager with extensive experience leading large-scale, complex and cross-functional projects for organizations, NGOs and startups.\n\nMISSION CONTEXT:\n- Mission name: ${name}\n- Mission scope (UN SDGs): ${oddScope.length ? oddScope.join(\", \") : \"Not specified\"}\n- Mission complexity level: ${niveau}\n- Mission intention: ${intention}\n- Mission type: ${missionType}\n- Ordering organization: ${donneurDOrdre}\n- Time projection: ${projection}\n- Services involved: ${services.length ? services.join(\", \") : \"None specified\"}\n- Required profiles: ${profils.length ? profils.join(\", \") : \"Not specified\"}\n\nTASK:\nProduce a clear, structured, and actionable ACTION PLAN as a senior project manager would do at the start of a major project.\n\nCRITICAL INSTRUCTIONS:\n- Respond ONLY in English.\n- Write as a senior project manager, not as a consultant or academic.\n- Focus on execution, structure, governance, and delivery.\n- Do NOT restate the mission description.\n- Do NOT use motivational or generic language.\n- Assume a complex, long-term mission with multiple stakeholders.\n- Be concise, concrete, and pragmatic.\n\nSTRUCTURE YOUR RESPONSE USING THE FOLLOWING SECTIONS:\n\n1. Mission Framing & Strategic Intent \nClarify the real objective of the mission, key constraints, and strategic priorities.\n\n2. Success Criteria & KPIs \nDefine how success will be measured (operational, impact, and sustainability metrics).\n\n3. Execution Roadmap \nBreak the mission into clear phases (e.g. initiation, build, deployment, scaling) with concrete outcomes per phase.\n\n4. Team Structure & Governance \nExplain how the different profiles will collaborate, decision-making model, and coordination mechanisms.\n\n5. Key Risks & Mitigation Plan \nIdentify major delivery, technical, organizational, and stakeholder risks and how to mitigate them.\n\n6. Delivery & Impact Measurement \nExplain how results will be delivered, validated, and aligned with the relevant UN SDGs over time.\n\nWrite in a professional, structured format, using short paragraphs or bullet points where relevant.\n`;\n\n// 5. Return LLM payload\nreturn {\n json: {\n response_prompt: prompt,\n num_predict: 2500,\n model: model,\n query: query\n }\n};\n"
},
"name": "Process Prompt for Ollama",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-736,
-32
],
"id": "6f0cdeb3-b5b5-4d2c-b13c-468eb92f0a52"
},
{
"parameters": {
"method": "POST",
"url": "http://172.16.0.117:11434/api/generate",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ {\n \"model\": $json.model,\n \"prompt\": $json.response_prompt,\n \"stream\": false,\n \"options\": {\n \"temperature\": 0.3,\n \"top_p\": 0.9,\n \"num_predict\": $json.num_predict\n }\n} }}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
-528,
-32
],
"id": "5918cf24-0473-44f7-9d36-c05d9b73039b",
"name": "HTTP Request"
},
{
"parameters": {
"jsCode": "// Clean Theme Response\nconst response = $input.item.json.response || $input.item.json.body?.response || '';\nlet cleanedResponse = response;\nif (cleanedResponse.includes('<think>')) {\n cleanedResponse = cleanedResponse.split('</think>')[1] || cleanedResponse;\n}\nreturn { json: { response: cleanedResponse.trim(), query: $input.item.json.query } };"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-320,
-32
],
"id": "5b029d07-b152-49b8-a269-8331e57b898f",
"name": "Clean Response"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "{{ { \"response\": $json.response } }}",
"options": {}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.4,
"position": [
-112,
-32
],
"id": "38099abd-8ac8-42e6-a400-dbbffbf14f04",
"name": "Respond to Webhook"
}
],
"pinData": {},
"connections": {
"Webhook GeneratePlan": {
"main": [
[
{
"node": "Process Prompt for Ollama",
"type": "main",
"index": 0
}
]
]
},
"Process Prompt for Ollama": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request": {
"main": [
[
{
"node": "Clean Response",
"type": "main",
"index": 0
}
]
]
},
"Clean Response": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {
"executionOrder": "v1"
},
"versionId": "6ce946c6-c278-43d0-acfe-1fe814c4f963",
"meta": {
"instanceId": "21947434c58170635d41cc9137ebeab13a628beaa4cf8318a6d7c90f9b354219"
},
"id": "k34Oeva3jxsmDg9M",
"tags": []
}

View File

@ -45,23 +45,26 @@ export async function POST(
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// Prepare data for N8N webhook
// Prepare data for N8N webhook - must be nested under "mission" key
const webhookData = {
name: mission.name,
oddScope: mission.oddScope,
niveau: mission.niveau,
intention: mission.intention,
missionType: mission.missionType,
donneurDOrdre: mission.donneurDOrdre,
projection: mission.projection,
services: mission.services,
participation: mission.participation,
profils: mission.profils,
mission: {
name: mission.name,
oddScope: mission.oddScope,
niveau: mission.niveau,
intention: mission.intention,
missionType: mission.missionType,
donneurDOrdre: mission.donneurDOrdre,
projection: mission.projection,
services: mission.services,
participation: mission.participation,
profils: mission.profils,
}
};
logger.debug('Calling N8N GeneratePlan webhook', {
missionId,
missionName: mission.name
missionName: mission.name,
webhookData
});
// Call N8N webhook
@ -92,11 +95,17 @@ export async function POST(
try {
const result = JSON.parse(responseText);
// The LLM response might be in different formats
actionPlan = result.plan || result.actionPlan || result.content || result.text || responseText;
// N8N returns { "response": "..." } based on the workflow
actionPlan = result.response || result.plan || result.actionPlan || result.content || result.text || responseText;
logger.debug('Parsed N8N response', {
hasResponse: !!result.response,
responseLength: actionPlan?.length || 0
});
} catch {
// If not JSON, use the raw text
actionPlan = responseText;
logger.debug('Using raw response text', { length: actionPlan.length });
}
logger.debug('Received action plan from N8N', {
@ -105,7 +114,8 @@ export async function POST(
});
// Save the action plan to the mission
const updatedMission = await prisma.mission.update({
// Note: Using 'as any' until prisma generate is run to update types
const updatedMission = await (prisma.mission as any).update({
where: { id: missionId },
data: {
actionPlan: actionPlan,
@ -184,7 +194,8 @@ export async function PUT(
}
// Update the action plan
const updatedMission = await prisma.mission.update({
// Note: Using 'as any' until prisma generate is run to update types
const updatedMission = await (prisma.mission as any).update({
where: { id: missionId },
data: {
actionPlan: actionPlan