Better solution here - again thanks for your help:
{
“nodes”: [
{
“parameters”: {},
“type”: “n8n-nodes-base.manualTrigger”,
“typeVersion”: 1,
“position”: [
-1056,
1792
],
“id”: “68ce03a1-38d8-4e4c-b211-b8318b7ad7ee”,
“name”: “When clicking ‘Execute workflow’”
},
{
“parameters”: {
“query”: “=machine learning in healthcare”,
“providers”: [
“pubmed”
],
“limit”: 20,
“yearFrom”: 2020,
“yearTo”: 2025,
“additionalFields”: {}
},
“id”: “07cb9b28-755e-4372-ac36-b00616e9f11b”,
“name”: “PDF Vector - Search Papers”,
“type”: “n8n-nodes-pdfvector.pdfVector”,
“position”: [
-832,
1792
],
“typeVersion”: 1,
“credentials”: {
“pdfVectorApi”: {
“id”: “q76YQwYHaBcm2Hgo”,
“name”: “PDF Vector account”
}
},
“notes”: “Search across multiple academic databases”
},
{
“parameters”: {
“jsCode”: “// Get the input data from the previous node\nconst items = $input.all();\n\n// Extract the results array from the search response\nconst results = items[0].json.results || ;\n\n// CAPTURE THE SEARCH QUERY from the original search parameters\nconst searchQuery = items[0].json.query || \n $(‘PDF Vector - Search Papers’).first().json.query || \n ‘machine learning in healthcare’; // fallback\n\nconsole.log(Found ${results.length} total papers for query: \"${searchQuery}\");\n\n// Sort papers by citation count (descending order)\nconst sortedResults = results.sort((a, b) => {\n const citationsA = a.totalCitations || a.citations || 0;\n const citationsB = b.totalCitations || b.citations || 0;\n return citationsB - citationsA;\n});\n\nconsole.log(‘Top 5 papers by citations:’);\nsortedResults.slice(0, 5).forEach((paper, i) => {\n console.log(${i+1}. ${paper.title} (${paper.totalCitations || paper.citations || 0} citations));\n});\n\n// Return sorted results as individual items WITH search query attached\nreturn sortedResults.map(paper => ({\n json: {\n …paper,\n originalSearchQuery: searchQuery // Pass the search query along\n },\n binary: {}\n}));”
},
“id”: “8b7a4002-1b83-4364-ba99-49c5a930f3a4”,
“name”: “Sort by Citations”,
“type”: “n8n-nodes-base.code”,
“position”: [
-608,
1792
],
“typeVersion”: 1
},
{
“parameters”: {
“jsCode”: “// Get all input items (sorted papers)\nconst items = $input.all();\n\nconsole.log(Processing ${items.length} sorted papers);\n\n// Filter for papers with abstracts and good metadata\nconst qualityPapers = items.filter(item => {\n const paper = item.json;\n const hasAbstract = paper.abstract && paper.abstract.length > 50;\n const hasTitle = paper.title && paper.title.length > 10;\n const hasAuthors = paper.authors && paper.authors.length > 0;\n \n if (hasAbstract && hasTitle && hasAuthors) {\n return true;\n }\n \n console.log(Filtered out: ${paper.title || 'No title'} - Missing abstract or metadata);\n return false;\n});\n\nconsole.log(${qualityPapers.length} papers have quality abstracts and metadata);\n\n// Select top papers with good abstracts\nconst maxPapers = 10;\nconst selectedPapers = qualityPapers.slice(0, maxPapers);\n\nconsole.log(Selected top ${selectedPapers.length} papers for analysis);\n\n// Return selected papers as individual items\nreturn selectedPapers.map(item => ({\n json: item.json,\n binary: {}\n}));”
},
“id”: “a55d7785-4e64-4f9a-ad84-9eba8a6859e0”,
“name”: “Select Top Papers”,
“type”: “n8n-nodes-base.code”,
“position”: [
-384,
1792
],
“typeVersion”: 1
},
{
“parameters”: {
“jsCode”: “// n8n Code Node (v2) — builds the LLM prompt and a structured papers array\n\nconst inputItems = $input.all();\n\nconsole.log(Processing ${inputItems.length} paper items directly);\n\nlet paperList = ‘’;\nlet paperCount = 0;\n\n// Each incoming item is a single paper\ninputItems.forEach((item, itemIndex) => {\n const paper = item.json;\n\n console.log(Processing paper ${itemIndex + 1}: ${paper.title || 'No title'});\n\n if (paper && paper.title) {\n paperCount++;\n\n // Normalize authors\n let authorList = ‘Unknown Authors’;\n if (paper.authors && Array.isArray(paper.authors)) {\n authorList = paper.authors.map(author => {\n if (typeof author === ‘string’) return author;\n if (author?.name) return author.name;\n return ‘Unknown Author’;\n }).join(', ‘);\n } else if (typeof paper.authors === ‘string’) {\n authorList = paper.authors;\n }\n\n paperList += **Paper ${paperCount}:**\\n**Title:** ${paper.title || 'Unknown'}\\n**Authors:** ${authorList}\\n**Year:** ${paper.year || 'Unknown'}\\n**Journal:** ${paper.journal || 'Unknown'}\\n**Citations:** ${paper.totalCitations || paper.citations || 'Unknown'}\\n**DOI:** ${paper.doi || 'Not available'}\\n**Provider:** ${paper.provider || 'Unknown'}\\n**Abstract:** ${paper.abstract || 'Abstract not available'}\\n**URL:** ${paper.providerURL || paper.url || 'URL not available'}\\n---\\n\\n;\n } else {\n console.log(Skipped item ${itemIndex + 1} - missing title or invalid paper data);\n }\n});\n\nconsole.log(Found ${paperCount} valid papers out of ${inputItems.length} items);\n\nconst fullPrompt = Create a comprehensive literature review analysis based on the following research papers and their abstracts:\\n\\n${paperList}\\n\\n**ANALYSIS INSTRUCTIONS:**\\nBased on these ${paperCount} research papers, provide a comprehensive literature review with the following sections:\\n\\n## 1. Executive Summary (2-3 paragraphs)\\nSummarize the key trends, findings, and contributions across all papers.\\n\\n## 2. Thematic Analysis\\nIdentify and discuss the major themes and research directions, such as:\\n- Clinical decision support systems\\n- Medical imaging and diagnostics\\n- Drug discovery and development\\n- Patient monitoring and wearables\\n- Electronic health records analysis\\n- Predictive modeling for health outcomes\\n\\n## 3. Methodological Approaches\\nAnalyze the common machine learning methods and techniques used:\\n- Deep learning approaches (CNNs, RNNs, Transformers)\\n- Traditional ML methods (SVM, Random Forest, etc.)\\n- Ensemble methods\\n- Data preprocessing and feature engineering\\n- Validation and evaluation metrics\\n\\n## 4. Key Findings and Contributions\\nHighlight the most significant discoveries, innovations, and clinical impacts demonstrated across the papers.\\n\\n## 5. Clinical Applications and Impact\\nDiscuss real-world applications and their potential impact on healthcare delivery, patient outcomes, and clinical workflows.\\n\\n## 6. Challenges and Limitations\\nIdentify common challenges mentioned across papers:\\n- Data privacy and security\\n- Regulatory approval processes\\n- Model interpretability and explainability\\n- Data quality and standardization\\n- Integration with existing healthcare systems\\n\\n## 7. Research Gaps and Future Directions\\nBased on the collective insights, identify areas needing further investigation and recommend next steps for the field.\\n\\n## 8. Conclusion\\nProvide a synthesis of how these papers collectively advance the field.\\n\\n**REQUIREMENTS:**\\n- Focus on synthesizing insights ACROSS papers rather than summarizing each individually\\n- Identify patterns, trends, and connections between different studies\\n- Highlight contradictions or different approaches to similar problems\\n- Ensure the analysis is suitable for researchers, clinicians, and healthcare technology professionals\\n- Use clear, professional academic language\\n- Cite papers by number (Paper 1, Paper 2, etc.) when referencing specific findings;\n\nconst detectedQuery = inputItems.length > 0 && inputItems[0].json.originalSearchQuery\n ? inputItems[0].json.originalSearchQuery\n : ‘research topic’;\n\nconsole.log(Detected research query: \"${detectedQuery}\");\n\nconst structuredPapers = inputItems.map((item, index) => {\n const p = item.json;\n\n let authorList = ‘Unknown Authors’;\n if (p.authors && Array.isArray(p.authors)) {\n authorList = p.authors.map(a => {\n if (typeof a === ‘string’) return a;\n if (a?.name) return a.name;\n return ‘Unknown Author’;\n }).join(’, ');\n } else if (typeof p.authors === ‘string’) {\n authorList = p.authors;\n }\n\n return {\n id: index + 1,\n title: p.title || ‘Unknown Title’,\n authors: authorList,\n year: p.year || ‘Unknown Year’,\n journal: p.journal || ‘Unknown Journal’,\n citations: p.totalCitations || p.citations || 0,\n abstract: p.abstract || ‘No abstract available’,\n doi: p.doi || ‘’,\n url: p.providerURL || p.url || ‘’,\n provider: p.provider || ‘Unknown’,\n originalSearchQuery: detectedQuery,\n };\n});\n\nreturn {\n prompt: fullPrompt,\n paperCount,\n paperList,\n papers: structuredPapers,\n totalPapers: structuredPapers.length,\n searchQuery: detectedQuery,\n};”
},
“type”: “n8n-nodes-base.code”,
“typeVersion”: 2,
“position”: [
-208,
1776
],
“id”: “93077724-87c2-4621-9769-c79f4dff70ee”,
“name”: “Code”
},
{
“parameters”: {
“modelId”: {
“__rl”: true,
“value”: “gpt-5-mini”,
“mode”: “list”,
“cachedResultName”: “GPT-5-MINI”
},
“messages”: {
“values”: [
{
“content”: “={{ $json.prompt }}”
}
]
},
“options”: {}
},
“type”: “@n8n/n8n-nodes-langchain.openAi”,
“typeVersion”: 1.8,
“position”: [
32,
1600
],
“id”: “03a08dc9-3560-45f0-98bf-3efca972fbec”,
“name”: “Message a model”,
“credentials”: {
“openAiApi”: {
“id”: “xjEGLjNNtlk2c7OM”,
“name”: “OpenAi account”
}
}
},
{
“parameters”: {
“mode”: “combine”,
“combinationMode”: “mergeByPosition”,
“options”: {}
},
“id”: “8d2c141a-3b57-40dc-bcc0-8d22214d3a4d”,
“name”: “Merge (Wait for both)”,
“type”: “n8n-nodes-base.merge”,
“typeVersion”: 2,
“position”: [
416,
1792
]
},
{
“parameters”: {
“jsCode”: “// Combine Sections — final version\nconst allItems = $input.all();\n\nlet synthesis = ‘No synthesis available’;\nlet paperData = null;\nlet searchQuery = ‘research’;\n\n// 1) Pick up the LLM output\nconst synthItem = allItems.find(i =>\n i.json?.response || i.json?.content || i.json?.message?.content || typeof i.json === ‘string’\n);\nif (synthItem) {\n synthesis =\n synthItem.json.response ||\n synthItem.json.content ||\n synthItem.json.message?.content ||\n (typeof synthItem.json === ‘string’ ? synthItem.json : ‘No synthesis available’);\n}\n\n// 2) Pick up structured papers\nconst dataItem = allItems.find(i => Array.isArray(i.json?.papers));\nif (dataItem) {\n paperData = dataItem.json;\n searchQuery = paperData.searchQuery || ‘research’;\n}\n\n// 3) Replace “Paper X” with links whose text is the full title\nlet enhanced = String(synthesis);\nif (paperData?.papers) {\n paperData.papers.forEach((paper, idx) => {\n const num = idx + 1;\n const url = paper.url || ‘’;\n if (!url) return;\n\n const title = paper.title || Paper ${num};\n const anchor = <a href=\"${url}\" target=\"_blank\" style=\"color:#3498db;text-decoration:none;font-weight:bold;\">${title}</a>;\n\n // (Paper X) → (Title link)\n enhanced = enhanced.replace(\n new RegExp(\\\\((?:Paper|paper)\\\\s+${num}\\\\), ‘g’),\n (${anchor})\n );\n\n // Standalone “Paper X” → Title link\n enhanced = enhanced.replace(\n new RegExp(\\\\b(Paper|paper)\\\\s+${num}(?=\\\\b|'s\\\\b|[.,:;)]|\\\\s), ‘gi’),\n anchor\n );\n });\n\n // Link every number inside “Papers 1, 2 and 3 …” lists\n // Match from "Papers " up to ., newline, ), ;, :, or end-of-string\n enhanced = enhanced.replace(\n /Papers?\s+([^\n\.\)\;\:]?)(?=[\.\n\)\;\:]|$)/gi,\n (whole, list) => {\n const updated = list.replace(/\b\d+\b/g, (nStr) => {\n const idx = parseInt(nStr, 10) - 1;\n const p = paperData.papers[idx];\n if (!p || !p.url) return nStr;\n const t = p.title || Paper ${nStr};\n return <a href=\"${p.url}\" target=\"_blank\" style=\"color:#3498db;font-weight:bold;\">${t}</a>;\n });\n return whole.replace(list, updated);\n }\n );\n}\n\n// 4) Escape stray \u to avoid Unicode parse errors when the JSON is serialized\nconst safeSynthesis = enhanced.replace(/\\u/g, ‘\\\\u’);\n\n// 5) Markdown-ish → HTML (and convert newlines)\nfunction formatSynthesisAsHtml(text) {\n return String(text)\n .replace(/## (.)/g, ‘$1’)\n .replace(/### (.)/g, ‘$1’)\n .replace(/\\(.?)\\/g, ‘$1’)\n .replace(/\n\s*\n/g, ‘’) // paragraph breaks (2+ newlines)\n .replace(/\n/g, ‘’) // single newlines\n .replace(/^/, ‘’)\n .replace(/$/, ‘’)\n .replace(/<h/g, ‘<h’)\n .replace(/<\/h([1-6])><\/p>/g, ‘</h$1>’);\n}\n\n// 6) Build the page\nconst titleCaseQuery = searchQuery.split(’ ‘)\n .map(w => w.charAt(0).toUpperCase() + w.slice(1))\n .join(’ ');\n\nconst literatureReview = {\n title: Literature Review: ${titleCaseQuery},\n subtitle: ‘A Comprehensive Analysis of Recent Research Trends and Applications’,\n generatedAt: new Date().toISOString(),\n totalPapers: paperData ? paperData.papers.length : 0,\n searchQuery,\n dateRange: ‘2020-2025’,\n methodology: ‘Analysis based on abstracts and metadata from academic databases including PubMed, arXiv, Google Scholar, and Semantic Scholar. Papers were selected based on citation count and abstract quality.’,\n synthesis: safeSynthesis,\n papers: paperData ? paperData.papers : ,\n};\n\nconst papers = Array.isArray(literatureReview.papers) ? literatureReview.papers : ;\nconst papersSummary = {\n totalCitations: papers.reduce((s, p) => s + (p.citations || 0), 0),\n averageCitations: papers.length ? Math.round(papers.reduce((s, p) => s + (p.citations || 0), 0) / papers.length) : 0,\n yearDistribution: papers.reduce((acc, p) => { const y = p.year || ‘Unknown’; acc[y] = (acc[y] || 0) + 1; return acc; }, {}),\n providerDistribution: papers.reduce((acc, p) => { const k = p.provider || ‘Unknown’; acc[k] = (acc[k] || 0) + 1; return acc; }, {}),\n};\n\n// 7) HTML (regular template literal — no “\n” anywhere)\nconst htmlContent = \n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>${literatureReview.title}</title>\n <style>\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; max-width: 1000px; margin: 0 auto; padding: 20px; color: #333; background-color: #ffffff; }\n h1 { color: #2c3e50; border-bottom: 3px solid #3498db; padding-bottom: 10px; margin-bottom: 5px; }\n h2 { color: #34495e; border-bottom: 2px solid #ecf0f1; padding-bottom: 8px; margin-top: 30px; }\n h3 { color: #2c3e50; margin-top: 25px; }\n .subtitle { font-size: 1.2em; color: #7f8c8d; font-style: italic; margin-bottom: 30px; }\n .meta-info { background-color: #f8f9fa; border-left: 4px solid #3498db; padding: 15px; margin: 20px 0; border-radius: 4px; }\n .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin: 20px 0; }\n .stat-box { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 15px; border-radius: 8px; text-align: center; }\n .stat-number { font-size: 2em; font-weight: bold; display: block; }\n .paper-item { background-color: #ffffff; border: 1px solid #e1e8ed; border-radius: 8px; padding: 20px; margin: 15px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }\n .paper-title { font-size: 1.1em; font-weight: bold; color: #2c3e50; margin-bottom: 10px; }\n .paper-meta { color: #7f8c8d; font-size: 0.9em; margin-bottom: 10px; }\n .paper-abstract { color: #555; font-style: italic; border-left: 3px solid #3498db; padding-left: 15px; margin-top: 10px; }\n .citation-count { background-color: #e74c3c; color: white; padding: 3px 8px; border-radius: 12px; font-size: 0.8em; font-weight: bold; }\n .provider-tag { background-color: #95a5a6; color: white; padding: 2px 6px; border-radius: 4px; font-size: 0.7em; text-transform: uppercase; }\n .synthesis-content { background-color: #f8f9fa; border-radius: 8px; padding: 25px; margin: 20px 0; }\n a { color: #3498db; text-decoration: none; font-weight: bold; }\n a:hover { text-decoration: underline; color: #2980b9; }\n .synthesis-content a { background-color: #e3f2fd; padding: 2px 6px; border-radius: 4px; border: 1px solid #3498db; }\n .synthesis-content a:hover { background-color: #bbdefb; }\n .distribution-item { display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px solid #ecf0f1; }\n .methodology { background-color: #fff3cd; border: 1px solid #ffeaa7; border-radius: 4px; padding: 15px; margin: 20px 0; }\n .footer { margin-top: 40px; padding-top: 20px; border-top: 2px solid #ecf0f1; text-align: center; color: #7f8c8d; font-size: 0.9em; }\n </style>\n</head>\n<body>\n <h1>${literatureReview.title}</h1>\n <div class=\"subtitle\">${literatureReview.subtitle}</div>\n\n <div class=\"meta-info\">\n <strong>Generated:</strong> ${new Date(literatureReview.generatedAt).toLocaleDateString()}<br>\n <strong>Search Query:</strong> ${literatureReview.searchQuery}<br>\n <strong>Date Range:</strong> ${literatureReview.dateRange}\n </div>\n\n <div class=\"stats-grid\">\n <div class=\"stat-box\"><span class=\"stat-number\">${literatureReview.totalPapers}</span>Papers Analyzed</div>\n <div class=\"stat-box\"><span class=\"stat-number\">${papersSummary.totalCitations.toLocaleString()}</span>Total Citations</div>\n <div class=\"stat-box\"><span class=\"stat-number\">${papersSummary.averageCitations}</span>Avg Citations/Paper</div>\n </div>\n\n <div class=\"methodology\">\n <h3>Methodology</h3>\n <p>${literatureReview.methodology}</p>\n </div>\n\n <h2>Dataset Overview</h2>\n\n <h3>Year Distribution</h3>\n ${Object.entries(papersSummary.yearDistribution)\n .sort(([a],[b]) => b.localeCompare(a))\n .map(([year, count]) =>
${year}${count} papers)\n .join('')}\n\n <h3>Source Distribution</h3>\n ${Object.entries(papersSummary.providerDistribution)\n .sort(([,a],[,b]) => b - a)\n .map(([provider, count]) =>
${provider}${count} papers)\n .join('')}\n\n <h2>Literature Review Analysis</h2>\n <div class=\"synthesis-content\">\n ${formatSynthesisAsHtml(literatureReview.synthesis)}\n </div>\n\n <h2>Papers Included in Analysis</h2>\n ${papers\n .slice()\n .sort((a,b) => (b.citations||0) - (a.citations||0))\n .map(p => \n
\n
\n ${p.title}\n \n
\n Authors: ${p.authors}\n Publication: ${p.journal} (${p.year})\n ${(p.citations || 0).toLocaleString()} citations\n ${p.provider}\n ${p.doi ? <br><strong>DOI:</strong> ${p.doi} : ‘’}\n URL: View Paper\n \n
Abstract: ${p.abstract}\n \n ).join('')}\n\n <div class=\"footer\">\n <p><em>This literature review was generated automatically using n8n workflow automation and AI.</em></p>\n <p>Generated on ${new Date().toLocaleDateString()} at ${new Date().toLocaleTimeString()}</p>\n </div>\n</body>\n</html>\n;\n\nreturn {\n json: {\n title: literatureReview.title,\n totalPapers: literatureReview.totalPapers,\n searchQuery: literatureReview.searchQuery,\n htmlContent,\n emailSubject: ${literatureReview.title} - ${new Date().toLocaleDateString()},\n emailPreview: Comprehensive analysis of ${literatureReview.totalPapers} research papers on \"${searchQuery}\" with ${papersSummary.totalCitations.toLocaleString()} total citations,\n },\n};\n”
},
“id”: “42723803-a139-4e1b-a7b5-6cb543b52b2c”,
“name”: “Combine Sections”,
“type”: “n8n-nodes-base.code”,
“position”: [
640,
1792
],
“typeVersion”: 1
},
{
“parameters”: {
“sendTo”: “[email protected]”,
“subject”: “={{ $json.emailSubject }}”,
“message”: “={{ $json.htmlContent }}”,
“options”: {}
},
“id”: “2492b0b1-0b2b-4d4a-9f30-b10d6f3c8812”,
“name”: “Send Email Report”,
“type”: “n8n-nodes-base.gmail”,
“position”: [
864,
1792
],
“typeVersion”: 2,
“webhookId”: “d044e9d8-ac7c-4f49-829b-e2735d5c6c9b”,
“credentials”: {
“gmailOAuth2”: {
“id”: “lepdJBYpmcE2dlhT”,
“name”: “Gmail account”
}
}
}
],
“connections”: {
“When clicking ‘Execute workflow’”: {
“main”: [
[
{
“node”: “PDF Vector - Search Papers”,
“type”: “main”,
“index”: 0
}
]
]
},
“PDF Vector - Search Papers”: {
“main”: [
[
{
“node”: “Sort by Citations”,
“type”: “main”,
“index”: 0
}
]
]
},
“Sort by Citations”: {
“main”: [
[
{
“node”: “Select Top Papers”,
“type”: “main”,
“index”: 0
}
]
]
},
“Select Top Papers”: {
“main”: [
[
{
“node”: “Code”,
“type”: “main”,
“index”: 0
}
]
]
},
“Code”: {
“main”: [
[
{
“node”: “Message a model”,
“type”: “main”,
“index”: 0
},
{
“node”: “Merge (Wait for both)”,
“type”: “main”,
“index”: 1
}
]
]
},
“Message a model”: {
“main”: [
[
{
“node”: “Merge (Wait for both)”,
“type”: “main”,
“index”: 0
}
]
]
},
“Merge (Wait for both)”: {
“main”: [
[
{
“node”: “Combine Sections”,
“type”: “main”,
“index”: 0
}
]
]
},
“Combine Sections”: {
“main”: [
[
{
“node”: “Send Email Report”,
“type”: “main”,
“index”: 0
}
]
]
}
},
“pinData”: {},
“meta”: {
“templateId”: “7354”,
“templateCredsSetupCompleted”: true,
“instanceId”: “ddc8b19b7fa20047424f31044d87873eaee7496fb5be5378767f1db53a15213a”
}
}
}