feat(flows):allineamento filtri
This commit is contained in:
@@ -31,8 +31,8 @@
|
||||
"type": "n8n-nodes-base.gmail",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
-1600,
|
||||
48
|
||||
-1568,
|
||||
144
|
||||
],
|
||||
"webhookId": "9959425d-bdbb-4597-ad72-77ec25fcf49c",
|
||||
"credentials": {
|
||||
@@ -54,25 +54,25 @@
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-1376,
|
||||
48
|
||||
144
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "getAll",
|
||||
"limit": 20,
|
||||
"filters": {
|
||||
"readStatus": "unread",
|
||||
"q": "-label:Processed"
|
||||
},
|
||||
"limit": 20
|
||||
"q": "-label:Processed newer_than:1d",
|
||||
"readStatus": "unread"
|
||||
}
|
||||
},
|
||||
"id": "b73f8915-1173-43a8-9f78-0c49fa471b7d",
|
||||
"name": "Gmail - Fetch ultime 3h",
|
||||
"type": "n8n-nodes-base.gmail",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
608,
|
||||
160
|
||||
-1008,
|
||||
304
|
||||
],
|
||||
"webhookId": "efc020f5-87c1-4009-8bc2-82ad371c0dde",
|
||||
"credentials": {
|
||||
@@ -92,8 +92,8 @@
|
||||
"type": "n8n-nodes-base.gmail",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
-944,
|
||||
48
|
||||
-736,
|
||||
-32
|
||||
],
|
||||
"webhookId": "078abbbb-a75c-4507-8c0a-23196b1fef45",
|
||||
"credentials": {
|
||||
@@ -114,13 +114,12 @@
|
||||
"type": "n8n-nodes-base.aggregate",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-720,
|
||||
48
|
||||
-512,
|
||||
-32
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "const item = $input.first();\nconst emailsRaw = item.json.emails || [];\nconst emails = emailsRaw.map(e => e.json || e);\n\nif (emails.length === 0) {\n return [{ json: { prompt: 'NESSUNA_EMAIL', emailCount: 0, emailMeta: [] } }];\n}\n\nconst emailMeta = emails.map(e => ({ id: e.id, threadId: e.threadId || e.id }));\n\nconst threads = {};\nemails.forEach(e => {\n const tid = e.threadId || e.id;\n if (!threads[tid]) threads[tid] = [];\n threads[tid].push(e.id);\n});\n\nconst emailList = emails.map((e, i) => {\n const from = e.From || e.from || 'Mittente sconosciuto';\n const subject = e.Subject || e.subject || '(nessun oggetto)';\n const date = e.Date || e.date || '';\n const body = (e.text || e.snippet || '').substring(0, 600);\n // Gmail node restituisce payload.mimeType='multipart/mixed' quando ci sono allegati\n const hasMixedPayload = e.payload && e.payload.mimeType === 'multipart/mixed';\n const hasAtt = (hasMixedPayload || (e.attachments && e.attachments.length > 0)) ? 'Sì (PDF probabile)' : 'No';\n const threadCount = (threads[e.threadId || e.id] || []).length;\n const threadNote = threadCount > 1 ? ` [THREAD: ${threadCount} msg]` : '';\n return `--- EMAIL ${i + 1}${threadNote} ---\nID: ${e.id}\nThreadID: ${e.threadId || e.id}\nDa: ${from}\nOggetto: ${subject}\nData: ${date}\nAllegati: ${hasAtt}\n${body}`;\n}).join('\\n\\n');\n\nconst today = new Date().toLocaleDateString('it-IT', {\n weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'\n});\n\nconst prompt = `Sei l'assistente personale di Martin. Analizza ${emails.length} email non lette.\n\nLABEL GMAIL (nomi ESATTI, rispetta maiuscole e &):\nLavoro/Comunicazioni, Lavoro/Cedolino, Lavoro/Contratto\nCondominio/Comunicazioni, Condominio/Spese\nInternet/Bollette, Internet/Comunicazioni\nLuce&Gas/Bollette, Luce&Gas/Comunicazioni\nMarketing, Prenotazioni\nRicevute/Trasporti, Ricevute/Acquisti, Ricevute/Abbonamenti\n\nREGOLE CLASSIFICAZIONE:\n- action \"trash\" solo per Marketing/newsletter → verranno eliminate automaticamente\n- action \"keep\" per tutto il resto → etichettate e incluse nel report\n- EMAIL con stesso ThreadID = stessa conversazione: riassumile INSIEME nel report\n- Non usare label non presenti nella lista sopra\n\nPAPERLESS ACTION (campo aggiuntivo, guarda il campo Allegati):\n- paperless_action \"pdf_allegato\" → email con allegato PDF che potrebbe valere la pena archiviare (Allegati: Sì) — bollette, ricevute, contratti, cedolini, comunicazioni ufficiali, etc.\n- paperless_action null → email senza allegati PDF rilevanti, o allegati irrilevanti (immagini, firma, etc.)\n\nREPORT (daily_report) - OBBLIGATORIO essere narrativo:\n- Scrivi come un assistente personale che racconta a Martin cosa c'è nella sua inbox\n- NON fare semplice elenco mittente/oggetto: racconta cosa è successo\n- Raggruppa thread correlati in un unico punto\n- Estrai dettagli concreti: importi €, date, numeri ordine, azioni richieste\n- Segnala esplicitamente se Martin deve fare qualcosa\n- Ordine: urgente/da rispondere, poi info utili, poi ricevute, poi eliminati\n- Usa *grassetto* e emoji Telegram. Max 3000 caratteri.\n\nRispondi SOLO JSON valido:\n{\n \"emails\": [\n {\"id\": \"ID_ESATTO\", \"action\": \"trash\", \"labels\": [\"Marketing\"], \"summary\": \"\", \"paperless_action\": null},\n {\"id\": \"ID_ESATTO\", \"action\": \"keep\", \"labels\": [\"Luce&Gas/Bollette\"], \"summary\": \"E.ON €87,50 scad. 28/03\", \"paperless_action\": \"pdf_allegato\"},\n {\"id\": \"ID_ESATTO\", \"action\": \"keep\", \"labels\": [\"Internet/Bollette\"], \"summary\": \"Avviso bolletta Fastweb disponibile\", \"paperless_action\": null}\n ],\n \"daily_report\": \"[REPORT NARRATIVO]\"\n}\n\n${emailList}`;\n\nreturn [{ json: { prompt, emailCount: emails.length, emailMeta } }];"
|
||||
},
|
||||
"id": "f6b052dd-48da-41dd-8c68-f58c8b9f8c1d",
|
||||
@@ -128,13 +127,12 @@
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-496,
|
||||
48
|
||||
-288,
|
||||
-32
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "const raw = $input.first().json;\n// Formato OpenAI: choices[0].message.content\nconst rawText = (raw.choices && raw.choices[0] && raw.choices[0].message)\n ? raw.choices[0].message.content\n : (raw.text || raw.output || '');\n\nconst cleaned = rawText\n .replace(/<thinking>[\\s\\S]*?<\\/thinking>/gi, '')\n .replace(/```json\\n?/g, '')\n .replace(/```\\n?/g, '')\n .trim();\n\nlet parsed;\ntry {\n parsed = JSON.parse(cleaned);\n} catch (e) {\n const match = cleaned.match(/\\{[\\s\\S]*\\}/);\n if (match) {\n try { parsed = JSON.parse(match[0]); }\n catch (e2) { throw new Error('Parse error: ' + rawText.substring(0, 300)); }\n } else {\n throw new Error('No JSON found: ' + rawText.substring(0, 300));\n }\n}\n\nif (!parsed.emails || !Array.isArray(parsed.emails)) {\n throw new Error('Missing emails array: ' + JSON.stringify(parsed).substring(0, 200));\n}\n\nconst emailMeta = $('Costruisci Prompt').first().json.emailMeta || [];\nconst metaMap = {};\nemailMeta.forEach(m => { metaMap[m.id] = m.threadId; });\n\nparsed.emails = parsed.emails.map(e => ({\n ...e,\n threadId: metaMap[e.id] || e.id\n}));\n\nreturn [{ json: parsed }];"
|
||||
},
|
||||
"id": "cac1efac-2d3e-482c-a529-22bdd2136575",
|
||||
@@ -142,8 +140,8 @@
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-64,
|
||||
48
|
||||
432,
|
||||
-32
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -159,8 +157,8 @@
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
160,
|
||||
-96
|
||||
704,
|
||||
-192
|
||||
],
|
||||
"webhookId": "d42335fd-4748-4406-b0dd-d8d7f4d0b027",
|
||||
"credentials": {
|
||||
@@ -180,8 +178,8 @@
|
||||
"type": "n8n-nodes-base.splitOut",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
160,
|
||||
48
|
||||
704,
|
||||
96
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -200,8 +198,8 @@
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
384,
|
||||
48
|
||||
944,
|
||||
96
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -214,8 +212,8 @@
|
||||
"type": "n8n-nodes-base.gmail",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
608,
|
||||
-96
|
||||
1200,
|
||||
-80
|
||||
],
|
||||
"webhookId": "abc3390b-9a83-4a40-bf36-d52a921a25b0",
|
||||
"credentials": {
|
||||
@@ -236,8 +234,8 @@
|
||||
"type": "n8n-nodes-base.gmail",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
832,
|
||||
-96
|
||||
1488,
|
||||
-80
|
||||
],
|
||||
"webhookId": "e0919d48-248a-4191-b8ca-6f9ff619bf16",
|
||||
"credentials": {
|
||||
@@ -249,7 +247,6 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "const items = $input.all();\nif (items.length === 0) return [];\n\n// Costruisce mappa nome → ID dalle label Gmail\nconst labelsData = $('Gmail - Leggi tutte le label').all();\nconst labelMap = {};\nlabelsData.forEach(item => {\n const name = item.json.name;\n const id = item.json.id;\n if (name && id) labelMap[name] = id;\n});\n\n// Processa ogni email nella branch \"keep\"\nconst result = [];\nfor (const item of items) {\n const email = item.json;\n const requestedLabels = Array.isArray(email.labels) ? email.labels : [];\n const resolvedLabelIds = requestedLabels\n .map(name => labelMap[name])\n .filter(id => !!id);\n\n // Skip se nessuna label trovata (evita errore Gmail 'No label add or removes')\n if (resolvedLabelIds.length === 0) continue;\n\n result.push({\n json: {\n id: String(email.id || ''),\n threadId: String(email.threadId || email.id || ''),\n action: String(email.action || 'keep'),\n labels: requestedLabels,\n summary: String(email.summary || ''),\n resolvedLabelIds: resolvedLabelIds\n }\n });\n}\n\nreturn result;"
|
||||
},
|
||||
"id": "99e0b420-3d9a-4ece-80ed-27dab248b018",
|
||||
@@ -257,8 +254,8 @@
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
608,
|
||||
192
|
||||
1504,
|
||||
368
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -272,8 +269,8 @@
|
||||
"type": "n8n-nodes-base.gmail",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
832,
|
||||
192
|
||||
1696,
|
||||
368
|
||||
],
|
||||
"webhookId": "debadb11-9b5c-44a6-9ad0-7f1a6598b14a",
|
||||
"credentials": {
|
||||
@@ -284,36 +281,24 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"path": "gmail-digest-test",
|
||||
"options": {}
|
||||
},
|
||||
"id": "test-webhook-trigger",
|
||||
"name": "🧪 Test Webhook",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-1824,
|
||||
200
|
||||
-112
|
||||
],
|
||||
"parameters": {
|
||||
"path": "gmail-digest-test",
|
||||
"responseMode": "onReceived",
|
||||
"options": {}
|
||||
},
|
||||
"webhookId": "gmail-digest-test-001"
|
||||
},
|
||||
{
|
||||
"id": "copilot-gpt41-node",
|
||||
"name": "GPT-4.1 - Classifica Email",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
-336,
|
||||
48
|
||||
],
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://api.githubcopilot.com/chat/completions",
|
||||
"sendBody": true,
|
||||
"options": {},
|
||||
"genericAuthType": "httpHeaderAuth",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
@@ -335,29 +320,36 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"authentication": "none",
|
||||
"sendBody": true,
|
||||
"contentType": "raw",
|
||||
"rawContentType": "application/json",
|
||||
"body": "={{ JSON.stringify({\"model\":\"gpt-4.1\",\"messages\":[{\"role\":\"system\",\"content\":\"Rispondi SOLO con JSON valido.\"},{\"role\":\"user\",\"content\": $('Costruisci Prompt').first().json.prompt}],\"response_format\":{\"type\":\"json_object\"},\"max_tokens\":8192}) }}"
|
||||
"body": "={{ JSON.stringify({\"model\":\"gpt-4.1\",\"messages\":[{\"role\":\"system\",\"content\":\"Rispondi SOLO con JSON valido.\"},{\"role\":\"user\",\"content\": $('Costruisci Prompt').first().json.prompt}],\"response_format\":{\"type\":\"json_object\"},\"max_tokens\":8192}) }}",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {}
|
||||
},
|
||||
{
|
||||
"id": "copilot-token-refresh",
|
||||
"name": "Ottieni Token Copilot",
|
||||
"id": "copilot-gpt41-node",
|
||||
"name": "GPT-4.1 - Classifica Email",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
-496,
|
||||
48
|
||||
],
|
||||
192,
|
||||
-32
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "https://api.github.com/copilot_internal/v2/token",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth",
|
||||
"options": {}
|
||||
},
|
||||
"id": "copilot-token-refresh",
|
||||
"name": "Ottieni Token Copilot",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
-64,
|
||||
-32
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "vBwUxlzKrX3oDHyN",
|
||||
@@ -366,14 +358,6 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "if-paperless",
|
||||
"name": "Ha azione Paperless?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
384,
|
||||
250
|
||||
],
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
@@ -393,18 +377,19 @@
|
||||
"rightValue": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "if-bolletta-or-fastweb",
|
||||
"name": "Ha PDF allegato?",
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "if-paperless",
|
||||
"name": "Ha azione Paperless?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
608,
|
||||
350
|
||||
],
|
||||
1200,
|
||||
272
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {},
|
||||
@@ -420,32 +405,32 @@
|
||||
"rightValue": "pdf_allegato"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "if-bolletta-or-fastweb",
|
||||
"name": "Ha PDF allegato?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1488,
|
||||
112
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Leggi il messaggio Gmail completo per trovare gli allegati PDF\n// Il nodo Gmail - Leggi contenuto ha già il payload completo\nconst emails = $input.all();\nconst results = [];\nfor (const emailItem of emails) {\n const email = emailItem.json;\n if (email.paperless_action !== 'pdf_allegato') continue;\n // Trova allegati PDF in payload.parts (ricorsivo)\n function findPDFs(parts, found) {\n if (!parts) return;\n for (const p of parts) {\n if (p.parts) findPDFs(p.parts, found);\n if (p.body && p.body.attachmentId) {\n const isPDF = (p.mimeType||'').includes('pdf') || (p.filename||'').toLowerCase().endsWith('.pdf');\n if (isPDF) found.push({ attachment_id: p.body.attachmentId, filename: p.filename || 'documento.pdf' });\n }\n }\n }\n const pdfs = [];\n findPDFs((email.payload && email.payload.parts) || [], pdfs);\n for (const pdf of pdfs) {\n results.push({ json: {\n email_id: email.id,\n attachment_id: pdf.attachment_id,\n filename: pdf.filename,\n hint: (email.Subject || email.subject || '') + ' da ' + (email.From || email.from || ''),\n from: email.From || email.from || '',\n }});\n }\n}\nreturn results;"
|
||||
},
|
||||
"id": "http-trigger-bolletta",
|
||||
"name": "Estrai Allegati PDF",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
832,
|
||||
260
|
||||
],
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "// Leggi il messaggio Gmail completo per trovare gli allegati PDF\n// Il nodo Gmail - Leggi contenuto ha già il payload completo\nconst emails = $input.all();\nconst results = [];\nfor (const emailItem of emails) {\n const email = emailItem.json;\n if (email.paperless_action !== 'pdf_allegato') continue;\n // Trova allegati PDF in payload.parts (ricorsivo)\n function findPDFs(parts, found) {\n if (!parts) return;\n for (const p of parts) {\n if (p.parts) findPDFs(p.parts, found);\n if (p.body && p.body.attachmentId) {\n const isPDF = (p.mimeType||'').includes('pdf') || (p.filename||'').toLowerCase().endsWith('.pdf');\n if (isPDF) found.push({ attachment_id: p.body.attachmentId, filename: p.filename || 'documento.pdf' });\n }\n }\n }\n const pdfs = [];\n findPDFs((email.payload && email.payload.parts) || [], pdfs);\n for (const pdf of pdfs) {\n results.push({ json: {\n email_id: email.id,\n attachment_id: pdf.attachment_id,\n filename: pdf.filename,\n hint: (email.Subject || email.subject || '') + ' da ' + (email.From || email.from || ''),\n from: email.From || email.from || '',\n }});\n }\n}\nreturn results;"
|
||||
}
|
||||
1824,
|
||||
-16
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "if-is-test",
|
||||
"name": "È un test?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
384,
|
||||
260
|
||||
],
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {},
|
||||
@@ -460,26 +445,36 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "if-is-test",
|
||||
"name": "È un test?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-1184,
|
||||
144
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "getAll",
|
||||
"limit": 20,
|
||||
"filters": {
|
||||
"q": "-label:Processed newer_than:1d",
|
||||
"readStatus": "unread"
|
||||
}
|
||||
},
|
||||
"id": "gmail-fetch-all-unread",
|
||||
"name": "Gmail - Fetch tutte non lette",
|
||||
"type": "n8n-nodes-base.gmail",
|
||||
"typeVersion": 2.1,
|
||||
"position": [
|
||||
608,
|
||||
360
|
||||
-992,
|
||||
-32
|
||||
],
|
||||
"parameters": {
|
||||
"operation": "getAll",
|
||||
"filters": {
|
||||
"readStatus": "unread",
|
||||
"q": "-label:Processed"
|
||||
},
|
||||
"limit": 20
|
||||
},
|
||||
"webhookId": "94c9342a-4f54-42f1-9f8f-4a165468ba33",
|
||||
"credentials": {
|
||||
"gmailOAuth2": {
|
||||
"id": "qvOikS6IF0H5khr8",
|
||||
@@ -488,14 +483,6 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n_callcore",
|
||||
"name": "Chiama Core Upload",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
2200,
|
||||
200
|
||||
],
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://orchestrator.mt-home.uk/webhook/paperless-upload",
|
||||
@@ -503,6 +490,79 @@
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ email_id: $json.email_id, attachment_id: $json.attachment_id, filename: $json.filename, hint: $json.hint, from: $json.from }) }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "n_callcore",
|
||||
"name": "Chiama Core Upload",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
2160,
|
||||
128
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "n_estrai_fatti",
|
||||
"name": "🧠 Estrai Fatti",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1900,
|
||||
400
|
||||
],
|
||||
"parameters": {
|
||||
"jsCode": "const parsed = $input.first().json;\nconst copilotToken = $('Ottieni Token Copilot').first().json.token;\n\n// Only non-trash emails with a summary\nconst keepEmails = (parsed.emails || []).filter(e => e.action !== 'trash' && e.summary?.trim());\nif (keepEmails.length === 0) return [{ json: { _skip: true } }];\n\nconst emailList = keepEmails.map((e, i) =>\n `${i+1}. [labels: ${(e.labels||[]).join(',')}] [threadId: ${e.threadId}]\\nRiassunto: ${e.summary}`\n).join('\\n\\n');\n\nconst gptRes = await this.helpers.httpRequest({\n method: 'POST',\n url: 'https://api.githubcopilot.com/chat/completions',\n headers: {\n 'Authorization': `Bearer ${copilotToken}`,\n 'editor-version': 'vscode/1.95.3',\n 'Copilot-Integration-Id': 'vscode-chat',\n 'content-type': 'application/json',\n },\n body: JSON.stringify({\n model: 'gpt-4.1',\n messages: [\n { role: 'system', content: 'Sei un assistente che estrae fatti strutturati da email per il sistema di memoria di Pompeo, l\\'AI personale di Martin Tahiraj. Rispondi SEMPRE con JSON valido.' },\n { role: 'user', content: `Estrai un fatto strutturato per ogni email che Martin ha ricevuto oggi.\n\nEMAIL (indice. [label] [threadId]):\n${emailList}\n\nRegole TTL:\n- Prenotazioni, viaggi, eventi imminenti → ttl_days: 14\n- Bollette → ttl_days: 45\n- Lavoro, condominio, documenti ufficiali → ttl_days: 90\n- Abbonamenti, acquisti → ttl_days: 30\n- Default → ttl_days: 30\n\nRispondi SOLO JSON:\n{\n \"facts\": [\n {\n \"index\": 1,\n \"thread_id\": \"threadId_esatto\",\n \"category\": \"finance|work|home|personal|travel|subscription|health\",\n \"subject\": \"fatto conciso max 120 caratteri\",\n \"detail\": {\"from\": \"mittente\", \"labels\": [\"label1\"]},\n \"action_required\": false,\n \"action_text\": \"\",\n \"pompeo_note\": \"inner monologue breve: perché questo fatto è rilevante per capire Martin o la sua vita\",\n \"entity_refs\": {\"people\": [], \"places\": [], \"products\": [], \"amounts\": []},\n \"ttl_days\": 30\n }\n ]\n}` }\n ],\n temperature: 0.2,\n response_format: { type: 'json_object' }\n })\n});\n\nconst content = gptRes.choices?.[0]?.message?.content || '{}';\nconst { facts = [] } = JSON.parse(content);\n\nconst now = new Date();\nconst results = facts.map(f => {\n const email = keepEmails[f.index - 1] || keepEmails.find(e => e.threadId === f.thread_id);\n if (!f.thread_id && !email?.threadId) return null;\n const expiresAt = (f.ttl_days > 0)\n ? new Date(now.getTime() + f.ttl_days * 86400000).toISOString()\n : null;\n const detail = JSON.stringify({ ...(f.detail || {}), labels: email?.labels || [] });\n return { json: {\n thread_id: f.thread_id || email?.threadId,\n category: f.category || 'personal',\n subject: (f.subject || email?.summary || '').substring(0, 200).replace(/'/g, \"''\"),\n detail,\n action_required: f.action_required ? true : false,\n action_text: (f.action_text || '').replace(/'/g, \"''\"),\n pompeo_note: (f.pompeo_note || '').replace(/'/g, \"''\"),\n entity_refs: JSON.stringify(f.entity_refs || {}),\n expires_at: expiresAt\n }};\n}).filter(Boolean);\n\nreturn results.length > 0 ? results : [{ json: { _skip: true } }];"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n_if_skip",
|
||||
"name": "🔀 Ha Fatti?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2120,
|
||||
400
|
||||
],
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "cond_skip",
|
||||
"leftValue": "={{ $json._skip }}",
|
||||
"rightValue": true,
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "notEquals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n_upsert_memoria",
|
||||
"name": "💾 Upsert Memoria",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.5,
|
||||
"position": [
|
||||
2340,
|
||||
400
|
||||
],
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "mRqzxhSboGscolqI",
|
||||
"name": "Pompeo — PostgreSQL"
|
||||
}
|
||||
},
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "INSERT INTO memory_facts\n (user_id, source, source_ref, category, subject, detail, action_required, action_text, pompeo_note, entity_refs, expires_at)\nVALUES (\n 'martin',\n 'email',\n '{{ $json.thread_id }}',\n '{{ $json.category }}',\n '{{ $json.subject }}',\n '{{ $json.detail }}',\n {{ $json.action_required }},\n '{{ $json.action_text }}',\n '{{ $json.pompeo_note }}',\n '{{ $json.entity_refs }}'::jsonb,\n {{ $json.expires_at ? \"'\" + $json.expires_at + \"'::timestamptz\" : \"NULL\" }}\n)\nON CONFLICT (user_id, source, source_ref) WHERE source_ref IS NOT NULL DO UPDATE SET\n subject = EXCLUDED.subject,\n detail = EXCLUDED.detail,\n action_required = EXCLUDED.action_required,\n action_text = EXCLUDED.action_text,\n pompeo_note = EXCLUDED.pompeo_note,\n entity_refs = EXCLUDED.entity_refs,\n expires_at = EXCLUDED.expires_at",
|
||||
"options": {}
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -647,6 +707,11 @@
|
||||
"node": "Dividi Email",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "🧠 Estrai Fatti",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -759,6 +824,29 @@
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🧠 Estrai Fatti": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🔀 Ha Fatti?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🔀 Ha Fatti?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "💾 Upsert Memoria",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
@@ -767,7 +855,7 @@
|
||||
"callerPolicy": "workflowsFromSameOwner"
|
||||
},
|
||||
"triggerCount": 2,
|
||||
"versionId": "55dea25a-a72c-4c4f-8126-cc6dae55e15a",
|
||||
"versionId": "ba68ff52-7646-45ef-b999-af915df23a00",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "Hdttz401OqqtObPo",
|
||||
|
||||
Reference in New Issue
Block a user