@ferchy/n8n-nodes-aimc-toolkit 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -5,12 +5,13 @@ AIMC Toolkit is a community node package for n8n with focused nodes:
|
|
|
5
5
|
- **AIMC Code**: run JavaScript with a practical toolbox of libraries.
|
|
6
6
|
- **AIMC Media**: FFmpeg-powered media operations without extra glue nodes.
|
|
7
7
|
- **AIMC TTS**: CPU-friendly text-to-speech using Piper.
|
|
8
|
-
- **AIMC Docling**: OCR/document extraction using Docling.
|
|
9
8
|
|
|
10
9
|
## Why I Built This
|
|
11
10
|
|
|
12
11
|
n8n is powerful, but real workflows often need basic utilities (validation, parsing, HTTP) and media tasks (convert, compress, merge). I built AIMC Toolkit to remove the busywork and keep workflows short, readable, and fast.
|
|
13
12
|
|
|
13
|
+
**Inspiration**: The original idea was sparked by Kenkaii’s SuperCode. I’m grateful for that work and built AIMC Toolkit as my own version, tailored to my needs and expanded with improvements over time.
|
|
14
|
+
|
|
14
15
|
## Who This Is For
|
|
15
16
|
|
|
16
17
|
- Automation builders who want fewer nodes and faster iterations.
|
|
@@ -24,7 +25,6 @@ n8n is powerful, but real workflows often need basic utilities (validation, pars
|
|
|
24
25
|
- **Media ready**: convert, compress, merge, and inspect media in one place.
|
|
25
26
|
- **Practical libraries**: parsing, validation, dates, and web utilities built in.
|
|
26
27
|
- **Local voice**: generate speech on a CPU server without paid APIs.
|
|
27
|
-
- **Document extraction**: pull clean text/markdown from PDFs and scans.
|
|
28
28
|
|
|
29
29
|
## Installation
|
|
30
30
|
|
|
@@ -76,6 +76,7 @@ brew install ffmpeg
|
|
|
76
76
|
- Run once for all items or once per item.
|
|
77
77
|
- Access libraries as globals (`axios`, `_`, `zod`) or via `libs`.
|
|
78
78
|
- Built-in helpers (`utils.now`, `utils.safeJson`, `utils.toArray`).
|
|
79
|
+
- **AIMC Connect Mode**: optional AI connectors for LLMs, tools, memory, and more.
|
|
79
80
|
|
|
80
81
|
**Example: normalize data**
|
|
81
82
|
```javascript
|
|
@@ -107,6 +108,20 @@ result = {
|
|
|
107
108
|
}
|
|
108
109
|
```
|
|
109
110
|
|
|
111
|
+
**AIMC Connect Mode (AI)**
|
|
112
|
+
Enable **AIMC Connect Mode** to attach AI connectors. When enabled, your code can use:
|
|
113
|
+
|
|
114
|
+
- `ai` (object with all connectors)
|
|
115
|
+
- `aiModel`, `aiTools`, `aiMemory`, `aiVectorStore`, `aiChain`, `aiDocument`
|
|
116
|
+
|
|
117
|
+
Example:
|
|
118
|
+
```javascript
|
|
119
|
+
if (aiModel) {
|
|
120
|
+
const response = await aiModel.invoke('Summarize this input.');
|
|
121
|
+
return { summary: response };
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
110
125
|
### AIMC Media
|
|
111
126
|
|
|
112
127
|
**Operations**
|
|
@@ -175,34 +190,6 @@ Output Mode: Binary
|
|
|
175
190
|
- Use **Output Mode = File Path** for large audio.
|
|
176
191
|
- For custom voice storage, set **Data Dir**.
|
|
177
192
|
|
|
178
|
-
### AIMC Docling (OCR + Document Parsing)
|
|
179
|
-
|
|
180
|
-
**What it does**
|
|
181
|
-
Extracts clean text/markdown from PDFs, images, and common document formats using Docling.
|
|
182
|
-
|
|
183
|
-
**Requirements**
|
|
184
|
-
- Python 3 installed
|
|
185
|
-
- Docling installed: `pip install docling` (or enable Auto-install)
|
|
186
|
-
|
|
187
|
-
**Input modes**
|
|
188
|
-
- Binary (from n8n)
|
|
189
|
-
- File Path (local file)
|
|
190
|
-
- URL (public file URL)
|
|
191
|
-
|
|
192
|
-
**Output formats**
|
|
193
|
-
- Markdown (default)
|
|
194
|
-
- JSON (structured output when available)
|
|
195
|
-
- Plain Text (markdown stripped)
|
|
196
|
-
|
|
197
|
-
**Auto-install**
|
|
198
|
-
Turn on **Auto-install Docling** to run `pip install docling` the first time the node runs.
|
|
199
|
-
Python must already be installed in your n8n environment.
|
|
200
|
-
|
|
201
|
-
**Example**
|
|
202
|
-
```
|
|
203
|
-
Input Mode: Binary
|
|
204
|
-
Output Format: Markdown
|
|
205
|
-
```
|
|
206
193
|
|
|
207
194
|
## Library Reference (AIMC Code)
|
|
208
195
|
|
|
@@ -147,6 +147,7 @@ function normalizeResult(result, fallbackItems) {
|
|
|
147
147
|
];
|
|
148
148
|
}
|
|
149
149
|
function buildSandbox(params) {
|
|
150
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
150
151
|
const cache = {};
|
|
151
152
|
const sandbox = {
|
|
152
153
|
items: params.items,
|
|
@@ -161,6 +162,16 @@ function buildSandbox(params) {
|
|
|
161
162
|
: params.items.map((entry) => entry.json),
|
|
162
163
|
},
|
|
163
164
|
params: params.nodeParams,
|
|
165
|
+
ai: params.aiContext || {},
|
|
166
|
+
aiModel: (_a = params.aiContext) === null || _a === void 0 ? void 0 : _a.languageModel,
|
|
167
|
+
aiTools: (_b = params.aiContext) === null || _b === void 0 ? void 0 : _b.tools,
|
|
168
|
+
aiMemory: (_c = params.aiContext) === null || _c === void 0 ? void 0 : _c.memory,
|
|
169
|
+
aiChain: (_d = params.aiContext) === null || _d === void 0 ? void 0 : _d.chain,
|
|
170
|
+
aiDocument: (_e = params.aiContext) === null || _e === void 0 ? void 0 : _e.document,
|
|
171
|
+
aiEmbedding: (_f = params.aiContext) === null || _f === void 0 ? void 0 : _f.embedding,
|
|
172
|
+
aiOutputParser: (_g = params.aiContext) === null || _g === void 0 ? void 0 : _g.outputParser,
|
|
173
|
+
aiTextSplitter: (_h = params.aiContext) === null || _h === void 0 ? void 0 : _h.textSplitter,
|
|
174
|
+
aiVectorStore: (_j = params.aiContext) === null || _j === void 0 ? void 0 : _j.vectorStore,
|
|
164
175
|
utils: {
|
|
165
176
|
now: () => new Date().toISOString(),
|
|
166
177
|
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
@@ -294,7 +305,32 @@ class AimcCode {
|
|
|
294
305
|
defaults: {
|
|
295
306
|
name: 'AIMC Code',
|
|
296
307
|
},
|
|
297
|
-
inputs:
|
|
308
|
+
inputs: `={{
|
|
309
|
+
((values, aiMode) => {
|
|
310
|
+
const connectorTypes = {
|
|
311
|
+
'${"ai_chain"}': 'Chain',
|
|
312
|
+
'${"ai_document"}': 'Document',
|
|
313
|
+
'${"ai_embedding"}': 'Embedding',
|
|
314
|
+
'${"ai_languageModel"}': 'Language Model',
|
|
315
|
+
'${"ai_memory"}': 'Memory',
|
|
316
|
+
'${"ai_outputParser"}': 'Output Parser',
|
|
317
|
+
'${"ai_textSplitter"}': 'Text Splitter',
|
|
318
|
+
'${"ai_tool"}': 'Tool',
|
|
319
|
+
'${"ai_vectorStore"}': 'Vector Store',
|
|
320
|
+
'${"main"}': 'Main'
|
|
321
|
+
};
|
|
322
|
+
const baseInputs = [{ displayName: '', type: '${"main"}' }];
|
|
323
|
+
if (aiMode && values) {
|
|
324
|
+
return baseInputs.concat(values.map(value => ({
|
|
325
|
+
type: value.type,
|
|
326
|
+
required: value.required,
|
|
327
|
+
maxConnections: value.maxConnections === -1 ? undefined : value.maxConnections,
|
|
328
|
+
displayName: connectorTypes[value.type] !== 'Main' ? connectorTypes[value.type] : undefined
|
|
329
|
+
})));
|
|
330
|
+
}
|
|
331
|
+
return baseInputs;
|
|
332
|
+
})($parameter.aiConnections?.input, $parameter.aiConnectMode)
|
|
333
|
+
}}`,
|
|
298
334
|
outputs: ['main'],
|
|
299
335
|
properties: [
|
|
300
336
|
{
|
|
@@ -333,6 +369,67 @@ class AimcCode {
|
|
|
333
369
|
maxValue: 300,
|
|
334
370
|
},
|
|
335
371
|
},
|
|
372
|
+
{
|
|
373
|
+
displayName: 'AIMC Connect Mode',
|
|
374
|
+
name: 'aiConnectMode',
|
|
375
|
+
type: 'boolean',
|
|
376
|
+
default: false,
|
|
377
|
+
description: 'Enable AI connector inputs for models, tools, memory, and more.',
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
displayName: 'AI Connections',
|
|
381
|
+
name: 'aiConnections',
|
|
382
|
+
placeholder: 'Add AI Connection',
|
|
383
|
+
type: 'fixedCollection',
|
|
384
|
+
displayOptions: {
|
|
385
|
+
show: {
|
|
386
|
+
aiConnectMode: [true],
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
typeOptions: {
|
|
390
|
+
multipleValues: true,
|
|
391
|
+
},
|
|
392
|
+
default: {},
|
|
393
|
+
options: [
|
|
394
|
+
{
|
|
395
|
+
name: 'input',
|
|
396
|
+
displayName: 'Input',
|
|
397
|
+
values: [
|
|
398
|
+
{
|
|
399
|
+
displayName: 'Type',
|
|
400
|
+
name: 'type',
|
|
401
|
+
type: 'options',
|
|
402
|
+
options: [
|
|
403
|
+
{ name: 'Chain', value: 'ai_chain' },
|
|
404
|
+
{ name: 'Document', value: 'ai_document' },
|
|
405
|
+
{ name: 'Embedding', value: 'ai_embedding' },
|
|
406
|
+
{ name: 'Language Model', value: 'ai_languageModel' },
|
|
407
|
+
{ name: 'Memory', value: 'ai_memory' },
|
|
408
|
+
{ name: 'Output Parser', value: 'ai_outputParser' },
|
|
409
|
+
{ name: 'Text Splitter', value: 'ai_textSplitter' },
|
|
410
|
+
{ name: 'Tool', value: 'ai_tool' },
|
|
411
|
+
{ name: 'Vector Store', value: 'ai_vectorStore' },
|
|
412
|
+
],
|
|
413
|
+
default: 'ai_languageModel',
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
displayName: 'Max Connections',
|
|
417
|
+
name: 'maxConnections',
|
|
418
|
+
type: 'number',
|
|
419
|
+
default: 1,
|
|
420
|
+
description: 'Set -1 for unlimited connections',
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
displayName: 'Required',
|
|
424
|
+
name: 'required',
|
|
425
|
+
type: 'boolean',
|
|
426
|
+
default: false,
|
|
427
|
+
description: 'Whether this connection is required',
|
|
428
|
+
},
|
|
429
|
+
],
|
|
430
|
+
},
|
|
431
|
+
],
|
|
432
|
+
},
|
|
336
433
|
{
|
|
337
434
|
displayName: 'JavaScript Code',
|
|
338
435
|
name: 'code',
|
|
@@ -410,8 +507,38 @@ class AimcCode {
|
|
|
410
507
|
const language = this.getNodeParameter('language', 0, 'javascript');
|
|
411
508
|
const mode = this.getNodeParameter('mode', 0, 'runOnceForAllItems');
|
|
412
509
|
const timeoutSeconds = this.getNodeParameter('timeoutSeconds', 0, 30);
|
|
510
|
+
const aiConnectMode = this.getNodeParameter('aiConnectMode', 0, false);
|
|
413
511
|
const timeoutMs = Math.max(1, timeoutSeconds) * 1000;
|
|
414
512
|
const nodeParams = this.getNode().parameters;
|
|
513
|
+
const aiContext = {};
|
|
514
|
+
const fetchAi = async (type) => {
|
|
515
|
+
if (!aiConnectMode) {
|
|
516
|
+
return undefined;
|
|
517
|
+
}
|
|
518
|
+
const getter = this
|
|
519
|
+
.getInputConnectionData;
|
|
520
|
+
if (!getter) {
|
|
521
|
+
return undefined;
|
|
522
|
+
}
|
|
523
|
+
try {
|
|
524
|
+
const value = await getter(type, 0);
|
|
525
|
+
return Array.isArray(value) ? value[0] : value;
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
return undefined;
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
if (aiConnectMode) {
|
|
532
|
+
aiContext.chain = await fetchAi('ai_chain');
|
|
533
|
+
aiContext.document = await fetchAi('ai_document');
|
|
534
|
+
aiContext.embedding = await fetchAi('ai_embedding');
|
|
535
|
+
aiContext.languageModel = await fetchAi('ai_languageModel');
|
|
536
|
+
aiContext.memory = await fetchAi('ai_memory');
|
|
537
|
+
aiContext.outputParser = await fetchAi('ai_outputParser');
|
|
538
|
+
aiContext.textSplitter = await fetchAi('ai_textSplitter');
|
|
539
|
+
aiContext.tools = await fetchAi('ai_tool');
|
|
540
|
+
aiContext.vectorStore = await fetchAi('ai_vectorStore');
|
|
541
|
+
}
|
|
415
542
|
if (language === 'python') {
|
|
416
543
|
const pythonCode = this.getNodeParameter('pythonCode', 0, '');
|
|
417
544
|
if (!pythonCode.trim()) {
|
|
@@ -477,6 +604,7 @@ class AimcCode {
|
|
|
477
604
|
item,
|
|
478
605
|
mode,
|
|
479
606
|
nodeParams,
|
|
607
|
+
aiContext,
|
|
480
608
|
});
|
|
481
609
|
const result = await runCode(sandbox);
|
|
482
610
|
const normalized = normalizeResult(result, [item]);
|
|
@@ -488,6 +616,7 @@ class AimcCode {
|
|
|
488
616
|
items,
|
|
489
617
|
mode,
|
|
490
618
|
nodeParams,
|
|
619
|
+
aiContext,
|
|
491
620
|
});
|
|
492
621
|
const result = await runCode(sandbox);
|
|
493
622
|
const normalized = normalizeResult(result, items);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ferchy/n8n-nodes-aimc-toolkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "AIMC Toolkit nodes for n8n: code execution and media operations.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ferchy",
|
|
@@ -29,8 +29,7 @@
|
|
|
29
29
|
"nodes": [
|
|
30
30
|
"dist/nodes/AimcCode/AimcCode.node.js",
|
|
31
31
|
"dist/nodes/AimcMedia/AimcMedia.node.js",
|
|
32
|
-
"dist/nodes/AimcTts/AimcTts.node.js"
|
|
33
|
-
"dist/nodes/AimcDocling/AimcDocling.node.js"
|
|
32
|
+
"dist/nodes/AimcTts/AimcTts.node.js"
|
|
34
33
|
]
|
|
35
34
|
},
|
|
36
35
|
"dependencies": {
|
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.AimcDocling = void 0;
|
|
37
|
-
const n8n_workflow_1 = require("n8n-workflow");
|
|
38
|
-
const fs = __importStar(require("fs"));
|
|
39
|
-
const path = __importStar(require("path"));
|
|
40
|
-
const os = __importStar(require("os"));
|
|
41
|
-
const child_process_1 = require("child_process");
|
|
42
|
-
const util_1 = require("util");
|
|
43
|
-
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
|
|
44
|
-
let doclingChecked = null;
|
|
45
|
-
async function ensureDoclingAvailable(pythonPath) {
|
|
46
|
-
if (doclingChecked) {
|
|
47
|
-
if (!doclingChecked.ok) {
|
|
48
|
-
throw new Error(doclingChecked.message || 'Docling not available');
|
|
49
|
-
}
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
try {
|
|
53
|
-
await execFileAsync(pythonPath, ['-c', 'import docling'], {
|
|
54
|
-
timeout: 10000,
|
|
55
|
-
maxBuffer: 1024 * 1024,
|
|
56
|
-
});
|
|
57
|
-
doclingChecked = { ok: true };
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
const message = error instanceof Error && error.message
|
|
61
|
-
? error.message
|
|
62
|
-
: 'Docling not available';
|
|
63
|
-
doclingChecked = { ok: false, message };
|
|
64
|
-
throw new Error(message);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
async function installDocling(pythonPath, packages, timeoutMs) {
|
|
68
|
-
const packageList = packages
|
|
69
|
-
.split(',')
|
|
70
|
-
.map((pkg) => pkg.trim())
|
|
71
|
-
.filter(Boolean);
|
|
72
|
-
if (!packageList.length) {
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
const strategies = [
|
|
76
|
-
['-m', 'pip', 'install', '--user', ...packageList],
|
|
77
|
-
['-m', 'pip', 'install', '--user', '--break-system-packages', ...packageList],
|
|
78
|
-
['-m', 'pip', 'install', '--user', '--force-reinstall', ...packageList],
|
|
79
|
-
];
|
|
80
|
-
for (const args of strategies) {
|
|
81
|
-
try {
|
|
82
|
-
await execFileAsync(pythonPath, args, {
|
|
83
|
-
timeout: timeoutMs,
|
|
84
|
-
maxBuffer: 5 * 1024 * 1024,
|
|
85
|
-
});
|
|
86
|
-
doclingChecked = null;
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
// Try next strategy.
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
throw new Error('Failed to install docling with pip.');
|
|
94
|
-
}
|
|
95
|
-
async function createTempDir() {
|
|
96
|
-
return fs.promises.mkdtemp(path.join(os.tmpdir(), 'aimc-docling-'));
|
|
97
|
-
}
|
|
98
|
-
function safeFileName(name) {
|
|
99
|
-
return name.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
100
|
-
}
|
|
101
|
-
class AimcDocling {
|
|
102
|
-
constructor() {
|
|
103
|
-
this.description = {
|
|
104
|
-
displayName: 'AIMC Docling',
|
|
105
|
-
name: 'aimcDocling',
|
|
106
|
-
icon: 'file:aimc-docling.svg',
|
|
107
|
-
group: ['transform'],
|
|
108
|
-
version: 1,
|
|
109
|
-
description: 'Document extraction using Docling (OCR, PDF, DOCX).',
|
|
110
|
-
defaults: {
|
|
111
|
-
name: 'AIMC Docling',
|
|
112
|
-
},
|
|
113
|
-
inputs: ['main'],
|
|
114
|
-
outputs: ['main'],
|
|
115
|
-
properties: [
|
|
116
|
-
{
|
|
117
|
-
displayName: 'Input Mode',
|
|
118
|
-
name: 'inputMode',
|
|
119
|
-
type: 'options',
|
|
120
|
-
options: [
|
|
121
|
-
{ name: 'Binary', value: 'binary' },
|
|
122
|
-
{ name: 'File Path', value: 'filePath' },
|
|
123
|
-
{ name: 'URL', value: 'url' },
|
|
124
|
-
],
|
|
125
|
-
default: 'binary',
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
displayName: 'Binary Property',
|
|
129
|
-
name: 'binaryProperty',
|
|
130
|
-
type: 'string',
|
|
131
|
-
default: 'data',
|
|
132
|
-
displayOptions: {
|
|
133
|
-
show: {
|
|
134
|
-
inputMode: ['binary'],
|
|
135
|
-
},
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
displayName: 'File Path',
|
|
140
|
-
name: 'inputFilePath',
|
|
141
|
-
type: 'string',
|
|
142
|
-
default: '',
|
|
143
|
-
placeholder: '/path/to/document.pdf',
|
|
144
|
-
displayOptions: {
|
|
145
|
-
show: {
|
|
146
|
-
inputMode: ['filePath'],
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
displayName: 'URL',
|
|
152
|
-
name: 'inputUrl',
|
|
153
|
-
type: 'string',
|
|
154
|
-
default: '',
|
|
155
|
-
placeholder: 'https://example.com/file.pdf',
|
|
156
|
-
displayOptions: {
|
|
157
|
-
show: {
|
|
158
|
-
inputMode: ['url'],
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
displayName: 'Output Format',
|
|
164
|
-
name: 'outputFormat',
|
|
165
|
-
type: 'options',
|
|
166
|
-
options: [
|
|
167
|
-
{ name: 'Markdown', value: 'markdown' },
|
|
168
|
-
{ name: 'JSON', value: 'json' },
|
|
169
|
-
{ name: 'Plain Text', value: 'text' },
|
|
170
|
-
],
|
|
171
|
-
default: 'markdown',
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
displayName: 'Python Path',
|
|
175
|
-
name: 'pythonPath',
|
|
176
|
-
type: 'string',
|
|
177
|
-
default: 'python3',
|
|
178
|
-
description: 'Path to Python binary with docling installed.',
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
displayName: 'Auto-install Docling',
|
|
182
|
-
name: 'autoInstall',
|
|
183
|
-
type: 'boolean',
|
|
184
|
-
default: false,
|
|
185
|
-
description: 'Install docling with pip on first run if missing.',
|
|
186
|
-
},
|
|
187
|
-
{
|
|
188
|
-
displayName: 'Python Packages',
|
|
189
|
-
name: 'pythonPackages',
|
|
190
|
-
type: 'string',
|
|
191
|
-
default: 'docling',
|
|
192
|
-
description: 'Comma-separated packages to install when auto-install is enabled.',
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
displayName: 'Timeout (Seconds)',
|
|
196
|
-
name: 'timeoutSeconds',
|
|
197
|
-
type: 'number',
|
|
198
|
-
default: 120,
|
|
199
|
-
typeOptions: {
|
|
200
|
-
minValue: 30,
|
|
201
|
-
maxValue: 1800,
|
|
202
|
-
},
|
|
203
|
-
},
|
|
204
|
-
],
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
async execute() {
|
|
208
|
-
const items = this.getInputData();
|
|
209
|
-
const results = [];
|
|
210
|
-
for (let index = 0; index < items.length; index++) {
|
|
211
|
-
const item = items[index];
|
|
212
|
-
const inputMode = this.getNodeParameter('inputMode', index);
|
|
213
|
-
const binaryProperty = this.getNodeParameter('binaryProperty', index, 'data');
|
|
214
|
-
const inputFilePath = this.getNodeParameter('inputFilePath', index, '');
|
|
215
|
-
const inputUrl = this.getNodeParameter('inputUrl', index, '');
|
|
216
|
-
const outputFormat = this.getNodeParameter('outputFormat', index, 'markdown');
|
|
217
|
-
const pythonPath = this.getNodeParameter('pythonPath', index, 'python3');
|
|
218
|
-
const autoInstall = this.getNodeParameter('autoInstall', index, false);
|
|
219
|
-
const pythonPackages = this.getNodeParameter('pythonPackages', index, 'docling');
|
|
220
|
-
const timeoutSeconds = this.getNodeParameter('timeoutSeconds', index, 120);
|
|
221
|
-
let tempDir = null;
|
|
222
|
-
let source = '';
|
|
223
|
-
if (inputMode === 'binary') {
|
|
224
|
-
const binary = this.helpers.assertBinaryData(index, binaryProperty);
|
|
225
|
-
const buffer = await this.helpers.getBinaryDataBuffer(index, binaryProperty);
|
|
226
|
-
tempDir = await createTempDir();
|
|
227
|
-
const name = safeFileName(binary.fileName || `document.${binary.fileExtension || 'bin'}`);
|
|
228
|
-
const filePath = path.join(tempDir, name);
|
|
229
|
-
await fs.promises.writeFile(filePath, buffer);
|
|
230
|
-
source = filePath;
|
|
231
|
-
}
|
|
232
|
-
else if (inputMode === 'filePath') {
|
|
233
|
-
if (!inputFilePath) {
|
|
234
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'File Path is required.');
|
|
235
|
-
}
|
|
236
|
-
source = inputFilePath;
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
if (!inputUrl) {
|
|
240
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'URL is required.');
|
|
241
|
-
}
|
|
242
|
-
source = inputUrl;
|
|
243
|
-
}
|
|
244
|
-
try {
|
|
245
|
-
if (autoInstall) {
|
|
246
|
-
await installDocling(pythonPath, pythonPackages, Math.max(30, timeoutSeconds) * 1000);
|
|
247
|
-
}
|
|
248
|
-
await ensureDoclingAvailable(pythonPath);
|
|
249
|
-
}
|
|
250
|
-
catch (error) {
|
|
251
|
-
const message = error instanceof Error ? error.message : 'Docling not available';
|
|
252
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Docling not found. Install with: pip install docling. ${message}`);
|
|
253
|
-
}
|
|
254
|
-
const script = `
|
|
255
|
-
+import json
|
|
256
|
-
+import sys
|
|
257
|
-
+from docling.document_converter import DocumentConverter
|
|
258
|
-
+
|
|
259
|
-
+source = ${JSON.stringify(source)}
|
|
260
|
-
+fmt = ${JSON.stringify(outputFormat)}
|
|
261
|
-
+
|
|
262
|
-
+converter = DocumentConverter()
|
|
263
|
-
+result = converter.convert(source)
|
|
264
|
-
+doc = result.document
|
|
265
|
-
+
|
|
266
|
-
+if fmt == 'markdown':
|
|
267
|
-
+ content = doc.export_to_markdown()
|
|
268
|
-
+elif fmt == 'text':
|
|
269
|
-
+ content = doc.export_to_markdown()
|
|
270
|
-
+ # naive text conversion
|
|
271
|
-
+ content = content.replace('#', '').replace('*', '')
|
|
272
|
-
+else:
|
|
273
|
-
+ try:
|
|
274
|
-
+ content = doc.model_dump()
|
|
275
|
-
+ except Exception:
|
|
276
|
-
+ content = {'text': doc.export_to_markdown()}
|
|
277
|
-
+
|
|
278
|
-
+payload = {"format": fmt, "content": content}
|
|
279
|
-
+print("__AIMC_RESULT__" + json.dumps(payload, default=str))
|
|
280
|
-
+`;
|
|
281
|
-
try {
|
|
282
|
-
const { stdout, stderr } = await execFileAsync(pythonPath, ['-c', script], {
|
|
283
|
-
timeout: Math.max(30, timeoutSeconds) * 1000,
|
|
284
|
-
maxBuffer: 20 * 1024 * 1024,
|
|
285
|
-
});
|
|
286
|
-
const combined = `${stdout}\n${stderr}`.trim();
|
|
287
|
-
const marker = '__AIMC_RESULT__';
|
|
288
|
-
const markerIndex = combined.lastIndexOf(marker);
|
|
289
|
-
if (markerIndex === -1) {
|
|
290
|
-
throw new Error('Docling did not return a result.');
|
|
291
|
-
}
|
|
292
|
-
const jsonPayload = combined.slice(markerIndex + marker.length).trim();
|
|
293
|
-
const payload = JSON.parse(jsonPayload);
|
|
294
|
-
results.push({
|
|
295
|
-
json: {
|
|
296
|
-
...item.json,
|
|
297
|
-
docling: {
|
|
298
|
-
format: payload.format,
|
|
299
|
-
content: payload.content,
|
|
300
|
-
},
|
|
301
|
-
},
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
catch (error) {
|
|
305
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
306
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Docling failed: ${message}`);
|
|
307
|
-
}
|
|
308
|
-
finally {
|
|
309
|
-
if (tempDir) {
|
|
310
|
-
await fs.promises.rm(tempDir, { recursive: true, force: true });
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
return [results];
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
exports.AimcDocling = AimcDocling;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
|
|
2
|
-
<defs>
|
|
3
|
-
<linearGradient id="aimc-docling-bg" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
-
<stop offset="0%" stop-color="#0A1021"/>
|
|
5
|
-
<stop offset="100%" stop-color="#0B1B3A"/>
|
|
6
|
-
</linearGradient>
|
|
7
|
-
<linearGradient id="aimc-docling-glow" x1="0" y1="0" x2="1" y2="1">
|
|
8
|
-
<stop offset="0%" stop-color="#7DD3FC"/>
|
|
9
|
-
<stop offset="100%" stop-color="#0EA5E9"/>
|
|
10
|
-
</linearGradient>
|
|
11
|
-
</defs>
|
|
12
|
-
<rect x="6" y="6" width="52" height="52" rx="10" fill="url(#aimc-docling-bg)"/>
|
|
13
|
-
<path d="M22 16H38L44 22V48H22Z" fill="#0F172A" stroke="#1E3A8A" stroke-width="2"/>
|
|
14
|
-
<path d="M38 16V22H44" stroke="#1E3A8A" stroke-width="2" fill="none"/>
|
|
15
|
-
<rect x="24" y="26" width="16" height="3" rx="1.5" fill="url(#aimc-docling-glow)"/>
|
|
16
|
-
<rect x="24" y="32" width="12" height="3" rx="1.5" fill="url(#aimc-docling-glow)"/>
|
|
17
|
-
<rect x="24" y="38" width="18" height="3" rx="1.5" fill="url(#aimc-docling-glow)"/>
|
|
18
|
-
<circle cx="46" cy="44" r="6" fill="#0B1223" stroke="#38BDF8" stroke-width="2"/>
|
|
19
|
-
<path d="M46 40V44L49 46" stroke="#38BDF8" stroke-width="2" stroke-linecap="round"/>
|
|
20
|
-
</svg>
|