implementazione 4 di n8n
This commit is contained in:
commit
4bfb3f0149
3 changed files with 273 additions and 0 deletions
6
next-env.d.ts
vendored
Normal file
6
next-env.d.ts
vendored
Normal 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
1
tsconfig.tsbuildinfo
Normal file
File diff suppressed because one or more lines are too long
266
webhook/magricambi-ricerca-prezzi
Normal file
266
webhook/magricambi-ricerca-prezzi
Normal 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
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue