update MAM

This commit is contained in:
2026-02-16 22:24:06 +00:00
parent 3298dd64f2
commit 52a2cfd827
7 changed files with 214 additions and 155 deletions

View File

@@ -7,8 +7,8 @@
"id": "workflow-trigger",
"name": "Workflow Trigger",
"position": [
48,
640
2272,
864
],
"type": "n8n-nodes-base.executeWorkflowTrigger",
"typeVersion": 1
@@ -21,8 +21,8 @@
"id": "ssh-list-general",
"name": "List General Folders",
"position": [
272,
544
2496,
768
],
"type": "n8n-nodes-base.ssh",
"typeVersion": 1,
@@ -40,8 +40,8 @@
"id": "code-parse",
"name": "Code",
"position": [
496,
448
2720,
672
],
"type": "n8n-nodes-base.code",
"typeVersion": 2
@@ -61,8 +61,8 @@
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 3,
"position": [
1648,
368
3872,
592
],
"id": "409e6caa-c009-4b43-9170-e8c3251ef003",
"name": "Lookup Agent",
@@ -84,8 +84,8 @@
"type": "@tavily/n8n-nodes-tavily.tavilyTool",
"typeVersion": 1,
"position": [
1776,
592
4000,
816
],
"id": "b7ce39bf-87d5-4af4-bcf2-2b58381f1956",
"name": "tavily_search",
@@ -286,8 +286,8 @@
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
3200,
480
5424,
704
],
"id": "a85dde4b-2471-4ced-8e3c-f999a6297895",
"name": "Insert or update rows in a table",
@@ -308,8 +308,8 @@
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
1168,
544
3392,
768
],
"id": "3e1127d2-a059-475c-a90e-9cf2dc3a8d46",
"name": "Loop Over Items"
@@ -342,8 +342,8 @@
"type": "n8n-nodes-base.httpRequestTool",
"typeVersion": 4.3,
"position": [
1904,
592
4128,
816
],
"id": "14c979cf-00b4-4f79-8b49-e3e1b6fe54da",
"name": "google_search"
@@ -384,8 +384,8 @@
"type": "n8n-nodes-base.filter",
"typeVersion": 2.3,
"position": [
944,
544
3168,
768
],
"id": "8b666e89-b553-42a1-8d91-94fd5404760c",
"name": "Filter Unwanted",
@@ -401,8 +401,8 @@
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2,
"position": [
2400,
544
4624,
768
],
"id": "31313285-3e56-44d1-a79b-c65bd2206460",
"name": "Merge"
@@ -443,8 +443,8 @@
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
2976,
544
5200,
768
],
"id": "d0d29d9a-606e-41d4-a8e9-867f51469f1e",
"name": "verified"
@@ -454,8 +454,8 @@
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [
3424,
720
5648,
944
],
"id": "618a5f45-e4d2-4ba3-a61a-858b7025ab85",
"name": "Formatting"
@@ -469,8 +469,8 @@
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"typeVersion": 1.3,
"position": [
2032,
592
4256,
816
],
"id": "3baa52c0-8ce1-40fb-9b91-cfe6f869e9ae",
"name": "Structured Output Parser",
@@ -486,8 +486,8 @@
"id": "postgres-filter",
"name": "Fetch Existing",
"position": [
496,
640
2720,
864
],
"type": "n8n-nodes-base.postgres",
"typeVersion": 1,
@@ -509,8 +509,8 @@
"id": "merge-check",
"name": "Filter Existing",
"position": [
720,
544
2944,
768
],
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2
@@ -526,8 +526,8 @@
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.3,
"position": [
48,
448
2272,
672
],
"id": "f411d807-43c9-459b-a0bb-1c079a4d8666",
"name": "Schedule Trigger"
@@ -545,8 +545,8 @@
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.2,
"position": [
1776,
192
4000,
416
],
"id": "383ff376-cec7-4cde-a9a6-265cee6db626",
"name": "Send a message",
@@ -565,8 +565,8 @@
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1600,
192
3824,
416
],
"id": "b77eac76-096e-40d6-907e-cc861acca2dd",
"name": "Code in JavaScript"
@@ -599,8 +599,8 @@
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
1376,
416
3600,
640
],
"id": "e357ecd8-c8e7-48e0-9505-7044f883dc14",
"name": "If"
@@ -614,8 +614,8 @@
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"typeVersion": 1.7,
"position": [
2624,
544
4848,
768
],
"id": "273ae694-f28a-456c-ad24-0dcd4e64e710",
"name": "QA Agent"
@@ -624,28 +624,27 @@
"parameters": {
"model": {
"__rl": true,
"value": "openai/gpt-oss-120b",
"value": "lightning_ai/lightning-ai/gpt-oss-120b",
"mode": "list",
"cachedResultName": "openai/gpt-oss-120b"
"cachedResultName": "lightning_ai/lightning-ai/gpt-oss-120b"
},
"responsesApiEnabled": false,
"options": {
"responseFormat": "json_object",
"temperature": 0
"responseFormat": "json_object"
}
},
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.3,
"position": [
2176,
768
4400,
992
],
"id": "8198be70-fcae-44f1-8643-a6b97d7a33b0",
"name": "gpt-oss:120b - Validator",
"credentials": {
"openAiApi": {
"id": "QRBx9RMx4KoFwGgl",
"name": "Nvidia account"
"id": "sxSUdecXdMfKPuTu",
"name": "llm-proxy.ext.ben.io"
}
}
},
@@ -704,8 +703,8 @@
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.2,
"position": [
1488,
624
3712,
848
],
"id": "78150d6a-0c28-46e5-ba55-953b063b6872",
"name": "audible_lookup_asin"
@@ -766,8 +765,8 @@
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.2,
"position": [
1616,
592
3840,
816
],
"id": "a41d0ed6-78a1-401b-9dcb-74f533dedb51",
"name": "audible_search_book"
@@ -809,8 +808,8 @@
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.2,
"position": [
1888,
752
4112,
976
],
"id": "943011b5-1af5-45f9-8146-fcf5069e2fa5",
"name": "brave_search"
@@ -820,8 +819,8 @@
"type": "@n8n/n8n-nodes-langchain.toolWikipedia",
"typeVersion": 1,
"position": [
1760,
752
3984,
976
],
"id": "c31ac1dc-4653-4d01-98dc-0b20f83f7c01",
"name": "Wikipedia"
@@ -835,8 +834,8 @@
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-48,
112
2176,
336
],
"id": "83bb9595-37ee-47ee-a3b2-ab034cc75333",
"name": "Sticky Note"
@@ -855,15 +854,15 @@
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.3,
"position": [
1632,
736
3856,
960
],
"id": "c69843d8-49d9-43c7-8de9-f67918c87e12",
"name": "gpt/oss-120b",
"credentials": {
"openAiApi": {
"id": "QRBx9RMx4KoFwGgl",
"name": "Nvidia account"
"id": "sxSUdecXdMfKPuTu",
"name": "llm-proxy.ext.ben.io"
}
}
},
@@ -871,9 +870,9 @@
"parameters": {
"model": {
"__rl": true,
"value": "antigravity/gemini-3-flash",
"value": "nanogpt/minimax/minimax-m2.5",
"mode": "list",
"cachedResultName": "antigravity/gemini-3-flash"
"cachedResultName": "nanogpt/minimax/minimax-m2.5"
},
"responsesApiEnabled": false,
"options": {}
@@ -881,47 +880,70 @@
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.3,
"position": [
1376,
736
3600,
960
],
"id": "ae56277d-f35d-4ece-b6b5-7981e649f242",
"name": "OpenAI Chat Model",
"credentials": {
"openAiApi": {
"id": "sxSUdecXdMfKPuTu",
"name": "llm-proxy.ben.io"
"name": "llm-proxy.ext.ben.io"
}
}
},
{
"parameters": {
"workflowId": {
"__rl": true,
"value": "WkAdUd9jXTtPagGO",
"mode": "list",
"cachedResultUrl": "/workflow/WkAdUd9jXTtPagGO",
"cachedResultName": "MAM Series Matcher"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {},
"matchingColumns": [],
"schema": [],
"attemptToConvertTypes": false,
"convertFieldsToString": true
},
"options": {
"waitForSubWorkflow": true
}
"jsCode": "// Fuzzy match incoming title against followed series\n// Input: title from Execute Workflow trigger\n// Output: matched series or null\n\nconst inputItem = $input.first();\nconst title = inputItem.json.title || '';\nconst metadata = inputItem.json.metadata || {};\n\n// Get all followed series from Supabase\nconst seriesList = $('Get Active Followed Series').all();\n\n// Helper function to normalize strings for comparison\nfunction normalize(str) {\n return str.toLowerCase()\n .replace(/[^a-z0-9\\s]/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n// Helper function to extract patterns from title\nfunction extractPatterns(title) {\n const patterns = {\n volume: null,\n author: null,\n cleanTitle: title\n };\n \n // Extract volume numbers (e.g., \"Vol 01\", \"Volume 1\", \"v01\")\n const volumeMatch = title.match(/\\b(?:vol(?:ume)?\\.?\\s*(\\d+)|v(\\d+))\\b/i);\n if (volumeMatch) {\n patterns.volume = parseInt(volumeMatch[1] || volumeMatch[2]);\n }\n \n // Extract author (common patterns: \"Author Name - Title\", \"[Author Name]\")\n const authorMatch1 = title.match(/^([^-\\[\\]]+?)\\s*-\\s*(.+)/);\n const authorMatch2 = title.match(/\\[([^\\]]+)\\]/);\n \n if (authorMatch1) {\n patterns.author = authorMatch1[1].trim();\n patterns.cleanTitle = authorMatch1[2].trim();\n } else if (authorMatch2) {\n patterns.author = authorMatch2[1].trim();\n }\n \n return patterns;\n}\n\n// Extract patterns from input title\nconst inputPatterns = extractPatterns(title);\nconst normalizedInput = normalize(inputPatterns.cleanTitle);\n\n// Try to match against series\nlet bestMatch = null;\nlet bestScore = 0;\n\nfor (const series of seriesList) {\n const seriesName = series.json.series_name || '';\n const author = series.json.author || '';\n const normalizedSeries = normalize(seriesName);\n const normalizedAuthor = normalize(author);\n \n let score = 0;\n \n // Check if series name is contained in title\n if (normalizedInput.includes(normalizedSeries)) {\n score += 50;\n }\n \n // Check if title is contained in series name\n if (normalizedSeries.includes(normalizedInput)) {\n score += 40;\n }\n \n // Check author match\n if (inputPatterns.author && normalizedAuthor) {\n if (normalize(inputPatterns.author).includes(normalizedAuthor)) {\n score += 30;\n }\n }\n \n // Simple word overlap check\n const inputWords = normalizedInput.split(' ').filter(w => w.length > 2);\n const seriesWords = normalizedSeries.split(' ').filter(w => w.length > 2);\n const commonWords = inputWords.filter(w => seriesWords.includes(w));\n score += commonWords.length * 5;\n \n if (score > bestScore) {\n bestScore = score;\n bestMatch = series.json;\n }\n}\n\n// Return match if score is above threshold\nif (bestMatch && bestScore >= 40) {\n return [{\n json: {\n matched: true,\n series_id: bestMatch.id,\n series_name: bestMatch.series_name,\n author: bestMatch.author,\n category: bestMatch.category,\n smb_path: bestMatch.smb_path,\n match_score: bestScore,\n original_title: title,\n extracted_volume: inputPatterns.volume,\n extracted_author: inputPatterns.author\n }\n }];\n} else {\n return [{\n json: {\n matched: false,\n series_id: null,\n series_name: null,\n author: null,\n category: null,\n smb_path: null,\n match_score: bestScore,\n original_title: title\n }\n }];\n}"
},
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.3,
"id": "6bc67604-5a6a-45ed-af56-5ab176e65e54",
"name": "Fuzzy Match Title",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2112,
352
4624,
560
]
},
{
"parameters": {
"operation": "select",
"schema": {
"__rl": true,
"mode": "list",
"value": "public"
},
"table": {
"__rl": true,
"value": "followed_series",
"mode": "list",
"cachedResultName": "followed_series"
},
"returnAll": true,
"where": {
"values": [
{
"column": "active",
"value": "true"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
4352,
528
],
"id": "5e966fb1-22e6-405c-b2cb-14cc7ae53cb1",
"name": "Call 'MAM Series Matcher'"
"id": "e10726a0-7a1c-4b9b-ba16-1ee3120c6486",
"name": "Get Active Followed Series",
"credentials": {
"postgres": {
"id": "9grzZwW7Br6SzdV8",
"name": "n8n-media"
}
}
}
],
"connections": {
@@ -929,9 +951,9 @@
"main": [
[
{
"index": 0,
"node": "Filter Existing",
"type": "main"
"type": "main",
"index": 0
}
]
]
@@ -940,9 +962,9 @@
"main": [
[
{
"index": 0,
"node": "Code",
"type": "main"
"type": "main",
"index": 0
},
{
"node": "Fetch Existing",
@@ -956,9 +978,9 @@
"main": [
[
{
"index": 0,
"node": "List General Folders",
"type": "main"
"type": "main",
"index": 0
}
]
]
@@ -978,7 +1000,7 @@
"main": [
[
{
"node": "Call 'MAM Series Matcher'",
"node": "Get Active Followed Series",
"type": "main",
"index": 0
}
@@ -1103,9 +1125,9 @@
"main": [
[
{
"index": 1,
"node": "Filter Existing",
"type": "main"
"type": "main",
"index": 1
}
]
]
@@ -1225,11 +1247,6 @@
]
]
},
"gpt/oss-120b": {
"ai_languageModel": [
[]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
@@ -1241,7 +1258,7 @@
]
]
},
"Call 'MAM Series Matcher'": {
"Fuzzy Match Title": {
"main": [
[
{
@@ -1251,6 +1268,17 @@
}
]
]
},
"Get Active Followed Series": {
"main": [
[
{
"node": "Fuzzy Match Title",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
@@ -1259,7 +1287,7 @@
"availableInMCP": false
},
"triggerCount": 1,
"versionId": "7e06408b-8b2a-4d35-83fa-00ffadece2f8",
"versionId": "28f307b7-5456-4809-a3e6-af121a2058dc",
"owner": {
"type": "personal",
"projectId": "FeLO36wNUAcn61Wj",