n8n-nodes-memcontext 0.1.5

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # n8n-nodes-memcontext
2
+
3
+ n8n custom nodes for a self-hosted memcontext API (Flask). Includes:
4
+ - **Memory Add**: add text memories (`/init_memory` → `/import_conversations`)
5
+ - **Memory Search**: search memories and get a reply (`/init_memory` → `/chat`)
6
+ - **Memory Add Multimodal**: add multimodal memories (video/audio/image, `/init_memory` → `/add_multimodal_memory`), supports binary upload or local file path.
7
+
8
+ ## Installation
9
+
10
+ ### Local / private install
11
+ ```bash
12
+ # in package root
13
+ npm run build
14
+ npm pack # produces n8n-nodes-memcontext-<version>.tgz
15
+
16
+ # in n8n custom nodes dir (example: C:\Users\<you>\.n8n\nodes)
17
+ npm init -y
18
+ npm install /path/to/n8n-nodes-memcontext-<version>.tgz
19
+ ```
20
+ Restart n8n to load the nodes.
21
+
22
+ ### Publish to npm (optional)
23
+ ```bash
24
+ # ensure version is bumped
25
+ npm login
26
+ npm publish # if using a scope, rename to @your-scope/n8n-nodes-memcontext and add --access public
27
+ ```
28
+ Target env: `npm install n8n-nodes-memcontext`, then restart n8n.
29
+
30
+ ## Backend requirements
31
+ - Your Flask backend running (default `http://127.0.0.1:5019`).
32
+ - Multimodal `video` converter needs:
33
+ - env vars: `LLM_API_KEY`, `LLM_BASE_URL`, `LLM_MODEL`
34
+ - `ffmpeg/ffprobe` in PATH
35
+ - `videorag` converter is optional (imagebind/hnswlib, better on Linux/GPU). On Windows/CPU, use `video`.
36
+
37
+ ## Credentials (Memory API)
38
+ - **Base URL**: backend URL (default `http://127.0.0.1:5019`)
39
+ - **API Key**: optional; if backend requires auth, fill it (e.g. `Bearer xxx`, passed through as-is)
40
+
41
+ ## Nodes
42
+
43
+ ### Memory Add
44
+ - Writes text memory.
45
+ - Params: `userId`, `userInput`, `agentResponse`, `timestamp` (optional), `metaData` (JSON).
46
+ - Routes: `/init_memory` → `/import_conversations`.
47
+
48
+ ### Memory Search
49
+ - Searches memory and returns reply.
50
+ - Params: `userId`, `query`.
51
+ - Routes: `/init_memory` → `/chat`.
52
+
53
+ ### Memory Add Multimodal
54
+ - Writes multimodal memory (video/audio/image).
55
+ - Modes:
56
+ - `Upload File (Binary)`: binary from upstream node; set Binary Property Name.
57
+ - `Use Local File Path`: absolute path accessible by backend host.
58
+ - Converters:
59
+ - `video` (recommended; requires ffmpeg + Ark SDK + LLM API env vars)
60
+ - `videorag` (optional; extra deps/models)
61
+ - Routes: `/init_memory` → `/add_multimodal_memory`.
62
+
63
+ ## Example workflow
64
+ ```
65
+ Manual Trigger
66
+ └─ Memory Add
67
+ └─ Memory Add Multimodal
68
+ └─ Memory Search
69
+ ```
70
+ - Three nodes share the same Memory API credential and the same `userId`.
71
+ - Base URL recommendation: `http://127.0.0.1:5019`.
72
+ - For `Memory Add Multimodal` path mode: `File Path` must be an absolute path reachable by the backend machine; start with converter `video`.
73
+
74
+ ## FAQ
75
+ - **ECONNREFUSED 127.0.0.1:5019**: backend not running or wrong port; `curl http://127.0.0.1:5019/` first.
76
+ - **Video file missing / ingested_rounds=0**: path not reachable or ffmpeg/env vars missing; check backend logs.
77
+ - **Backend 500**: inspect `python app.py` console stack; common causes are deps/env/path issues.
78
+ - **Community nodes TLS errors**: unrelated to node execution; usually external network/proxy; ignore or configure network.
79
+
80
+ ## Development build
81
+ - `npm run build` to generate `dist/`
82
+ - `npm pack` to produce release `.tgz`
83
+ - Before publishing, ensure `package.json` `n8n` field points to nodes/credentials under `dist`.
84
+
@@ -0,0 +1,6 @@
1
+ import { ICredentialType, INodeProperties } from 'n8n-workflow';
2
+ export declare class MemoryApi implements ICredentialType {
3
+ name: string;
4
+ displayName: string;
5
+ properties: INodeProperties[];
6
+ }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MemoryApi = void 0;
4
+ class MemoryApi {
5
+ constructor() {
6
+ // n8n 会使用这个 name 来匹配节点中 credentials 的 name 属性
7
+ this.name = 'memoryApi';
8
+ // 在 n8n UI 中显示的名字
9
+ this.displayName = 'Memory API';
10
+ this.properties = [
11
+ {
12
+ displayName: 'Base URL',
13
+ name: 'baseUrl',
14
+ type: 'string',
15
+ default: 'http://localhost:5019',
16
+ required: true,
17
+ description: 'Memory API base URL, e.g. http://localhost:5019 or https://api.example.com',
18
+ },
19
+ {
20
+ displayName: 'API Key', // 在 Credential 配置界面显示的名称
21
+ name: 'apiKey', // 内部使用的属性名
22
+ type: 'string',
23
+ default: '',
24
+ required: false,
25
+ description: 'Your Memory API Key (e.g., "Bearer YOUR_KEY")',
26
+ typeOptions: {
27
+ password: true,
28
+ },
29
+ },
30
+ ];
31
+ }
32
+ }
33
+ exports.MemoryApi = MemoryApi;
@@ -0,0 +1,4 @@
1
+ import { nodes } from './nodes';
2
+ import { MemoryApi } from './credentials/MemoryApi.credential';
3
+ export { nodes };
4
+ export declare const credentials: MemoryApi[];
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.credentials = exports.nodes = void 0;
4
+ const nodes_1 = require("./nodes");
5
+ Object.defineProperty(exports, "nodes", { enumerable: true, get: function () { return nodes_1.nodes; } });
6
+ const MemoryApi_credential_1 = require("./credentials/MemoryApi.credential");
7
+ exports.credentials = [
8
+ new MemoryApi_credential_1.MemoryApi(),
9
+ ];
@@ -0,0 +1,5 @@
1
+ import { IExecuteFunctions, INodeExecutionData, INodeType } from 'n8n-workflow';
2
+ export declare class MemoryAdd implements INodeType {
3
+ description: import("n8n-workflow").INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MemoryAdd = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const ui_1 = require("./ui");
6
+ class MemoryAdd {
7
+ constructor() {
8
+ this.description = ui_1.MemoryAddUiDescription;
9
+ }
10
+ async execute() {
11
+ const userId = this.getNodeParameter('userId', 0);
12
+ const userInput = this.getNodeParameter('userInput', 0);
13
+ const agentResponse = this.getNodeParameter('agentResponse', 0);
14
+ const timestamp = this.getNodeParameter('timestamp', 0);
15
+ const metaData = this.getNodeParameter('metaData', 0);
16
+ const initPath = '/init_memory';
17
+ const apiPath = '/import_conversations';
18
+ let authorizationHeader = '';
19
+ let memoryApiBaseUrl = 'http://localhost:5019';
20
+ try {
21
+ const credentials = await this.getCredentials('memoryApi');
22
+ const apiKey = credentials.apiKey || '';
23
+ const baseUrl = credentials.baseUrl;
24
+ memoryApiBaseUrl = baseUrl || memoryApiBaseUrl;
25
+ authorizationHeader = apiKey;
26
+ }
27
+ catch {
28
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Memory API credential is not configured.');
29
+ }
30
+ const baseHeaders = {
31
+ 'Content-Type': 'application/json',
32
+ };
33
+ if (authorizationHeader) {
34
+ baseHeaders.Authorization = authorizationHeader;
35
+ }
36
+ // 先初始化 Flask session(后端依赖 cookie 来找到 memory_system)
37
+ const initResp = await this.helpers.request({
38
+ url: `${memoryApiBaseUrl}${initPath}`,
39
+ method: 'POST',
40
+ headers: baseHeaders,
41
+ body: {
42
+ user_id: userId,
43
+ },
44
+ json: true,
45
+ resolveWithFullResponse: true,
46
+ });
47
+ const setCookie = initResp.headers?.['set-cookie'];
48
+ const cookieHeader = Array.isArray(setCookie)
49
+ ? setCookie.map((c) => String(c).split(';')[0]).join('; ')
50
+ : '';
51
+ if (!cookieHeader) {
52
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Failed to initialize session (no Set-Cookie returned from /init_memory).');
53
+ }
54
+ const response = await this.helpers.request({
55
+ url: `${memoryApiBaseUrl}${apiPath}`,
56
+ method: 'POST',
57
+ headers: {
58
+ ...baseHeaders,
59
+ Cookie: cookieHeader,
60
+ },
61
+ body: {
62
+ conversations: [
63
+ {
64
+ user_input: userInput,
65
+ agent_response: agentResponse,
66
+ ...(timestamp ? { timestamp } : {}),
67
+ meta_data: metaData,
68
+ },
69
+ ],
70
+ },
71
+ json: true,
72
+ });
73
+ return [this.helpers.returnJsonArray(response)];
74
+ }
75
+ }
76
+ exports.MemoryAdd = MemoryAdd;
@@ -0,0 +1,2 @@
1
+ import { INodeTypeDescription } from 'n8n-workflow';
2
+ export declare const MemoryAddUiDescription: INodeTypeDescription;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MemoryAddUiDescription = void 0;
4
+ exports.MemoryAddUiDescription = {
5
+ displayName: 'Memory Add',
6
+ name: 'memoryAdd',
7
+ icon: 'fa:plus',
8
+ group: ['transform'],
9
+ version: 1,
10
+ description: 'Add memories to your Memory API backend.',
11
+ defaults: {
12
+ name: 'Add Memory',
13
+ },
14
+ inputs: ['main'],
15
+ outputs: ['main'],
16
+ credentials: [
17
+ {
18
+ name: 'memoryApi',
19
+ required: true,
20
+ },
21
+ ],
22
+ properties: [
23
+ {
24
+ displayName: 'User ID',
25
+ name: 'userId',
26
+ type: 'string',
27
+ default: '',
28
+ required: true,
29
+ placeholder: 'e.g., user123',
30
+ description: 'User ID for memory space.',
31
+ },
32
+ {
33
+ displayName: 'User Input',
34
+ name: 'userInput',
35
+ type: 'string',
36
+ default: '',
37
+ required: true,
38
+ typeOptions: {
39
+ rows: 3,
40
+ },
41
+ description: "The content of the user's input.",
42
+ },
43
+ {
44
+ displayName: 'Agent Response',
45
+ name: 'agentResponse',
46
+ type: 'string',
47
+ default: '',
48
+ required: true,
49
+ typeOptions: {
50
+ rows: 3,
51
+ },
52
+ description: "The content of the agent's response.",
53
+ },
54
+ {
55
+ displayName: 'Timestamp',
56
+ name: 'timestamp',
57
+ type: 'string',
58
+ default: '',
59
+ placeholder: 'e.g., 2024-01-01T12:00:00Z (ISO 8601)',
60
+ description: 'Timestamp in ISO 8601 format. If not provided, current time will be used.',
61
+ },
62
+ {
63
+ displayName: 'Meta Data',
64
+ name: 'metaData',
65
+ type: 'json',
66
+ default: {},
67
+ description: 'Additional metadata for the memory entry.',
68
+ },
69
+ ],
70
+ };
@@ -0,0 +1,5 @@
1
+ import { IExecuteFunctions, INodeExecutionData, INodeType } from 'n8n-workflow';
2
+ export declare class MemoryAddMultimodal implements INodeType {
3
+ description: import("n8n-workflow").INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MemoryAddMultimodal = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const ui_1 = require("./ui");
6
+ class MemoryAddMultimodal {
7
+ constructor() {
8
+ this.description = ui_1.MemoryAddMultimodalUiDescription;
9
+ }
10
+ async execute() {
11
+ const formatRequestError = (error) => {
12
+ if (!error)
13
+ return 'Unknown error';
14
+ const e = error;
15
+ const parts = [];
16
+ if (e.statusCode)
17
+ parts.push(`statusCode=${e.statusCode}`);
18
+ if (e.message)
19
+ parts.push(`message=${e.message}`);
20
+ // n8n request helper often exposes response body as `response` or `responseBody`
21
+ const body = e.responseBody ?? e.response;
22
+ if (body !== undefined) {
23
+ try {
24
+ parts.push(`response=${typeof body === 'string' ? body : JSON.stringify(body)}`);
25
+ }
26
+ catch {
27
+ parts.push(`response=${String(body)}`);
28
+ }
29
+ }
30
+ if (!parts.length)
31
+ return String(error);
32
+ return parts.join(' | ');
33
+ };
34
+ const userId = this.getNodeParameter('userId', 0);
35
+ const agentResponse = this.getNodeParameter('agentResponse', 0, '');
36
+ const inputSource = this.getNodeParameter('inputSource', 0, 'upload');
37
+ const advancedOptions = this.getNodeParameter('advancedOptions', 0, {});
38
+ const initPath = '/init_memory';
39
+ const apiPath = '/add_multimodal_memory';
40
+ let authorizationHeader;
41
+ let memoryApiBaseUrl = 'http://localhost:5019';
42
+ try {
43
+ const credentials = await this.getCredentials('memoryApi');
44
+ const apiKey = credentials.apiKey || '';
45
+ const baseUrl = credentials.baseUrl;
46
+ memoryApiBaseUrl = baseUrl || memoryApiBaseUrl;
47
+ authorizationHeader = apiKey;
48
+ }
49
+ catch {
50
+ this.logger.error('Failed to get Memory API credentials');
51
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Memory API credential is not configured or invalid.');
52
+ }
53
+ const baseHeaders = {};
54
+ if (authorizationHeader) {
55
+ baseHeaders.Authorization = authorizationHeader;
56
+ }
57
+ // 先初始化 Flask session(后端依赖 cookie 来找到 memory_system)
58
+ const initResp = await this.helpers.request({
59
+ url: `${memoryApiBaseUrl}${initPath}`,
60
+ method: 'POST',
61
+ headers: {
62
+ ...baseHeaders,
63
+ 'Content-Type': 'application/json',
64
+ },
65
+ body: {
66
+ user_id: userId,
67
+ },
68
+ json: true,
69
+ resolveWithFullResponse: true,
70
+ });
71
+ const setCookie = initResp.headers?.['set-cookie'];
72
+ const cookieHeader = Array.isArray(setCookie)
73
+ ? setCookie.map((c) => String(c).split(';')[0]).join('; ')
74
+ : '';
75
+ if (!cookieHeader) {
76
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Failed to initialize session (no Set-Cookie returned from /init_memory).');
77
+ }
78
+ let response;
79
+ // ===== 上传文件(二进制) =====
80
+ if (inputSource === 'upload') {
81
+ const binaryPropertyName = this.getNodeParameter('file', 0, 'file');
82
+ const converterType = this.getNodeParameter('converterType', 0, 'videorag');
83
+ const binaryData = this.helpers.assertBinaryData(0, binaryPropertyName);
84
+ const formData = {
85
+ user_id: userId,
86
+ converter_type: converterType,
87
+ file: binaryData,
88
+ ...(agentResponse ? { agent_response: agentResponse } : {}),
89
+ };
90
+ if (advancedOptions?.converterKwargs) {
91
+ formData.converter_kwargs = JSON.stringify(advancedOptions.converterKwargs);
92
+ }
93
+ try {
94
+ response = await this.helpers.request({
95
+ url: `${memoryApiBaseUrl}${apiPath}`,
96
+ method: 'POST',
97
+ headers: {
98
+ ...baseHeaders,
99
+ Cookie: cookieHeader,
100
+ },
101
+ formData,
102
+ });
103
+ }
104
+ catch (error) {
105
+ this.logger.error('Error calling Memory API (upload)', {
106
+ error: error instanceof Error ? error.message : String(error),
107
+ });
108
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to upload multimodal memory. ${formatRequestError(error)}`);
109
+ }
110
+ // ===== 使用本地路径 =====
111
+ }
112
+ else if (inputSource === 'path') {
113
+ const filePath = this.getNodeParameter('filePath', 0, '');
114
+ const converterType = this.getNodeParameter('converterType', 0, 'videorag');
115
+ if (!filePath) {
116
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'File path is required when input source is "path".');
117
+ }
118
+ const requestBody = {
119
+ user_id: userId,
120
+ file_path: filePath,
121
+ converter_type: converterType,
122
+ ...(agentResponse ? { agent_response: agentResponse } : {}),
123
+ };
124
+ if (advancedOptions?.converterKwargs) {
125
+ requestBody.converter_kwargs = advancedOptions.converterKwargs;
126
+ }
127
+ try {
128
+ response = await this.helpers.request({
129
+ url: `${memoryApiBaseUrl}${apiPath}`,
130
+ method: 'POST',
131
+ headers: {
132
+ ...baseHeaders,
133
+ Cookie: cookieHeader,
134
+ 'Content-Type': 'application/json',
135
+ },
136
+ body: requestBody,
137
+ json: true,
138
+ });
139
+ }
140
+ catch (error) {
141
+ this.logger.error('Error calling Memory API (path)', {
142
+ error: error instanceof Error ? error.message : String(error),
143
+ });
144
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to add multimodal memory from path. ${formatRequestError(error)}`);
145
+ }
146
+ }
147
+ else {
148
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid input source: ${inputSource}`);
149
+ }
150
+ return [
151
+ this.helpers.returnJsonArray(response),
152
+ ];
153
+ }
154
+ }
155
+ exports.MemoryAddMultimodal = MemoryAddMultimodal;
@@ -0,0 +1,2 @@
1
+ import { INodeTypeDescription } from 'n8n-workflow';
2
+ export declare const MemoryAddMultimodalUiDescription: INodeTypeDescription;
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MemoryAddMultimodalUiDescription = void 0;
4
+ exports.MemoryAddMultimodalUiDescription = {
5
+ displayName: 'Memory Add Multimodal',
6
+ name: 'memoryAddMultimodal',
7
+ icon: 'fa:file-video',
8
+ group: ['transform'],
9
+ version: 1,
10
+ description: 'Add multimodal memories (video, audio, image) to your Memory API backend.',
11
+ defaults: {
12
+ name: 'Add Multimodal Memory',
13
+ },
14
+ inputs: ['main'],
15
+ outputs: ['main'],
16
+ credentials: [
17
+ {
18
+ name: 'memoryApi',
19
+ required: true,
20
+ },
21
+ ],
22
+ properties: [
23
+ {
24
+ displayName: 'User ID',
25
+ name: 'userId',
26
+ type: 'string',
27
+ default: '',
28
+ required: true,
29
+ placeholder: 'e.g., user123',
30
+ description: 'User ID for memory space.',
31
+ },
32
+ {
33
+ displayName: 'Agent Response',
34
+ name: 'agentResponse',
35
+ type: 'string',
36
+ default: '',
37
+ placeholder: 'e.g., User uploaded a test video.',
38
+ typeOptions: {
39
+ rows: 2,
40
+ },
41
+ description: 'Associated agent response describing the multimodal content.',
42
+ },
43
+ {
44
+ displayName: 'Input Source',
45
+ name: 'inputSource',
46
+ type: 'options',
47
+ options: [
48
+ { name: 'Upload File (Binary)', value: 'upload' },
49
+ { name: 'Use Local File Path', value: 'path' },
50
+ ],
51
+ default: 'upload',
52
+ description: 'Choose how to provide the file.',
53
+ },
54
+ /* ===== 上传文件(binary input) ===== */
55
+ {
56
+ displayName: 'Binary Property Name',
57
+ name: 'file',
58
+ type: 'string',
59
+ default: 'file',
60
+ displayOptions: {
61
+ show: {
62
+ inputSource: ['upload'],
63
+ },
64
+ },
65
+ description: 'Name of the binary property that contains the uploaded file (default: file).',
66
+ },
67
+ /* ===== 本地路径 ===== */
68
+ {
69
+ displayName: 'File Path',
70
+ name: 'filePath',
71
+ type: 'string',
72
+ default: '',
73
+ placeholder: '/path/to/your/video.mp4',
74
+ displayOptions: {
75
+ show: {
76
+ inputSource: ['path'],
77
+ },
78
+ },
79
+ description: 'Server-accessible path to the local file when using "Use Local File Path".',
80
+ },
81
+ {
82
+ displayName: 'Converter Type',
83
+ name: 'converterType',
84
+ type: 'options',
85
+ options: [
86
+ { name: 'Video (default)', value: 'video' },
87
+ { name: 'Video RAG (recommended)', value: 'videorag' },
88
+ { name: 'Audio', value: 'audio' },
89
+ { name: 'Image', value: 'image' },
90
+ ],
91
+ default: 'videorag',
92
+ description: 'The type of converter to use for processing the multimodal data.',
93
+ },
94
+ {
95
+ displayName: 'Advanced Converter Options',
96
+ name: 'advancedOptions',
97
+ type: 'collection',
98
+ default: {},
99
+ placeholder: 'Add Options',
100
+ description: 'Additional parameters to configure the converter behavior.',
101
+ options: [
102
+ {
103
+ displayName: 'Converter Kwargs (JSON)',
104
+ name: 'converterKwargs',
105
+ type: 'json',
106
+ default: {},
107
+ description: 'Custom arguments for the converter. Example: {"chunk_duration": 30}',
108
+ },
109
+ ],
110
+ },
111
+ ],
112
+ };
@@ -0,0 +1,5 @@
1
+ import { IExecuteFunctions, INodeExecutionData, INodeType } from 'n8n-workflow';
2
+ export declare class MemorySearch implements INodeType {
3
+ description: import("n8n-workflow").INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MemorySearch = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const ui_1 = require("./ui");
6
+ class MemorySearch {
7
+ constructor() {
8
+ this.description = ui_1.MemorySearchUiDescription;
9
+ }
10
+ async execute() {
11
+ const userId = this.getNodeParameter('userId', 0);
12
+ const query = this.getNodeParameter('query', 0);
13
+ const relationshipWithUser = this.getNodeParameter('relationshipWithUser', 0);
14
+ const styleHint = this.getNodeParameter('styleHint', 0);
15
+ const userConversationMetaData = this.getNodeParameter('userConversationMetaData', 0);
16
+ const initPath = '/init_memory';
17
+ const apiPath = '/chat';
18
+ let authorizationHeader;
19
+ let memoryApiBaseUrl = 'http://localhost:5019';
20
+ try {
21
+ const credentials = await this.getCredentials('memoryApi');
22
+ const apiKey = credentials.apiKey || '';
23
+ const baseUrl = credentials.baseUrl;
24
+ memoryApiBaseUrl = baseUrl || memoryApiBaseUrl;
25
+ authorizationHeader = apiKey;
26
+ }
27
+ catch {
28
+ this.logger.error('Failed to get Memory API credentials');
29
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Memory API credential is not configured or invalid. Please configure it in the Credentials tab.');
30
+ }
31
+ const baseHeaders = {
32
+ 'Content-Type': 'application/json',
33
+ };
34
+ if (authorizationHeader) {
35
+ baseHeaders.Authorization = authorizationHeader;
36
+ }
37
+ // 先初始化 Flask session(后端依赖 cookie 来找到 memory_system)
38
+ const initResp = await this.helpers.request({
39
+ url: `${memoryApiBaseUrl}${initPath}`,
40
+ method: 'POST',
41
+ headers: baseHeaders,
42
+ body: {
43
+ user_id: userId,
44
+ // 这些字段在当前 Flask demo 不使用,但保留在节点参数中,以后可扩展
45
+ relationship_with_user: relationshipWithUser,
46
+ style_hint: styleHint,
47
+ user_conversation_meta_data: userConversationMetaData,
48
+ },
49
+ json: true,
50
+ resolveWithFullResponse: true,
51
+ });
52
+ const setCookie = initResp.headers?.['set-cookie'];
53
+ const cookieHeader = Array.isArray(setCookie)
54
+ ? setCookie.map((c) => String(c).split(';')[0]).join('; ')
55
+ : '';
56
+ if (!cookieHeader) {
57
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Failed to initialize session (no Set-Cookie returned from /init_memory).');
58
+ }
59
+ try {
60
+ const response = await this.helpers.request({
61
+ url: `${memoryApiBaseUrl}${apiPath}`,
62
+ method: 'POST',
63
+ headers: {
64
+ ...baseHeaders,
65
+ Cookie: cookieHeader,
66
+ },
67
+ body: {
68
+ message: query,
69
+ },
70
+ json: true,
71
+ });
72
+ return [this.helpers.returnJsonArray(response)];
73
+ }
74
+ catch (error) {
75
+ this.logger.error('Error calling Memory API', {
76
+ error: error instanceof Error ? error.message : String(error),
77
+ });
78
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'An error occurred while calling the Memory API.');
79
+ }
80
+ }
81
+ }
82
+ exports.MemorySearch = MemorySearch;
@@ -0,0 +1,2 @@
1
+ import { INodeTypeDescription } from 'n8n-workflow';
2
+ export declare const MemorySearchUiDescription: INodeTypeDescription;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MemorySearchUiDescription = void 0;
4
+ exports.MemorySearchUiDescription = {
5
+ displayName: 'Memory Search',
6
+ name: 'memorySearch',
7
+ icon: 'fa:search',
8
+ group: ['transform'],
9
+ version: 1,
10
+ description: 'Search memories using your Memory API backend.',
11
+ defaults: {
12
+ name: 'Search Memory',
13
+ },
14
+ inputs: ['main'],
15
+ outputs: ['main'],
16
+ credentials: [
17
+ {
18
+ name: 'memoryApi',
19
+ required: true,
20
+ },
21
+ ],
22
+ properties: [
23
+ {
24
+ displayName: 'User ID',
25
+ name: 'userId',
26
+ type: 'string',
27
+ default: '',
28
+ required: true,
29
+ placeholder: 'e.g., user123',
30
+ description: "User ID for memory space. This identifies which user's memories to search.",
31
+ },
32
+ {
33
+ displayName: 'Query',
34
+ name: 'query',
35
+ type: 'string',
36
+ default: '',
37
+ required: true,
38
+ placeholder: 'e.g., What did I talk about before?',
39
+ typeOptions: {
40
+ rows: 3,
41
+ },
42
+ description: 'The query for retrieving relevant memories.',
43
+ },
44
+ {
45
+ displayName: 'Relationship with User',
46
+ name: 'relationshipWithUser',
47
+ type: 'options',
48
+ options: [
49
+ { name: 'Friend', value: 'friend' },
50
+ { name: 'Assistant', value: 'assistant' },
51
+ { name: 'Colleague', value: 'colleague' },
52
+ { name: 'Family', value: 'family' },
53
+ { name: 'Other', value: 'other' },
54
+ ],
55
+ default: 'friend',
56
+ description: "Relationship with the user, affects the LLM's response tone and style.",
57
+ },
58
+ {
59
+ displayName: 'Style Hint',
60
+ name: 'styleHint',
61
+ type: 'string',
62
+ default: '',
63
+ placeholder: 'e.g., Professional, Friendly, Concise, Detailed',
64
+ description: 'A hint to guide the LLM in generating a specific response style.',
65
+ },
66
+ {
67
+ displayName: 'User Conversation Meta Data',
68
+ name: 'userConversationMetaData',
69
+ type: 'json',
70
+ default: {},
71
+ description: 'Metadata for the current conversation context, for tracking or analysis.',
72
+ },
73
+ ],
74
+ };
@@ -0,0 +1,2 @@
1
+ import { MemoryAdd } from './MemoryAdd/MemoryAdd.node';
2
+ export declare const nodes: (typeof MemoryAdd)[];
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.nodes = void 0;
4
+ const MemoryAdd_node_1 = require("./MemoryAdd/MemoryAdd.node");
5
+ const MemorySearch_node_1 = require("./MemorySearch/MemorySearch.node");
6
+ const MemoryAddMultimodal_node_1 = require("./MemoryAddMultimodal/MemoryAddMultimodal.node");
7
+ exports.nodes = [
8
+ MemoryAdd_node_1.MemoryAdd,
9
+ MemorySearch_node_1.MemorySearch,
10
+ MemoryAddMultimodal_node_1.MemoryAddMultimodal,
11
+ ];
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "n8n-nodes-memcontext",
3
+ "version": "0.1.5",
4
+ "description": "Custom n8n nodes for memcontext API integration",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+
8
+ "n8n": {
9
+ "n8nNodesApiVersion": 1,
10
+ "credentials": [
11
+ "dist/credentials/MemoryApi.credential.js"
12
+ ],
13
+ "nodes": [
14
+ "dist/nodes/MemoryAdd/MemoryAdd.node.js",
15
+ "dist/nodes/MemorySearch/MemorySearch.node.js",
16
+ "dist/nodes/MemoryAddMultimodal/MemoryAddMultimodal.node.js"
17
+ ]
18
+ },
19
+
20
+ "scripts": {
21
+ "build": "tsc -p tsconfig.json",
22
+ "prepublishOnly": "npm run build",
23
+ "dev:build": "tsc -w"
24
+ },
25
+
26
+ "dependencies": {
27
+ "child-process-promise": "^2.2.1"
28
+ },
29
+
30
+ "peerDependencies": {
31
+ "n8n-workflow": "^2.1.4"
32
+ },
33
+
34
+ "devDependencies": {
35
+ "@types/node": "^20.19.27",
36
+ "typescript": "^5.9.3"
37
+ },
38
+
39
+ "keywords": [
40
+ "n8n",
41
+ "n8n-nodes",
42
+ "n8n-custom-nodes",
43
+ "n8n-community-node-package",
44
+ "memory",
45
+ "ai"
46
+ ],
47
+
48
+ "author": "Your Name <your.email@example.com>",
49
+ "license": "MIT",
50
+
51
+ "files": [
52
+ "dist"
53
+ ]
54
+ }