n8n-nodes-vlm 1.0.2

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.
@@ -0,0 +1,16 @@
1
+ import { ISupplyDataFunctions, SupplyData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ /**
3
+ * VLM Complexity Classifier Provider
4
+ *
5
+ * A classifier sub-node that integrates with n8n's Vector Store and AI nodes.
6
+ * Uses VLM servers (like deposium-embeddings-turbov2) to classify document complexity.
7
+ */
8
+ export declare class VLMComplexityClassifier implements INodeType {
9
+ description: INodeTypeDescription;
10
+ /**
11
+ * Supply Data Method
12
+ *
13
+ * Provider nodes use supplyData() to return a classifier provider object
14
+ */
15
+ supplyData(this: ISupplyDataFunctions, _itemIndex: number): Promise<SupplyData>;
16
+ }
@@ -0,0 +1,220 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VLMComplexityClassifier = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const vlm_logic_1 = require("../shared/vlm-logic");
6
+ /**
7
+ * VLM Complexity Classifier Provider
8
+ *
9
+ * A classifier sub-node that integrates with n8n's Vector Store and AI nodes.
10
+ * Uses VLM servers (like deposium-embeddings-turbov2) to classify document complexity.
11
+ */
12
+ class VLMComplexityClassifier {
13
+ constructor() {
14
+ this.description = {
15
+ displayName: 'VLM Complexity Classifier',
16
+ name: 'vlmComplexityClassifier',
17
+ icon: 'file:vlm.svg',
18
+ group: ['transform'],
19
+ version: 1,
20
+ subtitle: 'Document Complexity Classification',
21
+ description: 'Classify document complexity using Vision-Language Models (LOW/HIGH)',
22
+ defaults: {
23
+ name: 'VLM Complexity Classifier',
24
+ },
25
+ codex: {
26
+ categories: ['AI'],
27
+ subcategories: {
28
+ AI: ['Chains', 'Root Nodes', 'Tools'],
29
+ },
30
+ },
31
+ inputs: [],
32
+ outputs: [n8n_workflow_1.NodeConnectionTypes.AiTool],
33
+ outputNames: ['Classifier'],
34
+ credentials: [
35
+ {
36
+ name: 'httpBasicAuth',
37
+ required: false,
38
+ },
39
+ ],
40
+ properties: [
41
+ {
42
+ displayName: 'Server URL',
43
+ name: 'serverUrl',
44
+ type: 'string',
45
+ default: 'http://localhost:8000',
46
+ placeholder: 'http://localhost:8000',
47
+ description: 'URL to your VLM classifier server (e.g., deposium-embeddings-turbov2)',
48
+ required: true,
49
+ },
50
+ {
51
+ displayName: 'Model',
52
+ name: 'model',
53
+ type: 'string',
54
+ default: 'vl-classifier',
55
+ description: 'VLM model to use for classification',
56
+ },
57
+ {
58
+ displayName: 'Classification Strategy',
59
+ name: 'classificationStrategy',
60
+ type: 'options',
61
+ options: [
62
+ {
63
+ name: 'Add Metadata Only',
64
+ value: 'metadata',
65
+ description: 'Add complexity classification as document metadata',
66
+ },
67
+ {
68
+ name: 'Filter Documents',
69
+ value: 'filter',
70
+ description: 'Filter documents based on complexity',
71
+ },
72
+ {
73
+ name: 'Filter + Metadata',
74
+ value: 'both',
75
+ description: 'Both filter and add metadata',
76
+ },
77
+ ],
78
+ default: 'metadata',
79
+ description: 'How to use the classification results',
80
+ },
81
+ {
82
+ displayName: 'Filter Complexity',
83
+ name: 'filterComplexity',
84
+ type: 'options',
85
+ options: [
86
+ {
87
+ name: 'Low Complexity Only',
88
+ value: 'LOW',
89
+ description: 'Keep only simple documents (good for OCR)',
90
+ },
91
+ {
92
+ name: 'High Complexity Only',
93
+ value: 'HIGH',
94
+ description: 'Keep only complex documents (good for VLM)',
95
+ },
96
+ {
97
+ name: 'Both',
98
+ value: 'both',
99
+ description: 'Keep all documents regardless of complexity',
100
+ },
101
+ ],
102
+ default: 'both',
103
+ description: 'Which complexity level to keep when filtering',
104
+ displayOptions: {
105
+ show: {
106
+ classificationStrategy: ['filter', 'both'],
107
+ },
108
+ },
109
+ },
110
+ {
111
+ displayName: 'Additional Options',
112
+ name: 'additionalOptions',
113
+ type: 'collection',
114
+ placeholder: 'Add Option',
115
+ default: {},
116
+ options: [
117
+ {
118
+ displayName: 'Request Timeout (ms)',
119
+ name: 'timeout',
120
+ type: 'number',
121
+ default: 30000,
122
+ typeOptions: {
123
+ minValue: 1000,
124
+ maxValue: 300000,
125
+ },
126
+ description: 'Maximum time per API request',
127
+ },
128
+ ],
129
+ },
130
+ ],
131
+ };
132
+ }
133
+ /**
134
+ * Supply Data Method
135
+ *
136
+ * Provider nodes use supplyData() to return a classifier provider object
137
+ */
138
+ async supplyData(_itemIndex) {
139
+ var _a;
140
+ this.logger.debug('Initializing VLM Complexity Classifier Provider');
141
+ const self = this;
142
+ // Get node parameters
143
+ const serverUrl = this.getNodeParameter('serverUrl', 0).replace(/\/$/, '');
144
+ const model = this.getNodeParameter('model', 0);
145
+ const classificationStrategy = this.getNodeParameter('classificationStrategy', 0);
146
+ const filterComplexity = this.getNodeParameter('filterComplexity', 0, 'both');
147
+ const additionalOptions = this.getNodeParameter('additionalOptions', 0, {});
148
+ const timeout = (_a = additionalOptions.timeout) !== null && _a !== void 0 ? _a : 30000;
149
+ // Verify server has classifier capability
150
+ const status = await (0, vlm_logic_1.checkServerStatus)(this, serverUrl, timeout);
151
+ if (!status.hasClassifier) {
152
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Server at ${serverUrl} does not support VLM classification.\nMake sure /api/status returns has_classifier: true`);
153
+ }
154
+ /**
155
+ * Classifier Provider Object
156
+ */
157
+ const provider = {
158
+ name: 'VLM Complexity Classifier Provider',
159
+ description: `Classifies document complexity using VLM model: ${model}`,
160
+ /**
161
+ * classify() - Main classification method
162
+ */
163
+ classify: async (input) => {
164
+ // Log input for n8n execution tracking
165
+ const { index } = self.addInputData(n8n_workflow_1.NodeConnectionTypes.AiTool, [
166
+ [{ json: { documents: input.documents } }],
167
+ ]);
168
+ const { documents } = input || {};
169
+ // Validate inputs
170
+ const docs = Array.isArray(documents) ? documents : [];
171
+ if (docs.length === 0) {
172
+ self.logger.debug('No documents to classify, returning empty array');
173
+ return [];
174
+ }
175
+ // Convert documents to standard format
176
+ const processedDocs = docs.map((doc, docIndex) => {
177
+ if (doc && typeof doc === 'object') {
178
+ return {
179
+ pageContent: doc.pageContent || doc.text || doc.content || doc.document || JSON.stringify(doc),
180
+ metadata: doc.metadata || {},
181
+ _originalIndex: docIndex,
182
+ ...(doc.image ? { image: doc.image } : {}),
183
+ ...(doc.base64Image ? { base64Image: doc.base64Image } : {}),
184
+ };
185
+ }
186
+ return {
187
+ pageContent: String(doc),
188
+ metadata: {},
189
+ _originalIndex: docIndex,
190
+ };
191
+ });
192
+ self.logger.debug(`Classifying ${processedDocs.length} documents with model: ${model}`);
193
+ try {
194
+ // Classify documents
195
+ const classifiedDocs = await (0, vlm_logic_1.classifyDocuments)(self, {
196
+ serverUrl,
197
+ model,
198
+ documents: processedDocs,
199
+ timeout,
200
+ classificationStrategy,
201
+ filterComplexity,
202
+ });
203
+ self.logger.debug(`Classification complete: ${classifiedDocs.length} documents returned`);
204
+ // Log output for n8n execution tracking
205
+ self.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiTool, index, [
206
+ [{ json: { response: classifiedDocs } }],
207
+ ]);
208
+ return classifiedDocs;
209
+ }
210
+ catch (error) {
211
+ throw new n8n_workflow_1.NodeOperationError(self.getNode(), `VLM classification failed: ${error.message}`);
212
+ }
213
+ },
214
+ };
215
+ return {
216
+ response: provider,
217
+ };
218
+ }
219
+ }
220
+ exports.VLMComplexityClassifier = VLMComplexityClassifier;
@@ -0,0 +1 @@
1
+ export { VLMComplexityClassifier } from './VLMComplexityClassifier.node';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VLMComplexityClassifier = void 0;
4
+ var VLMComplexityClassifier_node_1 = require("./VLMComplexityClassifier.node");
5
+ Object.defineProperty(exports, "VLMComplexityClassifier", { enumerable: true, get: function () { return VLMComplexityClassifier_node_1.VLMComplexityClassifier; } });
@@ -0,0 +1,19 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
2
+ <defs>
3
+ <linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" style="stop-color:#667eea;stop-opacity:1" />
5
+ <stop offset="100%" style="stop-color:#764ba2;stop-opacity:1" />
6
+ </linearGradient>
7
+ </defs>
8
+
9
+ <!-- Vision eye symbol -->
10
+ <ellipse cx="50" cy="40" rx="30" ry="20" fill="url(#grad1)" opacity="0.8"/>
11
+ <circle cx="50" cy="40" r="10" fill="white"/>
12
+ <circle cx="50" cy="40" r="5" fill="#667eea"/>
13
+
14
+ <!-- Document/Language symbol -->
15
+ <rect x="25" y="55" width="50" height="35" rx="3" fill="url(#grad1)"/>
16
+ <line x1="32" y1="65" x2="68" y2="65" stroke="white" stroke-width="2" stroke-linecap="round"/>
17
+ <line x1="32" y1="73" x2="68" y2="73" stroke="white" stroke-width="2" stroke-linecap="round"/>
18
+ <line x1="32" y1="81" x2="55" y2="81" stroke="white" stroke-width="2" stroke-linecap="round"/>
19
+ </svg>
@@ -0,0 +1,16 @@
1
+ import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ /**
3
+ * VLM Complexity Classifier Workflow Node
4
+ *
5
+ * A workflow node that classifies document complexity using Vision-Language Models.
6
+ * Can be used as a standalone tool or chained in workflows.
7
+ */
8
+ export declare class VLMComplexityWorkflow implements INodeType {
9
+ description: INodeTypeDescription;
10
+ /**
11
+ * Execute Method
12
+ *
13
+ * Workflow nodes use execute() to process items through the workflow
14
+ */
15
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
16
+ }
@@ -0,0 +1,303 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VLMComplexityWorkflow = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const vlm_logic_1 = require("../shared/vlm-logic");
6
+ /**
7
+ * VLM Complexity Classifier Workflow Node
8
+ *
9
+ * A workflow node that classifies document complexity using Vision-Language Models.
10
+ * Can be used as a standalone tool or chained in workflows.
11
+ */
12
+ class VLMComplexityWorkflow {
13
+ constructor() {
14
+ this.description = {
15
+ displayName: 'VLM Complexity Workflow',
16
+ name: 'vlmComplexityWorkflow',
17
+ icon: 'file:vlm.svg',
18
+ group: ['transform'],
19
+ version: 1,
20
+ subtitle: '={{$parameter["operation"]}}',
21
+ description: 'Classify document complexity using Vision-Language Models',
22
+ defaults: {
23
+ name: 'VLM Complexity Workflow',
24
+ },
25
+ usableAsTool: true,
26
+ codex: {
27
+ categories: ['AI'],
28
+ subcategories: {
29
+ AI: ['Chains', 'Root Nodes', 'Tools'],
30
+ },
31
+ },
32
+ inputs: [n8n_workflow_1.NodeConnectionTypes.Main],
33
+ outputs: [n8n_workflow_1.NodeConnectionTypes.Main],
34
+ credentials: [
35
+ {
36
+ name: 'httpBasicAuth',
37
+ required: false,
38
+ },
39
+ ],
40
+ properties: [
41
+ {
42
+ displayName: 'Server URL',
43
+ name: 'serverUrl',
44
+ type: 'string',
45
+ default: 'http://localhost:8000',
46
+ placeholder: 'http://localhost:8000',
47
+ description: 'URL to your VLM classifier server (e.g., deposium-embeddings-turbov2)',
48
+ required: true,
49
+ },
50
+ {
51
+ displayName: 'Model',
52
+ name: 'model',
53
+ type: 'string',
54
+ default: 'vl-classifier',
55
+ description: 'VLM model to use for classification',
56
+ },
57
+ {
58
+ displayName: 'Document Source',
59
+ name: 'documentSource',
60
+ type: 'options',
61
+ options: [
62
+ {
63
+ name: 'All Items',
64
+ value: 'items',
65
+ description: 'Process all items from input',
66
+ },
67
+ {
68
+ name: 'From Field',
69
+ value: 'field',
70
+ description: 'Extract documents from a specific field',
71
+ },
72
+ {
73
+ name: 'From Expression',
74
+ value: 'expression',
75
+ description: 'Define documents using an expression',
76
+ },
77
+ ],
78
+ default: 'items',
79
+ description: 'Where to get documents for classification',
80
+ },
81
+ {
82
+ displayName: 'Field Name',
83
+ name: 'fieldName',
84
+ type: 'string',
85
+ default: 'documents',
86
+ description: 'Name of the field containing documents',
87
+ displayOptions: {
88
+ show: {
89
+ documentSource: ['field'],
90
+ },
91
+ },
92
+ },
93
+ {
94
+ displayName: 'Documents Expression',
95
+ name: 'documentsExpression',
96
+ type: 'string',
97
+ default: '={{$json.documents}}',
98
+ description: 'Expression to extract documents',
99
+ displayOptions: {
100
+ show: {
101
+ documentSource: ['expression'],
102
+ },
103
+ },
104
+ },
105
+ {
106
+ displayName: 'Classification Strategy',
107
+ name: 'classificationStrategy',
108
+ type: 'options',
109
+ options: [
110
+ {
111
+ name: 'Add Metadata Only',
112
+ value: 'metadata',
113
+ description: 'Add complexity classification as document metadata',
114
+ },
115
+ {
116
+ name: 'Filter Documents',
117
+ value: 'filter',
118
+ description: 'Filter documents based on complexity',
119
+ },
120
+ {
121
+ name: 'Filter + Metadata',
122
+ value: 'both',
123
+ description: 'Both filter and add metadata',
124
+ },
125
+ ],
126
+ default: 'metadata',
127
+ description: 'How to use the classification results',
128
+ },
129
+ {
130
+ displayName: 'Filter Complexity',
131
+ name: 'filterComplexity',
132
+ type: 'options',
133
+ options: [
134
+ {
135
+ name: 'Low Complexity Only',
136
+ value: 'LOW',
137
+ description: 'Keep only simple documents (good for OCR)',
138
+ },
139
+ {
140
+ name: 'High Complexity Only',
141
+ value: 'HIGH',
142
+ description: 'Keep only complex documents (good for VLM)',
143
+ },
144
+ {
145
+ name: 'Both',
146
+ value: 'both',
147
+ description: 'Keep all documents regardless of complexity',
148
+ },
149
+ ],
150
+ default: 'both',
151
+ description: 'Which complexity level to keep when filtering',
152
+ displayOptions: {
153
+ show: {
154
+ classificationStrategy: ['filter', 'both'],
155
+ },
156
+ },
157
+ },
158
+ {
159
+ displayName: 'Additional Options',
160
+ name: 'additionalOptions',
161
+ type: 'collection',
162
+ placeholder: 'Add Option',
163
+ default: {},
164
+ options: [
165
+ {
166
+ displayName: 'Request Timeout (ms)',
167
+ name: 'timeout',
168
+ type: 'number',
169
+ default: 30000,
170
+ typeOptions: {
171
+ minValue: 1000,
172
+ maxValue: 300000,
173
+ },
174
+ description: 'Maximum time per API request',
175
+ },
176
+ {
177
+ displayName: 'Output Format',
178
+ name: 'outputFormat',
179
+ type: 'options',
180
+ options: [
181
+ {
182
+ name: 'Documents Array',
183
+ value: 'documents',
184
+ description: 'Return classified documents in an array',
185
+ },
186
+ {
187
+ name: 'Separate Items',
188
+ value: 'items',
189
+ description: 'Return each document as a separate item',
190
+ },
191
+ {
192
+ name: 'Original Format',
193
+ value: 'original',
194
+ description: 'Maintain original input structure with classifications added',
195
+ },
196
+ ],
197
+ default: 'documents',
198
+ description: 'How to format the output',
199
+ },
200
+ ],
201
+ },
202
+ ],
203
+ };
204
+ }
205
+ /**
206
+ * Execute Method
207
+ *
208
+ * Workflow nodes use execute() to process items through the workflow
209
+ */
210
+ async execute() {
211
+ var _a, _b;
212
+ const items = this.getInputData();
213
+ this.logger.debug(`VLM Complexity Workflow: Processing ${items.length} input items`);
214
+ // Get node parameters
215
+ const serverUrl = this.getNodeParameter('serverUrl', 0).replace(/\/$/, '');
216
+ const model = this.getNodeParameter('model', 0);
217
+ const documentSource = this.getNodeParameter('documentSource', 0);
218
+ const classificationStrategy = this.getNodeParameter('classificationStrategy', 0);
219
+ const filterComplexity = this.getNodeParameter('filterComplexity', 0, 'both');
220
+ const additionalOptions = this.getNodeParameter('additionalOptions', 0, {});
221
+ const timeout = (_a = additionalOptions.timeout) !== null && _a !== void 0 ? _a : 30000;
222
+ const outputFormat = (_b = additionalOptions.outputFormat) !== null && _b !== void 0 ? _b : 'documents';
223
+ // Verify server has classifier capability
224
+ const status = await (0, vlm_logic_1.checkServerStatus)(this, serverUrl, timeout);
225
+ if (!status.hasClassifier) {
226
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Server at ${serverUrl} does not support VLM classification.\nMake sure /api/status returns has_classifier: true`);
227
+ }
228
+ // Extract documents based on source
229
+ let documents = [];
230
+ if (documentSource === 'items') {
231
+ // Use all items as documents
232
+ documents = items.map((item, index) => ({
233
+ pageContent: JSON.stringify(item.json),
234
+ metadata: { _itemIndex: index },
235
+ }));
236
+ }
237
+ else if (documentSource === 'field') {
238
+ // Extract from field
239
+ const fieldName = this.getNodeParameter('fieldName', 0);
240
+ for (let i = 0; i < items.length; i++) {
241
+ const fieldData = items[i].json[fieldName];
242
+ if (Array.isArray(fieldData)) {
243
+ documents.push(...fieldData);
244
+ }
245
+ else if (fieldData) {
246
+ documents.push(fieldData);
247
+ }
248
+ }
249
+ }
250
+ else if (documentSource === 'expression') {
251
+ // Extract from expression
252
+ for (let i = 0; i < items.length; i++) {
253
+ const expressionValue = this.getNodeParameter('documentsExpression', i);
254
+ if (Array.isArray(expressionValue)) {
255
+ documents.push(...expressionValue);
256
+ }
257
+ else if (expressionValue) {
258
+ documents.push(expressionValue);
259
+ }
260
+ }
261
+ }
262
+ if (documents.length === 0) {
263
+ this.logger.debug('No documents found to classify');
264
+ return [items];
265
+ }
266
+ this.logger.debug(`Classifying ${documents.length} documents with model: ${model}`);
267
+ try {
268
+ // Classify documents
269
+ const classifiedDocs = await (0, vlm_logic_1.classifyDocuments)(this, {
270
+ serverUrl,
271
+ model,
272
+ documents,
273
+ timeout,
274
+ classificationStrategy,
275
+ filterComplexity,
276
+ });
277
+ this.logger.debug(`Classification complete: ${classifiedDocs.length} documents returned`);
278
+ // Format output based on outputFormat option
279
+ if (outputFormat === 'items') {
280
+ // Return each document as a separate item
281
+ return [classifiedDocs.map((doc) => ({ json: doc }))];
282
+ }
283
+ else if (outputFormat === 'original') {
284
+ // Maintain original structure but add classifications
285
+ const outputItems = items.map((item) => ({
286
+ json: {
287
+ ...item.json,
288
+ _classifications: classifiedDocs,
289
+ },
290
+ }));
291
+ return [outputItems];
292
+ }
293
+ else {
294
+ // Default: return documents array
295
+ return [[{ json: { documents: classifiedDocs } }]];
296
+ }
297
+ }
298
+ catch (error) {
299
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `VLM classification failed: ${error.message}`);
300
+ }
301
+ }
302
+ }
303
+ exports.VLMComplexityWorkflow = VLMComplexityWorkflow;
@@ -0,0 +1 @@
1
+ export { VLMComplexityWorkflow } from './VLMComplexityWorkflow.node';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VLMComplexityWorkflow = void 0;
4
+ var VLMComplexityWorkflow_node_1 = require("./VLMComplexityWorkflow.node");
5
+ Object.defineProperty(exports, "VLMComplexityWorkflow", { enumerable: true, get: function () { return VLMComplexityWorkflow_node_1.VLMComplexityWorkflow; } });
@@ -0,0 +1,19 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
2
+ <defs>
3
+ <linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" style="stop-color:#667eea;stop-opacity:1" />
5
+ <stop offset="100%" style="stop-color:#764ba2;stop-opacity:1" />
6
+ </linearGradient>
7
+ </defs>
8
+
9
+ <!-- Vision eye symbol -->
10
+ <ellipse cx="50" cy="40" rx="30" ry="20" fill="url(#grad1)" opacity="0.8"/>
11
+ <circle cx="50" cy="40" r="10" fill="white"/>
12
+ <circle cx="50" cy="40" r="5" fill="#667eea"/>
13
+
14
+ <!-- Document/Language symbol -->
15
+ <rect x="25" y="55" width="50" height="35" rx="3" fill="url(#grad1)"/>
16
+ <line x1="32" y1="65" x2="68" y2="65" stroke="white" stroke-width="2" stroke-linecap="round"/>
17
+ <line x1="32" y1="73" x2="68" y2="73" stroke="white" stroke-width="2" stroke-linecap="round"/>
18
+ <line x1="32" y1="81" x2="55" y2="81" stroke="white" stroke-width="2" stroke-linecap="round"/>
19
+ </svg>
@@ -0,0 +1,37 @@
1
+ import { IExecuteFunctions, ISupplyDataFunctions } from 'n8n-workflow';
2
+ type VLMContext = IExecuteFunctions | ISupplyDataFunctions;
3
+ export interface VLMConfig {
4
+ serverUrl: string;
5
+ model?: string;
6
+ documents: any[];
7
+ timeout: number;
8
+ classificationStrategy?: 'metadata' | 'filter' | 'both';
9
+ filterComplexity?: 'LOW' | 'HIGH' | 'both';
10
+ }
11
+ export interface VLClassificationResult {
12
+ complexity: 'LOW' | 'HIGH';
13
+ confidence?: number;
14
+ processingTime?: number;
15
+ modelUsed?: string;
16
+ }
17
+ export interface ServerStatus {
18
+ status: 'healthy' | 'degraded' | 'error';
19
+ modelsLoaded?: string[];
20
+ vramUsage?: number;
21
+ hasClassifier?: boolean;
22
+ hasReranker?: boolean;
23
+ version?: string;
24
+ }
25
+ /**
26
+ * Check server status to detect VLM classifier capabilities
27
+ */
28
+ export declare function checkServerStatus(context: VLMContext, serverUrl: string, timeout?: number): Promise<ServerStatus>;
29
+ /**
30
+ * Classify documents using VLM Classifier
31
+ */
32
+ export declare function classifyDocuments(context: VLMContext, config: VLMConfig): Promise<any[]>;
33
+ /**
34
+ * List available VLM models from server
35
+ */
36
+ export declare function listVLMModels(context: VLMContext, serverUrl: string, timeout?: number): Promise<any[]>;
37
+ export {};
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkServerStatus = checkServerStatus;
4
+ exports.classifyDocuments = classifyDocuments;
5
+ exports.listVLMModels = listVLMModels;
6
+ const n8n_workflow_1 = require("n8n-workflow");
7
+ /**
8
+ * Check server status to detect VLM classifier capabilities
9
+ */
10
+ async function checkServerStatus(context, serverUrl, timeout = 5000) {
11
+ try {
12
+ const response = await context.helpers.httpRequest({
13
+ method: 'GET',
14
+ url: `${serverUrl}/api/status`,
15
+ headers: {
16
+ Accept: 'application/json',
17
+ },
18
+ json: true,
19
+ timeout,
20
+ });
21
+ return {
22
+ status: response.status || 'healthy',
23
+ modelsLoaded: response.models_loaded || response.models || [],
24
+ vramUsage: response.vram_usage,
25
+ hasClassifier: response.has_classifier || false,
26
+ hasReranker: response.has_reranker || false,
27
+ version: response.version,
28
+ };
29
+ }
30
+ catch (error) {
31
+ // If /api/status doesn't exist, it's not a VLM classifier server
32
+ return {
33
+ status: 'error',
34
+ hasClassifier: false,
35
+ hasReranker: false,
36
+ };
37
+ }
38
+ }
39
+ /**
40
+ * Classify a single document's complexity using VLM Classifier API
41
+ */
42
+ async function classifyDocumentComplexity(context, serverUrl, document, timeout) {
43
+ try {
44
+ // Prepare document content for classification
45
+ const content = document.pageContent || JSON.stringify(document);
46
+ // Check if document contains image data
47
+ const hasImage = document.image || document.base64Image;
48
+ let requestBody = {
49
+ text: content,
50
+ };
51
+ if (hasImage) {
52
+ requestBody.image = document.image || document.base64Image;
53
+ }
54
+ const response = await context.helpers.httpRequest({
55
+ method: 'POST',
56
+ url: `${serverUrl}/api/classify`,
57
+ headers: {
58
+ 'Content-Type': 'application/json',
59
+ Accept: 'application/json',
60
+ },
61
+ body: requestBody,
62
+ json: true,
63
+ timeout,
64
+ });
65
+ return {
66
+ complexity: response.complexity || response.classification || 'LOW',
67
+ confidence: response.confidence,
68
+ processingTime: response.processing_time || response.processingTime,
69
+ modelUsed: response.model || 'VLM-Classifier',
70
+ };
71
+ }
72
+ catch (error) {
73
+ // On error, default to LOW complexity to not filter out documents
74
+ console.warn('Classification failed, defaulting to LOW complexity:', error.message);
75
+ return {
76
+ complexity: 'LOW',
77
+ confidence: 0,
78
+ };
79
+ }
80
+ }
81
+ /**
82
+ * Classify documents using VLM Classifier
83
+ */
84
+ async function classifyDocuments(context, config) {
85
+ const { serverUrl, documents, timeout, classificationStrategy = 'metadata', filterComplexity = 'both' } = config;
86
+ // Step 1: Classify all documents
87
+ const classificationPromises = documents.map(async (doc, index) => {
88
+ const classification = await classifyDocumentComplexity(context, serverUrl, doc, timeout);
89
+ return { doc, index, classification };
90
+ });
91
+ const classifiedDocs = await Promise.all(classificationPromises);
92
+ // Step 2: Filter documents based on strategy
93
+ let docsToReturn = classifiedDocs;
94
+ if (classificationStrategy === 'filter' || classificationStrategy === 'both') {
95
+ if (filterComplexity !== 'both') {
96
+ docsToReturn = classifiedDocs.filter(item => item.classification.complexity === filterComplexity);
97
+ }
98
+ }
99
+ // If no documents pass the filter, return empty
100
+ if (docsToReturn.length === 0) {
101
+ return [];
102
+ }
103
+ // Step 3: Add metadata if requested
104
+ const finalDocs = docsToReturn.map(item => {
105
+ const enrichedDoc = { ...item.doc };
106
+ if (classificationStrategy === 'metadata' || classificationStrategy === 'both') {
107
+ enrichedDoc._complexityClass = item.classification.complexity;
108
+ enrichedDoc._complexityConfidence = item.classification.confidence;
109
+ enrichedDoc._processingTime = item.classification.processingTime;
110
+ enrichedDoc._modelUsed = item.classification.modelUsed;
111
+ }
112
+ enrichedDoc._originalIndex = item.index;
113
+ return enrichedDoc;
114
+ });
115
+ return finalDocs;
116
+ }
117
+ /**
118
+ * List available VLM models from server
119
+ */
120
+ async function listVLMModels(context, serverUrl, timeout = 5000) {
121
+ try {
122
+ const response = await context.helpers.httpRequest({
123
+ method: 'GET',
124
+ url: `${serverUrl}/api/tags`,
125
+ headers: {
126
+ Accept: 'application/json',
127
+ },
128
+ json: true,
129
+ timeout,
130
+ });
131
+ return response.models || [];
132
+ }
133
+ catch (error) {
134
+ throw new n8n_workflow_1.NodeApiError(context.getNode(), error, {
135
+ message: 'Failed to fetch VLM models',
136
+ description: `Endpoint: ${serverUrl}/api/tags\nError: ${error.message}`,
137
+ });
138
+ }
139
+ }
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "n8n-nodes-vlm",
3
+ "version": "1.0.2",
4
+ "description": "Vision-Language Models for n8n - Lightweight specialized VLMs for document analysis and classification",
5
+ "main": "index.js",
6
+ "author": "Gabriel BRUMENT",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/theseedship/n8n_vlm.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/theseedship/n8n_vlm/issues"
14
+ },
15
+ "homepage": "https://github.com/theseedship/n8n_vlm#readme",
16
+ "files": [
17
+ "dist/**/*",
18
+ "src/**/*.svg",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsc && npm run copy-assets",
24
+ "copy-assets": "cp src/nodes/VLMComplexityClassifier/vlm.svg dist/nodes/VLMComplexityClassifier/ && cp src/nodes/VLMComplexityWorkflow/vlm.svg dist/nodes/VLMComplexityWorkflow/",
25
+ "dev": "tsc --watch",
26
+ "lint": "eslint . --ext .ts",
27
+ "lint:fix": "eslint . --ext .ts --fix",
28
+ "test": "jest",
29
+ "format": "prettier --write \"**/*.{ts,js,json,md}\"",
30
+ "format:check": "prettier --check \"**/*.{ts,js,json,md}\"",
31
+ "prepare": "cd .. && husky custom-nodes/.husky"
32
+ },
33
+ "keywords": [
34
+ "n8n",
35
+ "n8n-nodes",
36
+ "n8n-community-node-package",
37
+ "vlm",
38
+ "vision-language",
39
+ "classification",
40
+ "complexity",
41
+ "document-analysis",
42
+ "ai"
43
+ ],
44
+ "n8n": {
45
+ "n8nNodesApiVersion": 1,
46
+ "nodes": [
47
+ "dist/nodes/VLMComplexityClassifier/VLMComplexityClassifier.node.js",
48
+ "dist/nodes/VLMComplexityWorkflow/VLMComplexityWorkflow.node.js"
49
+ ],
50
+ "credentials": []
51
+ },
52
+ "devDependencies": {
53
+ "@types/jest": "^29.0.0",
54
+ "@types/node": "^18.0.0",
55
+ "@typescript-eslint/eslint-plugin": "^5.0.0",
56
+ "@typescript-eslint/parser": "^5.0.0",
57
+ "eslint": "^8.0.0",
58
+ "husky": "^9.1.7",
59
+ "jest": "^29.0.0",
60
+ "lint-staged": "^15.2.11",
61
+ "prettier": "^3.4.2",
62
+ "ts-jest": "^29.0.0",
63
+ "typescript": "^5.0.0"
64
+ },
65
+ "dependencies": {
66
+ "n8n-workflow": "^1.0.0"
67
+ },
68
+ "lint-staged": {
69
+ "*.{ts,js}": [
70
+ "eslint --fix",
71
+ "prettier --write"
72
+ ],
73
+ "*.{json,md}": [
74
+ "prettier --write"
75
+ ]
76
+ },
77
+ "engines": {
78
+ "node": ">=18.0.0",
79
+ "npm": ">=9.0.0"
80
+ }
81
+ }
@@ -0,0 +1,19 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
2
+ <defs>
3
+ <linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" style="stop-color:#667eea;stop-opacity:1" />
5
+ <stop offset="100%" style="stop-color:#764ba2;stop-opacity:1" />
6
+ </linearGradient>
7
+ </defs>
8
+
9
+ <!-- Vision eye symbol -->
10
+ <ellipse cx="50" cy="40" rx="30" ry="20" fill="url(#grad1)" opacity="0.8"/>
11
+ <circle cx="50" cy="40" r="10" fill="white"/>
12
+ <circle cx="50" cy="40" r="5" fill="#667eea"/>
13
+
14
+ <!-- Document/Language symbol -->
15
+ <rect x="25" y="55" width="50" height="35" rx="3" fill="url(#grad1)"/>
16
+ <line x1="32" y1="65" x2="68" y2="65" stroke="white" stroke-width="2" stroke-linecap="round"/>
17
+ <line x1="32" y1="73" x2="68" y2="73" stroke="white" stroke-width="2" stroke-linecap="round"/>
18
+ <line x1="32" y1="81" x2="55" y2="81" stroke="white" stroke-width="2" stroke-linecap="round"/>
19
+ </svg>
@@ -0,0 +1,19 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
2
+ <defs>
3
+ <linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" style="stop-color:#667eea;stop-opacity:1" />
5
+ <stop offset="100%" style="stop-color:#764ba2;stop-opacity:1" />
6
+ </linearGradient>
7
+ </defs>
8
+
9
+ <!-- Vision eye symbol -->
10
+ <ellipse cx="50" cy="40" rx="30" ry="20" fill="url(#grad1)" opacity="0.8"/>
11
+ <circle cx="50" cy="40" r="10" fill="white"/>
12
+ <circle cx="50" cy="40" r="5" fill="#667eea"/>
13
+
14
+ <!-- Document/Language symbol -->
15
+ <rect x="25" y="55" width="50" height="35" rx="3" fill="url(#grad1)"/>
16
+ <line x1="32" y1="65" x2="68" y2="65" stroke="white" stroke-width="2" stroke-linecap="round"/>
17
+ <line x1="32" y1="73" x2="68" y2="73" stroke="white" stroke-width="2" stroke-linecap="round"/>
18
+ <line x1="32" y1="81" x2="55" y2="81" stroke="white" stroke-width="2" stroke-linecap="round"/>
19
+ </svg>