implementazione 4 di n8n
All checks were successful
Deploy to VPS / build (push) Successful in 41s
Deploy to VPS / deploy (push) Successful in 30s

This commit is contained in:
AV77web 2026-06-06 18:17:18 +02:00
commit 4bfb3f0149
3 changed files with 273 additions and 0 deletions

6
next-env.d.ts vendored Normal file
View file

@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

1
tsconfig.tsbuildinfo Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,266 @@
{
"name": "MagRicambi - Ricerca prezzi articolo",
"nodes": [
{
"parameters": {
"content": "## MagRicambi — Ricerca prezzi\n\n**Setup:**\n1. Credenziale Header Auth sul Webhook (`X-Webhook-Secret`)\n2. Credenziale Tavily su nodo HTTP\n3. Credenziale OpenAI\n4. Attiva workflow → copia Production URL in `N8N_PRICE_SEARCH_WEBHOOK_URL`\n\n**Chiamata da MagRicambi:**\n`POST /api/articoli/ricerca-prezzi` → proxy verso questo webhook",
"height": 320,
"width": 420
},
"id": "sticky-note-setup",
"name": "Note Setup",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [-80, 80]
},
{
"parameters": {
"httpMethod": "POST",
"path": "magricambi-ricerca-prezzi",
"responseMode": "responseNode",
"authentication": "headerAuth",
"options": {}
},
"id": "webhook-magricambi",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [240, 300],
"webhookId": "magricambi-ricerca-prezzi",
"credentials": {
"httpHeaderAuth": {
"id": "REPLACE_WITH_CREDENTIAL_ID",
"name": "MagRicambi Webhook Secret"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "cond-descrizione-not-empty",
"leftValue": "={{ ($json.body?.descrizione ?? $json.descrizione ?? '').toString().trim() }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "if-descrizione-valida",
"name": "IF Descrizione valida",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [480, 300]
},
{
"parameters": {
"jsCode": "const item = $input.first().json;\nconst body = item.body ?? item;\n\nconst descrizione = String(body.descrizione ?? '').trim();\nconst barcode = String(body.barcode ?? '').trim();\nconst codice = String(body.codice ?? '').trim();\n\nconst parts = [];\nif (barcode) parts.push(barcode);\nif (codice) parts.push(codice);\nif (descrizione) parts.push(descrizione);\nparts.push('prezzo acquisto ricambio industriale Italia EUR');\n\nreturn [{\n json: {\n descrizione,\n barcode: barcode || null,\n codice: codice || null,\n quantita: body.quantita ?? null,\n query: parts.join(' '),\n meta: body.meta ?? {},\n },\n}];"
},
"id": "code-build-query",
"name": "Build Query",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [720, 200]
},
{
"parameters": {
"method": "POST",
"url": "https://api.tavily.com/search",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"query\": {{ JSON.stringify($json.query) }},\n \"search_depth\": \"basic\",\n \"max_results\": 8,\n \"include_answer\": false\n}",
"options": {
"timeout": 20000
}
},
"id": "http-tavily-search",
"name": "Tavily Search",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [960, 200],
"credentials": {
"httpHeaderAuth": {
"id": "REPLACE_WITH_CREDENTIAL_ID",
"name": "Tavily API"
}
}
},
{
"parameters": {
"method": "POST",
"url": "https://api.openai.com/v1/chat/completions",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "openAiApi",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"model\": \"gpt-4o-mini\",\n \"temperature\": 0.2,\n \"response_format\": { \"type\": \"json_object\" },\n \"messages\": [\n {\n \"role\": \"system\",\n \"content\": \"Sei un assistente per acquisti industriali. Ricevi risultati di ricerca web. Estrai SOLO offerte con prezzo esplicito nei testi forniti. Se il prezzo non è chiaro usa price null e confidence low. Rispondi SOLO con JSON valido nel formato: {\\\"offers\\\":[{\\\"vendor\\\":\\\"\\\",\\\"title\\\":\\\"\\\",\\\"price\\\":null,\\\"currency\\\":\\\"EUR\\\",\\\"url\\\":\\\"\\\",\\\"shippingNote\\\":\\\"\\\",\\\"confidence\\\":\\\"high|medium|low\\\",\\\"source\\\":\\\"snippet|page|estimate\\\"}]}. Massimo 8 offerte. Valuta EUR. Nessun testo fuori dal JSON.\"\n },\n {\n \"role\": \"user\",\n \"content\": \"Query: \" + $('Build Query').first().json.query + \"\\n\\nRisultati ricerca:\\n\" + JSON.stringify($json.results ?? $json)\n }\n ]\n}",
"options": {
"timeout": 25000
}
},
"id": "http-openai-extract",
"name": "OpenAI Extract",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [1200, 200],
"credentials": {
"openAiApi": {
"id": "REPLACE_WITH_CREDENTIAL_ID",
"name": "OpenAI API"
}
}
},
{
"parameters": {
"jsCode": "const query = $('Build Query').first().json.query;\nconst descrizione = $('Build Query').first().json.descrizione;\n\nlet parsed = { offers: [] };\n\ntry {\n const item = $input.first().json;\n const raw =\n item.choices?.[0]?.message?.content ??\n item.message?.content ??\n item.text ??\n '{}';\n const clean = String(raw).replace(/```json|```/g, '').trim();\n parsed = JSON.parse(clean);\n} catch (e) {\n parsed = { offers: [] };\n}\n\nconst offers = (parsed.offers ?? [])\n .map((o) => ({\n vendor: String(o.vendor ?? 'Sconosciuto').slice(0, 120),\n title: String(o.title ?? descrizione).slice(0, 200),\n price:\n o.price === null || o.price === undefined || o.price === ''\n ? null\n : Number(o.price),\n currency: 'EUR',\n url: String(o.url ?? ''),\n shippingNote: o.shippingNote ? String(o.shippingNote) : undefined,\n confidence: ['high', 'medium', 'low'].includes(o.confidence)\n ? o.confidence\n : 'low',\n source: ['page', 'snippet', 'estimate'].includes(o.source)\n ? o.source\n : 'snippet',\n }))\n .filter((o) => o.url && o.url !== '#');\n\nreturn [{\n json: {\n query,\n searchedAt: new Date().toISOString(),\n offers,\n disclaimer:\n 'Prezzi indicativi da fonti web: verificare su ordine fornitore prima dell\\'acquisto.',\n },\n}];"
},
"id": "code-format-response",
"name": "Format Response",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [1440, 200]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {
"responseCode": 200
}
},
"id": "respond-ok",
"name": "Respond OK",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [1680, 200]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ { \"error\": \"descrizione obbligatoria\" } }}",
"options": {
"responseCode": 400
}
},
"id": "respond-errore-400",
"name": "Respond Errore 400",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [720, 420]
}
],
"connections": {
"Webhook": {
"main": [
[
{
"node": "IF Descrizione valida",
"type": "main",
"index": 0
}
]
]
},
"IF Descrizione valida": {
"main": [
[
{
"node": "Build Query",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond Errore 400",
"type": "main",
"index": 0
}
]
]
},
"Build Query": {
"main": [
[
{
"node": "Tavily Search",
"type": "main",
"index": 0
}
]
]
},
"Tavily Search": {
"main": [
[
{
"node": "OpenAI Extract",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Extract": {
"main": [
[
{
"node": "Format Response",
"type": "main",
"index": 0
}
]
]
},
"Format Response": {
"main": [
[
{
"node": "Respond OK",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {},
"settings": {
"executionOrder": "v1",
"saveManualExecutions": true,
"callerPolicy": "workflowsFromSameOwner",
"errorWorkflow": ""
},
"staticData": null,
"tags": [
{
"name": "magricambi"
},
{
"name": "ricerca-prezzi"
}
],
"triggerCount": 1,
"updatedAt": "2026-06-04T12:00:00.000Z",
"versionId": "magricambi-ricerca-prezzi-v1",
"meta": {
"templateCredsSetupCompleted": false,
"instanceId": "magricambi-local"
},
"active": false
}