n8n 1.116.2 → 1.117.1
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/dist/active-executions.d.ts +3 -1
- package/dist/active-executions.js +7 -8
- package/dist/active-executions.js.map +1 -1
- package/dist/build.tsbuildinfo +1 -1
- package/dist/commands/base-command.js +1 -5
- package/dist/commands/base-command.js.map +1 -1
- package/dist/commands/execute-batch.js +2 -3
- package/dist/commands/execute-batch.js.map +1 -1
- package/dist/commands/execute.js +2 -6
- package/dist/commands/execute.js.map +1 -1
- package/dist/commands/start.js +5 -5
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/webhook.js +4 -8
- package/dist/commands/webhook.js.map +1 -1
- package/dist/commands/worker.js +5 -9
- package/dist/commands/worker.js.map +1 -1
- package/dist/concurrency/concurrency-control.service.js +1 -2
- package/dist/concurrency/concurrency-control.service.js.map +1 -1
- package/dist/config/index.d.ts +0 -3
- package/dist/config/schema.d.ts +0 -8
- package/dist/config/schema.js +0 -8
- package/dist/config/schema.js.map +1 -1
- package/dist/controllers/ai.controller.d.ts +2 -1
- package/dist/controllers/ai.controller.js +19 -0
- package/dist/controllers/ai.controller.js.map +1 -1
- package/dist/controllers/e2e.controller.d.ts +3 -1
- package/dist/controllers/e2e.controller.js +7 -5
- package/dist/controllers/e2e.controller.js.map +1 -1
- package/dist/controllers/users.controller.d.ts +1 -1
- package/dist/controllers/users.controller.js +1 -1
- package/dist/controllers/users.controller.js.map +1 -1
- package/dist/deprecation/deprecation.service.js +1 -5
- package/dist/deprecation/deprecation.service.js.map +1 -1
- package/dist/environments.ee/source-control/source-control-export.service.ee.d.ts +1 -1
- package/dist/environments.ee/source-control/source-control-export.service.ee.js +2 -2
- package/dist/environments.ee/source-control/source-control-export.service.ee.js.map +1 -1
- package/dist/environments.ee/source-control/source-control-helper.ee.d.ts +2 -0
- package/dist/environments.ee/source-control/source-control-helper.ee.js +11 -2
- package/dist/environments.ee/source-control/source-control-helper.ee.js.map +1 -1
- package/dist/environments.ee/source-control/source-control-import.service.ee.d.ts +6 -4
- package/dist/environments.ee/source-control/source-control-import.service.ee.js +63 -51
- package/dist/environments.ee/source-control/source-control-import.service.ee.js.map +1 -1
- package/dist/environments.ee/source-control/source-control-status.service.ee.js +28 -3
- package/dist/environments.ee/source-control/source-control-status.service.ee.js.map +1 -1
- package/dist/environments.ee/variables/variables.service.ee.js +6 -1
- package/dist/environments.ee/variables/variables.service.ee.js.map +1 -1
- package/dist/evaluation.ee/test-runner/test-runner.service.ee.d.ts +3 -1
- package/dist/evaluation.ee/test-runner/test-runner.service.ee.js +7 -5
- package/dist/evaluation.ee/test-runner/test-runner.service.ee.js.map +1 -1
- package/dist/eventbus/message-event-bus/message-event-bus.js +1 -2
- package/dist/eventbus/message-event-bus/message-event-bus.js.map +1 -1
- package/dist/eventbus/message-event-bus-destination/message-event-bus-destination-syslog.ee.js +1 -0
- package/dist/eventbus/message-event-bus-destination/message-event-bus-destination-syslog.ee.js.map +1 -1
- package/dist/events/maps/relay.event-map.d.ts +18 -1
- package/dist/events/relays/telemetry.event-relay.d.ts +1 -0
- package/dist/events/relays/telemetry.event-relay.js +12 -4
- package/dist/events/relays/telemetry.event-relay.js.map +1 -1
- package/dist/executions/execution.service.js +2 -6
- package/dist/executions/execution.service.js.map +1 -1
- package/dist/license.js +1 -5
- package/dist/license.js.map +1 -1
- package/dist/metrics/prometheus-metrics.service.js +1 -2
- package/dist/metrics/prometheus-metrics.service.js.map +1 -1
- package/dist/middlewares/list-query/dtos/workflow.select.dto.js +1 -0
- package/dist/middlewares/list-query/dtos/workflow.select.dto.js.map +1 -1
- package/dist/modules/chat-hub/chat-hub-message.entity.d.ts +28 -0
- package/dist/modules/chat-hub/chat-hub-message.entity.js +124 -0
- package/dist/modules/chat-hub/chat-hub-message.entity.js.map +1 -0
- package/dist/modules/chat-hub/chat-hub-session.entity.d.ts +17 -0
- package/dist/modules/chat-hub/chat-hub-session.entity.js +72 -0
- package/dist/modules/chat-hub/chat-hub-session.entity.js.map +1 -0
- package/dist/modules/chat-hub/chat-hub.controller.d.ts +8 -5
- package/dist/modules/chat-hub/chat-hub.controller.js +151 -22
- package/dist/modules/chat-hub/chat-hub.controller.js.map +1 -1
- package/dist/modules/chat-hub/chat-hub.module.d.ts +1 -0
- package/dist/modules/chat-hub/chat-hub.module.js +6 -1
- package/dist/modules/chat-hub/chat-hub.module.js.map +1 -1
- package/dist/modules/chat-hub/chat-hub.service.d.ts +34 -13
- package/dist/modules/chat-hub/chat-hub.service.js +500 -315
- package/dist/modules/chat-hub/chat-hub.service.js.map +1 -1
- package/dist/modules/chat-hub/chat-hub.types.d.ts +25 -11
- package/dist/modules/chat-hub/chat-message.repository.d.ts +16 -0
- package/dist/modules/chat-hub/chat-message.repository.js +60 -0
- package/dist/modules/chat-hub/chat-message.repository.js.map +1 -0
- package/dist/modules/chat-hub/chat-session.repository.d.ts +12 -0
- package/dist/modules/chat-hub/chat-session.repository.js +77 -0
- package/dist/modules/chat-hub/chat-session.repository.js.map +1 -0
- package/dist/modules/chat-hub/context-limits.d.ts +3 -0
- package/dist/modules/chat-hub/context-limits.js +142 -0
- package/dist/modules/chat-hub/context-limits.js.map +1 -0
- package/dist/modules/community-packages/community-node-types.controller.js +2 -2
- package/dist/modules/community-packages/community-node-types.controller.js.map +1 -1
- package/dist/modules/mcp/mcp-api-key.service.d.ts +4 -1
- package/dist/modules/mcp/mcp-api-key.service.js +23 -8
- package/dist/modules/mcp/mcp-api-key.service.js.map +1 -1
- package/dist/modules/mcp/mcp.constants.d.ts +5 -0
- package/dist/modules/mcp/mcp.constants.js +9 -0
- package/dist/modules/mcp/mcp.constants.js.map +1 -0
- package/dist/modules/mcp/mcp.controller.d.ts +4 -1
- package/dist/modules/mcp/mcp.controller.js +56 -4
- package/dist/modules/mcp/mcp.controller.js.map +1 -1
- package/dist/modules/mcp/mcp.event-relay.d.ts +11 -0
- package/dist/modules/mcp/mcp.event-relay.js +61 -0
- package/dist/modules/mcp/mcp.event-relay.js.map +1 -0
- package/dist/modules/mcp/mcp.module.js +2 -0
- package/dist/modules/mcp/mcp.module.js.map +1 -1
- package/dist/modules/mcp/mcp.settings.controller.js +1 -1
- package/dist/modules/mcp/mcp.settings.controller.js.map +1 -1
- package/dist/modules/mcp/mcp.typeguards.d.ts +3 -0
- package/dist/modules/mcp/mcp.typeguards.js +19 -1
- package/dist/modules/mcp/mcp.typeguards.js.map +1 -1
- package/dist/modules/mcp/mcp.types.d.ts +24 -0
- package/dist/modules/mcp/mcp.utils.d.ts +8 -0
- package/dist/modules/mcp/mcp.utils.js +37 -0
- package/dist/modules/mcp/mcp.utils.js.map +1 -0
- package/dist/modules/provisioning.ee/constants.d.ts +1 -0
- package/dist/modules/provisioning.ee/constants.js +5 -0
- package/dist/modules/provisioning.ee/constants.js.map +1 -0
- package/dist/modules/provisioning.ee/provisioning.controller.ee.d.ts +10 -0
- package/dist/modules/provisioning.ee/provisioning.controller.ee.js +41 -0
- package/dist/modules/provisioning.ee/provisioning.controller.ee.js.map +1 -0
- package/dist/modules/provisioning.ee/provisioning.module.d.ts +4 -0
- package/dist/modules/provisioning.ee/provisioning.module.js +53 -0
- package/dist/modules/provisioning.ee/provisioning.module.js.map +1 -0
- package/dist/modules/provisioning.ee/provisioning.service.ee.d.ts +15 -0
- package/dist/modules/provisioning.ee/provisioning.service.ee.js +67 -0
- package/dist/modules/provisioning.ee/provisioning.service.ee.js.map +1 -0
- package/dist/posthog/index.js +0 -4
- package/dist/posthog/index.js.map +1 -1
- package/dist/public-api/v1/handlers/workflows/workflows.handler.js +12 -0
- package/dist/public-api/v1/handlers/workflows/workflows.handler.js.map +1 -1
- package/dist/scaling/pubsub/publisher.service.d.ts +3 -1
- package/dist/scaling/pubsub/publisher.service.js +7 -8
- package/dist/scaling/pubsub/publisher.service.js.map +1 -1
- package/dist/scaling/pubsub/subscriber.service.d.ts +3 -1
- package/dist/scaling/pubsub/subscriber.service.js +6 -4
- package/dist/scaling/pubsub/subscriber.service.js.map +1 -1
- package/dist/server.js +2 -2
- package/dist/server.js.map +1 -1
- package/dist/services/ai-workflow-builder.service.d.ts +3 -0
- package/dist/services/ai-workflow-builder.service.js +6 -0
- package/dist/services/ai-workflow-builder.service.js.map +1 -1
- package/dist/services/cache/cache.service.js +1 -5
- package/dist/services/cache/cache.service.js.map +1 -1
- package/dist/services/frontend.service.js +12 -3
- package/dist/services/frontend.service.js.map +1 -1
- package/dist/services/role.service.js +15 -3
- package/dist/services/role.service.js.map +1 -1
- package/dist/services/user.service.d.ts +3 -1
- package/dist/services/user.service.js +14 -5
- package/dist/services/user.service.js.map +1 -1
- package/dist/sso.ee/oidc/oidc.service.ee.js +6 -1
- package/dist/sso.ee/oidc/oidc.service.ee.js.map +1 -1
- package/dist/workflow-runner.d.ts +0 -2
- package/dist/workflow-runner.js +4 -9
- package/dist/workflow-runner.js.map +1 -1
- package/dist/workflows/workflow-execution.service.js +1 -5
- package/dist/workflows/workflow-execution.service.js.map +1 -1
- package/dist/workflows/workflow.service.js +24 -0
- package/dist/workflows/workflow.service.js.map +1 -1
- package/package.json +16 -16
|
@@ -16,24 +16,53 @@ const db_1 = require("@n8n/db");
|
|
|
16
16
|
const di_1 = require("@n8n/di");
|
|
17
17
|
const n8n_workflow_1 = require("n8n-workflow");
|
|
18
18
|
const uuid_1 = require("uuid");
|
|
19
|
-
const
|
|
19
|
+
const active_executions_1 = require("../../active-executions");
|
|
20
|
+
const credentials_service_1 = require("../../credentials/credentials.service");
|
|
21
|
+
const bad_request_error_1 = require("../../errors/response-errors/bad-request.error");
|
|
20
22
|
const not_found_error_1 = require("../../errors/response-errors/not-found.error");
|
|
23
|
+
const execution_service_1 = require("../../executions/execution.service");
|
|
24
|
+
const dynamic_node_parameters_service_1 = require("../../services/dynamic-node-parameters.service");
|
|
21
25
|
const workflow_execute_additional_data_1 = require("../../workflow-execute-additional-data");
|
|
22
26
|
const workflow_execution_service_1 = require("../../workflows/workflow-execution.service");
|
|
23
|
-
const
|
|
24
|
-
const
|
|
27
|
+
const chat_message_repository_1 = require("./chat-message.repository");
|
|
28
|
+
const chat_session_repository_1 = require("./chat-session.repository");
|
|
29
|
+
const context_limits_1 = require("./context-limits");
|
|
30
|
+
const providerNodeTypeMapping = {
|
|
31
|
+
openai: {
|
|
32
|
+
name: '@n8n/n8n-nodes-langchain.lmChatOpenAi',
|
|
33
|
+
version: 1.2,
|
|
34
|
+
},
|
|
35
|
+
anthropic: {
|
|
36
|
+
name: '@n8n/n8n-nodes-langchain.lmChatAnthropic',
|
|
37
|
+
version: 1.3,
|
|
38
|
+
},
|
|
39
|
+
google: {
|
|
40
|
+
name: '@n8n/n8n-nodes-langchain.lmChatGoogleGemini',
|
|
41
|
+
version: 1.2,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
const NODE_NAMES = {
|
|
45
|
+
CHAT_TRIGGER: 'When chat message received',
|
|
46
|
+
AI_AGENT: 'AI Agent',
|
|
47
|
+
CHAT_MODEL: 'Chat Model',
|
|
48
|
+
MEMORY: 'Memory',
|
|
49
|
+
RESTORE_CHAT_MEMORY: 'Restore Chat Memory',
|
|
50
|
+
CLEAR_CHAT_MEMORY: 'Clear Chat Memory',
|
|
51
|
+
};
|
|
25
52
|
let ChatHubService = class ChatHubService {
|
|
26
|
-
constructor(logger, credentialsService,
|
|
53
|
+
constructor(logger, credentialsService, executionService, nodeParametersService, executionRepository, workflowExecutionService, workflowRepository, projectRepository, sharedWorkflowRepository, activeExecutions, sessionRepository, messageRepository) {
|
|
27
54
|
this.logger = logger;
|
|
28
55
|
this.credentialsService = credentialsService;
|
|
29
|
-
this.
|
|
56
|
+
this.executionService = executionService;
|
|
57
|
+
this.nodeParametersService = nodeParametersService;
|
|
30
58
|
this.executionRepository = executionRepository;
|
|
31
59
|
this.workflowExecutionService = workflowExecutionService;
|
|
32
60
|
this.workflowRepository = workflowRepository;
|
|
33
61
|
this.projectRepository = projectRepository;
|
|
34
62
|
this.sharedWorkflowRepository = sharedWorkflowRepository;
|
|
35
63
|
this.activeExecutions = activeExecutions;
|
|
36
|
-
this.
|
|
64
|
+
this.sessionRepository = sessionRepository;
|
|
65
|
+
this.messageRepository = messageRepository;
|
|
37
66
|
}
|
|
38
67
|
async getModels(user, credentialIds) {
|
|
39
68
|
const additionalData = await (0, workflow_execute_additional_data_1.getBase)({ userId: user.id });
|
|
@@ -43,16 +72,14 @@ let ChatHubService = class ChatHubService {
|
|
|
43
72
|
return [provider, { models: [] }];
|
|
44
73
|
}
|
|
45
74
|
await this.credentialsService.getOne(user, credentialId, false);
|
|
46
|
-
const credentials = await this.credentialsHelper.getDecrypted(additionalData, {
|
|
47
|
-
id: credentialId,
|
|
48
|
-
name: api_types_1.PROVIDER_CREDENTIAL_TYPE_MAP[provider],
|
|
49
|
-
}, api_types_1.PROVIDER_CREDENTIAL_TYPE_MAP[provider], 'internal', undefined, true);
|
|
50
|
-
const apiKey = this.extractApiKey(provider, credentials);
|
|
51
|
-
if (!apiKey) {
|
|
52
|
-
return [provider, { models: [] }];
|
|
53
|
-
}
|
|
54
75
|
try {
|
|
55
|
-
|
|
76
|
+
const credentials = {
|
|
77
|
+
[api_types_1.PROVIDER_CREDENTIAL_TYPE_MAP[provider]]: { name: '', id: credentialId },
|
|
78
|
+
};
|
|
79
|
+
return [
|
|
80
|
+
provider,
|
|
81
|
+
await this.fetchModelsForProvider(provider, credentials, additionalData),
|
|
82
|
+
];
|
|
56
83
|
}
|
|
57
84
|
catch {
|
|
58
85
|
return [
|
|
@@ -70,93 +97,74 @@ let ChatHubService = class ChatHubService {
|
|
|
70
97
|
google: { models: [] },
|
|
71
98
|
});
|
|
72
99
|
}
|
|
73
|
-
async fetchModelsForProvider(provider,
|
|
100
|
+
async fetchModelsForProvider(provider, credentials, additionalData) {
|
|
74
101
|
switch (provider) {
|
|
75
102
|
case 'openai':
|
|
76
|
-
return await this.fetchOpenAiModels(
|
|
103
|
+
return await this.fetchOpenAiModels(credentials, additionalData);
|
|
77
104
|
case 'anthropic':
|
|
78
|
-
return await this.fetchAnthropicModels(
|
|
105
|
+
return await this.fetchAnthropicModels(credentials, additionalData);
|
|
79
106
|
case 'google':
|
|
80
|
-
return await this.fetchGoogleModels(
|
|
107
|
+
return await this.fetchGoogleModels(credentials, additionalData);
|
|
81
108
|
}
|
|
82
109
|
}
|
|
83
|
-
async fetchOpenAiModels(
|
|
84
|
-
const
|
|
85
|
-
method: 'GET',
|
|
86
|
-
headers: {
|
|
87
|
-
Authorization: `Bearer ${apiKey}`,
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
if (!response.ok) {
|
|
91
|
-
throw new Error(`Failed to fetch OpenAI models: ${response.statusText}`);
|
|
92
|
-
}
|
|
93
|
-
const data = await response.json();
|
|
110
|
+
async fetchOpenAiModels(credentials, additionalData) {
|
|
111
|
+
const resourceLocatorResults = await this.nodeParametersService.getResourceLocatorResults('searchModels', 'parameters.model', additionalData, providerNodeTypeMapping.openai, {}, credentials);
|
|
94
112
|
return {
|
|
95
|
-
models:
|
|
96
|
-
.filter((model) => model.id.includes('gpt') &&
|
|
97
|
-
!model.id.includes('instruct') &&
|
|
98
|
-
!model.id.includes('audio'))
|
|
99
|
-
.map((model) => ({ name: model.id })),
|
|
113
|
+
models: resourceLocatorResults.results.map((result) => ({ name: String(result.value) })),
|
|
100
114
|
};
|
|
101
115
|
}
|
|
102
|
-
async fetchAnthropicModels(
|
|
103
|
-
const
|
|
104
|
-
method: 'GET',
|
|
105
|
-
headers: {
|
|
106
|
-
'x-api-key': apiKey,
|
|
107
|
-
'anthropic-version': '2023-06-01',
|
|
108
|
-
},
|
|
109
|
-
});
|
|
110
|
-
if (!response.ok) {
|
|
111
|
-
throw new Error(`Failed to fetch Anthropic models: ${response.statusText}`);
|
|
112
|
-
}
|
|
113
|
-
const data = (await response.json());
|
|
116
|
+
async fetchAnthropicModels(credentials, additionalData) {
|
|
117
|
+
const resourceLocatorResults = await this.nodeParametersService.getResourceLocatorResults('searchModels', 'parameters.model', additionalData, providerNodeTypeMapping.anthropic, {}, credentials);
|
|
114
118
|
return {
|
|
115
|
-
models: (
|
|
116
|
-
.sort((a, b) => {
|
|
117
|
-
const dateA = new Date(a.created_at);
|
|
118
|
-
const dateB = new Date(b.created_at);
|
|
119
|
-
return dateB.getTime() - dateA.getTime();
|
|
120
|
-
})
|
|
121
|
-
.map((model) => ({ name: model.id })),
|
|
119
|
+
models: resourceLocatorResults.results.map((result) => ({ name: String(result.value) })),
|
|
122
120
|
};
|
|
123
121
|
}
|
|
124
|
-
async fetchGoogleModels(
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
122
|
+
async fetchGoogleModels(credentials, additionalData) {
|
|
123
|
+
const results = await this.nodeParametersService.getOptionsViaLoadOptions({
|
|
124
|
+
routing: {
|
|
125
|
+
request: {
|
|
126
|
+
method: 'GET',
|
|
127
|
+
url: '/v1beta/models',
|
|
128
|
+
},
|
|
129
|
+
output: {
|
|
130
|
+
postReceive: [
|
|
131
|
+
{
|
|
132
|
+
type: 'rootProperty',
|
|
133
|
+
properties: {
|
|
134
|
+
property: 'models',
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
type: 'filter',
|
|
139
|
+
properties: {
|
|
140
|
+
pass: "={{ !$responseItem.name.includes('embedding') }}",
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
type: 'setKeyValue',
|
|
145
|
+
properties: {
|
|
146
|
+
name: '={{$responseItem.name}}',
|
|
147
|
+
value: '={{$responseItem.name}}',
|
|
148
|
+
description: '={{$responseItem.description}}',
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
type: 'sort',
|
|
153
|
+
properties: {
|
|
154
|
+
key: 'name',
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
}, additionalData, providerNodeTypeMapping.google, {}, credentials);
|
|
132
161
|
return {
|
|
133
|
-
models:
|
|
134
|
-
?.filter((model) => model.name.includes('gemini') &&
|
|
135
|
-
model.supportedGenerationMethods?.includes('generateContent'))
|
|
136
|
-
.map((model) => {
|
|
137
|
-
const modelId = model.name.split('/').pop();
|
|
138
|
-
return { name: modelId };
|
|
139
|
-
}),
|
|
162
|
+
models: results.map((result) => ({ name: String(result.value) })),
|
|
140
163
|
};
|
|
141
164
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return undefined;
|
|
145
|
-
}
|
|
146
|
-
const creds = credentials;
|
|
147
|
-
switch (provider) {
|
|
148
|
-
case 'openai':
|
|
149
|
-
case 'anthropic':
|
|
150
|
-
case 'google':
|
|
151
|
-
return typeof creds.apiKey === 'string' ? creds.apiKey : undefined;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
async createChatWorkflow(user, sessionId, nodes, connections) {
|
|
165
|
+
async createChatWorkflow(user, sessionId, history, humanMessage, credentials, model) {
|
|
166
|
+
const { nodes, connections, startNodes, triggerToStartFrom } = this.prepareChatWorkflow(sessionId, history, humanMessage, credentials, model);
|
|
155
167
|
const { manager } = this.projectRepository;
|
|
156
|
-
const existing = await this.workflowRepository.findOneBy({ id: sessionId });
|
|
157
|
-
if (existing) {
|
|
158
|
-
return existing;
|
|
159
|
-
}
|
|
160
168
|
return await manager.transaction(async (trx) => {
|
|
161
169
|
const project = await this.projectRepository.getPersonalProjectForUser(user.id, trx);
|
|
162
170
|
if (!project) {
|
|
@@ -164,7 +172,6 @@ let ChatHubService = class ChatHubService {
|
|
|
164
172
|
}
|
|
165
173
|
const newWorkflow = new db_1.WorkflowEntity();
|
|
166
174
|
newWorkflow.versionId = (0, uuid_1.v4)();
|
|
167
|
-
newWorkflow.id = sessionId;
|
|
168
175
|
newWorkflow.name = `Chat ${sessionId}`;
|
|
169
176
|
newWorkflow.active = false;
|
|
170
177
|
newWorkflow.nodes = nodes;
|
|
@@ -175,40 +182,218 @@ let ChatHubService = class ChatHubService {
|
|
|
175
182
|
projectId: project.id,
|
|
176
183
|
workflow,
|
|
177
184
|
}));
|
|
178
|
-
return
|
|
185
|
+
return {
|
|
186
|
+
workflowData: {
|
|
187
|
+
...workflow,
|
|
188
|
+
nodes,
|
|
189
|
+
connections,
|
|
190
|
+
versionId: (0, uuid_1.v4)(),
|
|
191
|
+
},
|
|
192
|
+
startNodes,
|
|
193
|
+
triggerToStartFrom,
|
|
194
|
+
};
|
|
179
195
|
});
|
|
180
196
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
197
|
+
async deleteChatWorkflow(workflowId) {
|
|
198
|
+
await this.workflowRepository.delete(workflowId);
|
|
199
|
+
}
|
|
200
|
+
getErrorMessage(execution) {
|
|
201
|
+
if (execution.data.resultData.error) {
|
|
202
|
+
return execution.data.resultData.error.description ?? execution.data.resultData.error.message;
|
|
203
|
+
}
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
getAIOutput(execution) {
|
|
207
|
+
const agent = execution.data.resultData.runData[NODE_NAMES.AI_AGENT];
|
|
208
|
+
if (!agent || !Array.isArray(agent) || agent.length === 0)
|
|
184
209
|
return undefined;
|
|
185
|
-
const runIndex =
|
|
186
|
-
const mainOutputs =
|
|
210
|
+
const runIndex = agent.length - 1;
|
|
211
|
+
const mainOutputs = agent[runIndex].data?.main;
|
|
187
212
|
if (mainOutputs && Array.isArray(mainOutputs)) {
|
|
188
213
|
for (const branch of mainOutputs) {
|
|
189
214
|
if (branch && Array.isArray(branch) && branch.length > 0 && branch[0].json?.output) {
|
|
190
|
-
|
|
215
|
+
if (typeof branch[0].json.output === 'string') {
|
|
216
|
+
return branch[0].json.output;
|
|
217
|
+
}
|
|
191
218
|
}
|
|
192
219
|
}
|
|
193
220
|
}
|
|
194
221
|
return undefined;
|
|
195
222
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
223
|
+
getCredentialId(provider, credentials) {
|
|
224
|
+
return credentials[api_types_1.PROVIDER_CREDENTIAL_TYPE_MAP[provider]]?.id ?? null;
|
|
225
|
+
}
|
|
226
|
+
async sendHumanMessage(res, user, payload) {
|
|
227
|
+
const { sessionId, messageId, replyId, message } = payload;
|
|
228
|
+
const selectedModel = {
|
|
229
|
+
...payload.model,
|
|
230
|
+
credentialId: this.getCredentialId(payload.model.provider, payload.credentials),
|
|
231
|
+
};
|
|
232
|
+
const session = await this.getChatSession(user, sessionId, selectedModel, true, message);
|
|
233
|
+
if (payload.previousMessageId) {
|
|
234
|
+
const previousMessage = await this.messageRepository.getOneById(payload.previousMessageId, sessionId);
|
|
235
|
+
if (!previousMessage) {
|
|
236
|
+
throw new bad_request_error_1.BadRequestError('The previous message does not exist in the session');
|
|
237
|
+
}
|
|
201
238
|
}
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
239
|
+
const messages = Object.fromEntries((session.messages ?? []).map((m) => [m.id, m]));
|
|
240
|
+
const history = this.buildMessageHistory(messages, payload.previousMessageId);
|
|
241
|
+
await this.saveHumanMessage(payload, user, payload.previousMessageId, selectedModel);
|
|
242
|
+
const workflow = await this.createChatWorkflow(user, session.id, history, message, payload.credentials, payload.model);
|
|
243
|
+
try {
|
|
244
|
+
await this.executeChatWorkflow(res, user, workflow, replyId, sessionId, messageId, selectedModel);
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
await this.deleteChatWorkflow(workflow.workflowData.id);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async editHumanMessage(res, user, payload) {
|
|
251
|
+
const { sessionId, editId, messageId, message, replyId } = payload;
|
|
252
|
+
const selectedModel = {
|
|
253
|
+
...payload.model,
|
|
254
|
+
credentialId: this.getCredentialId(payload.model.provider, payload.credentials),
|
|
255
|
+
};
|
|
256
|
+
const session = await this.getChatSession(user, sessionId);
|
|
257
|
+
const messageToEdit = await this.getChatMessage(session.id, editId);
|
|
258
|
+
if (messageToEdit.type !== 'human') {
|
|
259
|
+
throw new bad_request_error_1.BadRequestError('Can only edit human messages');
|
|
260
|
+
}
|
|
261
|
+
const messages = Object.fromEntries((session.messages ?? []).map((m) => [m.id, m]));
|
|
262
|
+
const history = this.buildMessageHistory(messages, messageToEdit.previousMessageId);
|
|
263
|
+
const revisionOfMessageId = messageToEdit.revisionOfMessageId ?? messageToEdit.id;
|
|
264
|
+
await this.saveHumanMessage(payload, user, messageToEdit.previousMessageId, selectedModel, revisionOfMessageId);
|
|
265
|
+
const workflow = await this.createChatWorkflow(user, session.id, history, message, payload.credentials, payload.model);
|
|
266
|
+
try {
|
|
267
|
+
await this.executeChatWorkflow(res, user, workflow, replyId, sessionId, messageId, selectedModel);
|
|
268
|
+
}
|
|
269
|
+
finally {
|
|
270
|
+
await this.deleteChatWorkflow(workflow.workflowData.id);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
async regenerateAIMessage(res, user, payload) {
|
|
274
|
+
const { sessionId, retryId, replyId } = payload;
|
|
275
|
+
const selectedModel = {
|
|
276
|
+
...payload.model,
|
|
277
|
+
credentialId: this.getCredentialId(payload.model.provider, payload.credentials),
|
|
278
|
+
};
|
|
279
|
+
const session = await this.getChatSession(user, sessionId);
|
|
280
|
+
const messageToRetry = await this.getChatMessage(session.id, retryId);
|
|
281
|
+
if (messageToRetry.type !== 'ai') {
|
|
282
|
+
throw new bad_request_error_1.BadRequestError('Can only retry AI messages');
|
|
283
|
+
}
|
|
284
|
+
const messages = Object.fromEntries((session.messages ?? []).map((m) => [m.id, m]));
|
|
285
|
+
const history = this.buildMessageHistory(messages, messageToRetry.previousMessageId);
|
|
286
|
+
const lastHumanMessage = history.filter((m) => m.type === 'human').pop();
|
|
287
|
+
if (!lastHumanMessage) {
|
|
288
|
+
throw new bad_request_error_1.BadRequestError('No human message found to base the retry on');
|
|
289
|
+
}
|
|
290
|
+
const lastHumanMessageIndex = history.indexOf(lastHumanMessage);
|
|
291
|
+
if (lastHumanMessageIndex !== -1) {
|
|
292
|
+
history.splice(lastHumanMessageIndex + 1);
|
|
293
|
+
}
|
|
294
|
+
const retryOfMessageId = messageToRetry.retryOfMessageId ?? messageToRetry.id;
|
|
295
|
+
const workflow = await this.createChatWorkflow(user, session.id, history, lastHumanMessage ? lastHumanMessage.content : '', payload.credentials, payload.model);
|
|
296
|
+
try {
|
|
297
|
+
await this.executeChatWorkflow(res, user, workflow, replyId, sessionId, lastHumanMessage.id, selectedModel, retryOfMessageId);
|
|
298
|
+
}
|
|
299
|
+
finally {
|
|
300
|
+
await this.deleteChatWorkflow(workflow.workflowData.id);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
async stopGeneration(user, sessionId, messageId) {
|
|
304
|
+
const session = await this.getChatSession(user, sessionId);
|
|
305
|
+
const message = await this.getChatMessage(session.id, messageId, [
|
|
306
|
+
'execution',
|
|
307
|
+
'execution.workflow',
|
|
308
|
+
]);
|
|
309
|
+
if (message.type !== 'ai') {
|
|
310
|
+
throw new bad_request_error_1.BadRequestError('Can only stop AI messages');
|
|
311
|
+
}
|
|
312
|
+
if (!message.executionId || !message.execution) {
|
|
313
|
+
throw new bad_request_error_1.BadRequestError('Message is not associated with a workflow execution');
|
|
314
|
+
}
|
|
315
|
+
if (message.status !== 'running') {
|
|
316
|
+
throw new bad_request_error_1.BadRequestError('Can only stop messages that are currently running');
|
|
317
|
+
}
|
|
318
|
+
await this.executionService.stop(message.execution.id, [message.execution.workflowId]);
|
|
319
|
+
await this.messageRepository.updateChatMessage(messageId, { status: 'cancelled' });
|
|
320
|
+
}
|
|
321
|
+
async executeChatWorkflow(res, user, workflow, replyId, sessionId, previousMessageId, selectedModel, retryOfMessageId) {
|
|
322
|
+
const { workflowData, startNodes, triggerToStartFrom } = workflow;
|
|
323
|
+
this.logger.debug(`Starting execution of workflow "${workflowData.name}" with ID ${workflowData.id}`);
|
|
324
|
+
const { executionId } = await this.workflowExecutionService.executeManually({
|
|
325
|
+
workflowData,
|
|
326
|
+
startNodes,
|
|
327
|
+
triggerToStartFrom,
|
|
328
|
+
}, user, undefined, true, res);
|
|
329
|
+
if (!executionId) {
|
|
330
|
+
throw new n8n_workflow_1.OperationalError('There was a problem starting the chat execution.');
|
|
331
|
+
}
|
|
332
|
+
await this.saveAIMessage({
|
|
333
|
+
id: replyId,
|
|
334
|
+
sessionId,
|
|
335
|
+
executionId,
|
|
336
|
+
previousMessageId,
|
|
337
|
+
message: '',
|
|
338
|
+
selectedModel,
|
|
339
|
+
retryOfMessageId,
|
|
340
|
+
status: 'running',
|
|
211
341
|
});
|
|
342
|
+
try {
|
|
343
|
+
let result;
|
|
344
|
+
try {
|
|
345
|
+
result = await this.activeExecutions.getPostExecutePromise(executionId);
|
|
346
|
+
if (!result) {
|
|
347
|
+
throw new n8n_workflow_1.OperationalError('There was a problem executing the chat workflow.');
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
if (error instanceof n8n_workflow_1.ManualExecutionCancelledError) {
|
|
352
|
+
const execution = await this.executionRepository.findWithUnflattenedData(executionId, [
|
|
353
|
+
workflowData.id,
|
|
354
|
+
]);
|
|
355
|
+
if (!execution) {
|
|
356
|
+
throw new n8n_workflow_1.OperationalError(`Could not find execution with ID ${executionId}`);
|
|
357
|
+
}
|
|
358
|
+
if (execution.status === 'canceled') {
|
|
359
|
+
const message = 'Generation cancelled.';
|
|
360
|
+
await this.messageRepository.updateChatMessage(replyId, {
|
|
361
|
+
content: message,
|
|
362
|
+
status: 'cancelled',
|
|
363
|
+
});
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
throw error;
|
|
368
|
+
}
|
|
369
|
+
const execution = await this.executionRepository.findWithUnflattenedData(executionId, [
|
|
370
|
+
workflowData.id,
|
|
371
|
+
]);
|
|
372
|
+
if (!execution) {
|
|
373
|
+
throw new n8n_workflow_1.OperationalError(`Could not find execution with ID ${executionId}`);
|
|
374
|
+
}
|
|
375
|
+
if (!execution.status || execution.status !== 'success') {
|
|
376
|
+
const message = this.getErrorMessage(execution) ?? 'Failed to generate a response';
|
|
377
|
+
throw new n8n_workflow_1.OperationalError(message);
|
|
378
|
+
}
|
|
379
|
+
const message = this.getAIOutput(execution);
|
|
380
|
+
if (!message) {
|
|
381
|
+
throw new n8n_workflow_1.OperationalError('No response generated');
|
|
382
|
+
}
|
|
383
|
+
await this.messageRepository.updateChatMessage(replyId, {
|
|
384
|
+
content: message,
|
|
385
|
+
status: 'success',
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
390
|
+
await this.messageRepository.updateChatMessage(replyId, {
|
|
391
|
+
content: `Error: ${message}`,
|
|
392
|
+
status: 'error',
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
prepareChatWorkflow(sessionId, history, humanMessage, credentials, model) {
|
|
212
397
|
const nodes = [
|
|
213
398
|
{
|
|
214
399
|
parameters: {
|
|
@@ -220,7 +405,7 @@ let ChatHubService = class ChatHubService {
|
|
|
220
405
|
typeVersion: 1.3,
|
|
221
406
|
position: [0, 0],
|
|
222
407
|
id: (0, uuid_1.v4)(),
|
|
223
|
-
name:
|
|
408
|
+
name: NODE_NAMES.CHAT_TRIGGER,
|
|
224
409
|
webhookId: (0, uuid_1.v4)(),
|
|
225
410
|
},
|
|
226
411
|
{
|
|
@@ -229,69 +414,103 @@ let ChatHubService = class ChatHubService {
|
|
|
229
414
|
text: "={{ $('When chat message received').item.json.chatInput }}",
|
|
230
415
|
options: {
|
|
231
416
|
enableStreaming: true,
|
|
417
|
+
maxTokensFromMemory: (0, context_limits_1.getMaxContextWindowTokens)(model.provider, model.model),
|
|
232
418
|
},
|
|
233
419
|
},
|
|
234
420
|
type: n8n_workflow_1.AGENT_LANGCHAIN_NODE_TYPE,
|
|
235
421
|
typeVersion: 3,
|
|
236
422
|
position: [600, 0],
|
|
237
423
|
id: (0, uuid_1.v4)(),
|
|
238
|
-
name:
|
|
424
|
+
name: NODE_NAMES.AI_AGENT,
|
|
239
425
|
},
|
|
240
|
-
this.createModelNode(
|
|
426
|
+
this.createModelNode(credentials, model),
|
|
241
427
|
{
|
|
242
428
|
parameters: {
|
|
243
429
|
sessionIdType: 'customKey',
|
|
244
|
-
sessionKey:
|
|
430
|
+
sessionKey: `={{ $('${NODE_NAMES.CHAT_TRIGGER}').item.json.sessionId }}`,
|
|
431
|
+
contextWindowLength: 20,
|
|
245
432
|
},
|
|
246
433
|
type: '@n8n/n8n-nodes-langchain.memoryBufferWindow',
|
|
247
434
|
typeVersion: 1.3,
|
|
248
|
-
position: [
|
|
435
|
+
position: [480, 208],
|
|
249
436
|
id: (0, uuid_1.v4)(),
|
|
250
|
-
name:
|
|
437
|
+
name: NODE_NAMES.MEMORY,
|
|
251
438
|
},
|
|
252
439
|
{
|
|
253
440
|
parameters: {
|
|
254
441
|
mode: 'insert',
|
|
442
|
+
insertMode: 'override',
|
|
255
443
|
messages: {
|
|
256
|
-
messageValues:
|
|
444
|
+
messageValues: history.map((message) => {
|
|
445
|
+
const typeMap = {
|
|
446
|
+
human: 'user',
|
|
447
|
+
ai: 'ai',
|
|
448
|
+
system: 'system',
|
|
449
|
+
};
|
|
450
|
+
return {
|
|
451
|
+
type: typeMap[message.type] || 'system',
|
|
452
|
+
message: message.content,
|
|
453
|
+
hideFromUI: false,
|
|
454
|
+
};
|
|
455
|
+
}),
|
|
257
456
|
},
|
|
258
457
|
},
|
|
259
458
|
type: '@n8n/n8n-nodes-langchain.memoryManager',
|
|
260
459
|
typeVersion: 1.1,
|
|
261
|
-
position: [
|
|
460
|
+
position: [224, 0],
|
|
262
461
|
id: (0, uuid_1.v4)(),
|
|
263
|
-
name:
|
|
462
|
+
name: NODE_NAMES.RESTORE_CHAT_MEMORY,
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
parameters: {
|
|
466
|
+
mode: 'delete',
|
|
467
|
+
deleteMode: 'all',
|
|
468
|
+
},
|
|
469
|
+
type: '@n8n/n8n-nodes-langchain.memoryManager',
|
|
470
|
+
typeVersion: 1.1,
|
|
471
|
+
position: [976, 0],
|
|
472
|
+
id: (0, uuid_1.v4)(),
|
|
473
|
+
name: NODE_NAMES.CLEAR_CHAT_MEMORY,
|
|
264
474
|
},
|
|
265
475
|
];
|
|
266
476
|
const connections = {
|
|
267
|
-
|
|
268
|
-
main: [
|
|
477
|
+
[NODE_NAMES.CHAT_TRIGGER]: {
|
|
478
|
+
main: [
|
|
479
|
+
[{ node: NODE_NAMES.RESTORE_CHAT_MEMORY, type: n8n_workflow_1.NodeConnectionTypes.Main, index: 0 }],
|
|
480
|
+
],
|
|
269
481
|
},
|
|
270
|
-
|
|
271
|
-
main: [[{ node:
|
|
482
|
+
[NODE_NAMES.RESTORE_CHAT_MEMORY]: {
|
|
483
|
+
main: [[{ node: NODE_NAMES.AI_AGENT, type: n8n_workflow_1.NodeConnectionTypes.Main, index: 0 }]],
|
|
272
484
|
},
|
|
273
|
-
|
|
274
|
-
ai_languageModel: [
|
|
485
|
+
[NODE_NAMES.CHAT_MODEL]: {
|
|
486
|
+
ai_languageModel: [
|
|
487
|
+
[{ node: NODE_NAMES.AI_AGENT, type: n8n_workflow_1.NodeConnectionTypes.AiLanguageModel, index: 0 }],
|
|
488
|
+
],
|
|
275
489
|
},
|
|
276
|
-
|
|
490
|
+
[NODE_NAMES.MEMORY]: {
|
|
277
491
|
ai_memory: [
|
|
278
492
|
[
|
|
279
|
-
{ node:
|
|
280
|
-
{ node:
|
|
493
|
+
{ node: NODE_NAMES.AI_AGENT, type: n8n_workflow_1.NodeConnectionTypes.AiMemory, index: 0 },
|
|
494
|
+
{ node: NODE_NAMES.RESTORE_CHAT_MEMORY, type: n8n_workflow_1.NodeConnectionTypes.AiMemory, index: 0 },
|
|
495
|
+
{ node: NODE_NAMES.CLEAR_CHAT_MEMORY, type: n8n_workflow_1.NodeConnectionTypes.AiMemory, index: 0 },
|
|
496
|
+
],
|
|
497
|
+
],
|
|
498
|
+
},
|
|
499
|
+
[NODE_NAMES.AI_AGENT]: {
|
|
500
|
+
main: [
|
|
501
|
+
[
|
|
502
|
+
{
|
|
503
|
+
node: NODE_NAMES.CLEAR_CHAT_MEMORY,
|
|
504
|
+
type: n8n_workflow_1.NodeConnectionTypes.Main,
|
|
505
|
+
index: 0,
|
|
506
|
+
},
|
|
281
507
|
],
|
|
282
508
|
],
|
|
283
509
|
},
|
|
284
|
-
};
|
|
285
|
-
const workflow = await this.createChatWorkflow(user, payload.sessionId, nodes, connections);
|
|
286
|
-
const workflowData = {
|
|
287
|
-
...workflow,
|
|
288
|
-
nodes,
|
|
289
|
-
connections,
|
|
290
|
-
versionId: (0, uuid_1.v4)(),
|
|
291
510
|
};
|
|
292
511
|
const startNodes = [{ name: 'Restore Chat Memory', sourceData: null }];
|
|
293
512
|
const triggerToStartFrom = {
|
|
294
|
-
name:
|
|
513
|
+
name: NODE_NAMES.CHAT_TRIGGER,
|
|
295
514
|
data: {
|
|
296
515
|
startTime: Date.now(),
|
|
297
516
|
executionTime: 0,
|
|
@@ -302,9 +521,9 @@ let ChatHubService = class ChatHubService {
|
|
|
302
521
|
[
|
|
303
522
|
{
|
|
304
523
|
json: {
|
|
305
|
-
sessionId
|
|
524
|
+
sessionId,
|
|
306
525
|
action: 'sendMessage',
|
|
307
|
-
chatInput:
|
|
526
|
+
chatInput: humanMessage,
|
|
308
527
|
},
|
|
309
528
|
},
|
|
310
529
|
],
|
|
@@ -313,53 +532,74 @@ let ChatHubService = class ChatHubService {
|
|
|
313
532
|
source: [null],
|
|
314
533
|
},
|
|
315
534
|
};
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
535
|
+
return { nodes, connections, startNodes, triggerToStartFrom };
|
|
536
|
+
}
|
|
537
|
+
async saveHumanMessage(payload, user, previousMessageId, selectedModel, revisionOfMessageId) {
|
|
538
|
+
await this.messageRepository.createChatMessage({
|
|
539
|
+
id: payload.messageId,
|
|
540
|
+
sessionId: payload.sessionId,
|
|
541
|
+
type: 'human',
|
|
542
|
+
name: user.firstName || 'User',
|
|
543
|
+
status: 'success',
|
|
544
|
+
content: payload.message,
|
|
545
|
+
previousMessageId,
|
|
546
|
+
revisionOfMessageId,
|
|
547
|
+
...selectedModel,
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
async saveAIMessage({ id, sessionId, executionId, previousMessageId, message, selectedModel, retryOfMessageId, status, }) {
|
|
551
|
+
await this.messageRepository.createChatMessage({
|
|
552
|
+
id,
|
|
553
|
+
sessionId,
|
|
554
|
+
previousMessageId,
|
|
555
|
+
executionId: parseInt(executionId, 10),
|
|
556
|
+
type: 'ai',
|
|
557
|
+
name: 'AI',
|
|
558
|
+
status,
|
|
559
|
+
content: message,
|
|
560
|
+
retryOfMessageId,
|
|
561
|
+
...selectedModel,
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
async getChatSession(user, sessionId, selectedModel, initialize = false, title = null) {
|
|
565
|
+
const existing = await this.sessionRepository.getOneById(sessionId, user.id);
|
|
566
|
+
if (existing) {
|
|
567
|
+
return existing;
|
|
324
568
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
throw new n8n_workflow_1.OperationalError('There was a problem executing the chat workflow.');
|
|
569
|
+
else if (!initialize) {
|
|
570
|
+
throw new not_found_error_1.NotFoundError('Chat session not found');
|
|
328
571
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
message,
|
|
341
|
-
type: 'ai',
|
|
342
|
-
createdAt: new Date(),
|
|
343
|
-
});
|
|
572
|
+
return await this.sessionRepository.createChatSession({
|
|
573
|
+
id: sessionId,
|
|
574
|
+
ownerId: user.id,
|
|
575
|
+
title: title ?? 'New Chat',
|
|
576
|
+
...selectedModel,
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
async getChatMessage(sessionId, messageId, relations = []) {
|
|
580
|
+
const message = await this.messageRepository.getOneById(messageId, sessionId, relations);
|
|
581
|
+
if (!message) {
|
|
582
|
+
throw new not_found_error_1.NotFoundError('Chat message not found');
|
|
344
583
|
}
|
|
584
|
+
return message;
|
|
345
585
|
}
|
|
346
|
-
createModelNode(
|
|
586
|
+
createModelNode(credentials, { provider, model }) {
|
|
347
587
|
const common = {
|
|
348
588
|
position: [600, 200],
|
|
349
589
|
id: (0, uuid_1.v4)(),
|
|
350
590
|
name: 'Chat Model',
|
|
351
|
-
credentials
|
|
591
|
+
credentials,
|
|
592
|
+
type: providerNodeTypeMapping[provider].name,
|
|
593
|
+
typeVersion: providerNodeTypeMapping[provider].version,
|
|
352
594
|
};
|
|
353
|
-
switch (
|
|
595
|
+
switch (provider) {
|
|
354
596
|
case 'openai':
|
|
355
597
|
return {
|
|
356
598
|
...common,
|
|
357
599
|
parameters: {
|
|
358
|
-
model: { __rl: true, mode: 'list', value:
|
|
600
|
+
model: { __rl: true, mode: 'list', value: model },
|
|
359
601
|
options: {},
|
|
360
602
|
},
|
|
361
|
-
type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',
|
|
362
|
-
typeVersion: 1.2,
|
|
363
603
|
};
|
|
364
604
|
case 'anthropic':
|
|
365
605
|
return {
|
|
@@ -368,169 +608,111 @@ let ChatHubService = class ChatHubService {
|
|
|
368
608
|
model: {
|
|
369
609
|
__rl: true,
|
|
370
610
|
mode: 'list',
|
|
371
|
-
value:
|
|
372
|
-
cachedResultName:
|
|
611
|
+
value: model,
|
|
612
|
+
cachedResultName: model,
|
|
373
613
|
},
|
|
374
614
|
options: {},
|
|
375
615
|
},
|
|
376
|
-
type: '@n8n/n8n-nodes-langchain.lmChatAnthropic',
|
|
377
|
-
typeVersion: 1.3,
|
|
378
616
|
};
|
|
379
617
|
case 'google':
|
|
380
618
|
return {
|
|
381
619
|
...common,
|
|
382
620
|
parameters: {
|
|
383
|
-
model: { __rl: true, mode: 'list', value:
|
|
621
|
+
model: { __rl: true, mode: 'list', value: model },
|
|
384
622
|
options: {},
|
|
385
623
|
},
|
|
386
|
-
type: '@n8n/n8n-nodes-langchain.lmChatGoogleGemini',
|
|
387
|
-
typeVersion: 1.2,
|
|
388
624
|
};
|
|
389
625
|
}
|
|
390
626
|
}
|
|
391
|
-
async getConversations() {
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
createdAt: twoWeeksAgo.toISOString(),
|
|
425
|
-
updatedAt: twoWeeksAgo.toISOString(),
|
|
627
|
+
async getConversations(userId) {
|
|
628
|
+
const sessions = await this.sessionRepository.getManyByUserId(userId);
|
|
629
|
+
return sessions.map((session) => ({
|
|
630
|
+
id: session.id,
|
|
631
|
+
title: session.title,
|
|
632
|
+
ownerId: session.ownerId,
|
|
633
|
+
lastMessageAt: session.lastMessageAt?.toISOString() ?? null,
|
|
634
|
+
credentialId: session.credentialId,
|
|
635
|
+
provider: session.provider,
|
|
636
|
+
model: session.model,
|
|
637
|
+
workflowId: session.workflowId,
|
|
638
|
+
createdAt: session.createdAt.toISOString(),
|
|
639
|
+
updatedAt: session.updatedAt.toISOString(),
|
|
640
|
+
}));
|
|
641
|
+
}
|
|
642
|
+
async getConversation(userId, sessionId) {
|
|
643
|
+
const session = await this.sessionRepository.getOneById(sessionId, userId);
|
|
644
|
+
if (!session) {
|
|
645
|
+
throw new not_found_error_1.NotFoundError('Chat session not found');
|
|
646
|
+
}
|
|
647
|
+
const messages = await this.messageRepository.getManyBySessionId(sessionId);
|
|
648
|
+
return {
|
|
649
|
+
session: {
|
|
650
|
+
id: session.id,
|
|
651
|
+
title: session.title,
|
|
652
|
+
ownerId: session.ownerId,
|
|
653
|
+
lastMessageAt: session.lastMessageAt?.toISOString() ?? null,
|
|
654
|
+
credentialId: session.credentialId,
|
|
655
|
+
provider: session.provider,
|
|
656
|
+
model: session.model,
|
|
657
|
+
workflowId: session.workflowId,
|
|
658
|
+
createdAt: session.createdAt.toISOString(),
|
|
659
|
+
updatedAt: session.updatedAt.toISOString(),
|
|
426
660
|
},
|
|
427
|
-
{
|
|
428
|
-
|
|
429
|
-
title: 'Docker Deployment Questions',
|
|
430
|
-
createdAt: twoMonthsAgo.toISOString(),
|
|
431
|
-
updatedAt: twoMonthsAgo.toISOString(),
|
|
661
|
+
conversation: {
|
|
662
|
+
messages: Object.fromEntries(messages.map((m) => [m.id, this.convertMessageToDto(m)])),
|
|
432
663
|
},
|
|
433
|
-
|
|
664
|
+
};
|
|
434
665
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
},
|
|
453
|
-
{
|
|
454
|
-
id: '650e8400-e29b-41d4-a716-446655440003',
|
|
455
|
-
conversationId,
|
|
456
|
-
role: 'user',
|
|
457
|
-
content: 'Yes, I want to automate sending emails when a new row is added to a Google Sheet.',
|
|
458
|
-
createdAt: new Date('2025-01-08T10:05:00Z').toISOString(),
|
|
459
|
-
},
|
|
460
|
-
{
|
|
461
|
-
id: '650e8400-e29b-41d4-a716-446655440004',
|
|
462
|
-
conversationId,
|
|
463
|
-
role: 'assistant',
|
|
464
|
-
content: "Perfect! Here's how to set that up:\n\n1. Add a **Google Sheets Trigger** node\n - Select 'On Row Added'\n - Connect your Google account\n - Choose your spreadsheet\n\n2. Add a **Gmail** node\n - Connect your Gmail account\n - Set the recipient email\n - Use expressions to include data from the sheet\n\n3. Activate the workflow\n\nWould you like more details on any of these steps?",
|
|
465
|
-
createdAt: new Date('2025-01-08T10:05:45Z').toISOString(),
|
|
466
|
-
},
|
|
467
|
-
],
|
|
468
|
-
'3a8f5c2d-1e9b-4d7a-8c3e-6f2a9b4d8e1c': [
|
|
469
|
-
{
|
|
470
|
-
id: '650e8400-e29b-41d4-a716-446655440011',
|
|
471
|
-
conversationId,
|
|
472
|
-
role: 'user',
|
|
473
|
-
content: 'What are some creative workflow automation ideas?',
|
|
474
|
-
createdAt: new Date('2025-01-07T14:30:00Z').toISOString(),
|
|
475
|
-
},
|
|
476
|
-
{
|
|
477
|
-
id: '650e8400-e29b-41d4-a716-446655440012',
|
|
478
|
-
conversationId,
|
|
479
|
-
role: 'assistant',
|
|
480
|
-
content: 'Here are some creative workflow automation ideas:\n\n**Social Media Automation:**\n- Auto-post blog content to multiple platforms\n- Monitor mentions and send notifications\n- Generate reports on engagement metrics\n\n**Business Operations:**\n- Sync data between CRM and accounting software\n- Auto-generate invoices from project completion\n- Send weekly team reports\n\n**Personal Productivity:**\n- Save email attachments to cloud storage\n- Create calendar events from emails\n- Track expenses from receipts\n\nWhich area interests you most?',
|
|
481
|
-
createdAt: new Date('2025-01-07T14:32:00Z').toISOString(),
|
|
482
|
-
},
|
|
483
|
-
{
|
|
484
|
-
id: '650e8400-e29b-41d4-a716-446655440013',
|
|
485
|
-
conversationId,
|
|
486
|
-
role: 'user',
|
|
487
|
-
content: 'The social media automation sounds great! How complex is it to set up?',
|
|
488
|
-
createdAt: new Date('2025-01-07T14:45:00Z').toISOString(),
|
|
489
|
-
},
|
|
490
|
-
{
|
|
491
|
-
id: '650e8400-e29b-41d4-a716-446655440014',
|
|
492
|
-
conversationId,
|
|
493
|
-
role: 'assistant',
|
|
494
|
-
content: "It's actually quite straightforward! For auto-posting to multiple platforms:\n\n**Difficulty: Beginner-friendly**\n\n1. Use an **RSS** trigger to monitor your blog\n2. Add **Twitter**, **LinkedIn**, and **Facebook** nodes\n3. Format your message with expressions\n4. Add conditions to customize per platform\n\nMost of the work is just connecting your accounts. The actual workflow can be set up in under 30 minutes!\n\nWant me to walk you through the specific nodes you'll need?",
|
|
495
|
-
createdAt: new Date('2025-01-07T14:47:00Z').toISOString(),
|
|
496
|
-
},
|
|
497
|
-
],
|
|
498
|
-
'9b2e4f6a-7d1c-4a8b-9e3f-5c7d2a8b4e6f': [
|
|
499
|
-
{
|
|
500
|
-
id: '650e8400-e29b-41d4-a716-446655440021',
|
|
501
|
-
conversationId,
|
|
502
|
-
role: 'user',
|
|
503
|
-
content: "I'm having trouble integrating with the Stripe API. Any tips?",
|
|
504
|
-
createdAt: new Date('2025-01-06T09:00:00Z').toISOString(),
|
|
505
|
-
},
|
|
506
|
-
{
|
|
507
|
-
id: '650e8400-e29b-41d4-a716-446655440022',
|
|
508
|
-
conversationId,
|
|
509
|
-
role: 'assistant',
|
|
510
|
-
content: "I'd be happy to help with Stripe integration! What specific issue are you encountering?\n\n**Common Stripe integration patterns in n8n:**\n\n1. **Webhook-based** (Recommended)\n - Stripe sends events to n8n\n - Great for payment notifications\n - Real-time updates\n\n2. **Polling-based**\n - Check for new data periodically\n - Good for reports and syncing\n\n3. **Manual trigger**\n - Run on-demand operations\n - Create customers, charges, etc.\n\nWhat's your use case?",
|
|
511
|
-
createdAt: new Date('2025-01-06T09:02:00Z').toISOString(),
|
|
512
|
-
},
|
|
513
|
-
{
|
|
514
|
-
id: '650e8400-e29b-41d4-a716-446655440023',
|
|
515
|
-
conversationId,
|
|
516
|
-
role: 'user',
|
|
517
|
-
content: 'I want to receive notifications when a payment succeeds and create an invoice in my accounting software.',
|
|
518
|
-
createdAt: new Date('2025-01-06T09:15:00Z').toISOString(),
|
|
519
|
-
},
|
|
520
|
-
{
|
|
521
|
-
id: '650e8400-e29b-41d4-a716-446655440024',
|
|
522
|
-
conversationId,
|
|
523
|
-
role: 'assistant',
|
|
524
|
-
content: "Perfect use case for webhooks! Here's the setup:\n\n**Step 1: n8n Webhook**\n- Add a Webhook node\n- Set method to POST\n- Copy the webhook URL\n\n**Step 2: Stripe Dashboard**\n- Go to Developers → Webhooks\n- Add endpoint with your n8n URL\n- Select `payment_intent.succeeded` event\n\n**Step 3: Process Payment Data**\n- Add a **Function** node to extract payment details\n- Parse customer, amount, currency\n\n**Step 4: Create Invoice**\n- Add your accounting software node (QuickBooks, Xero, etc.)\n- Map payment data to invoice fields\n\n**Step 5: Send Notification**\n- Add Email/Slack node for confirmation\n\nWant the specific code for the Function node?",
|
|
525
|
-
createdAt: new Date('2025-01-06T09:18:00Z').toISOString(),
|
|
526
|
-
},
|
|
527
|
-
],
|
|
666
|
+
convertMessageToDto(message) {
|
|
667
|
+
return {
|
|
668
|
+
id: message.id,
|
|
669
|
+
sessionId: message.sessionId,
|
|
670
|
+
type: message.type,
|
|
671
|
+
name: message.name,
|
|
672
|
+
content: message.content,
|
|
673
|
+
provider: message.provider,
|
|
674
|
+
model: message.model,
|
|
675
|
+
workflowId: message.workflowId,
|
|
676
|
+
executionId: message.executionId,
|
|
677
|
+
status: message.status,
|
|
678
|
+
createdAt: message.createdAt.toISOString(),
|
|
679
|
+
updatedAt: message.updatedAt.toISOString(),
|
|
680
|
+
previousMessageId: message.previousMessageId,
|
|
681
|
+
retryOfMessageId: message.retryOfMessageId,
|
|
682
|
+
revisionOfMessageId: message.revisionOfMessageId,
|
|
528
683
|
};
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
684
|
+
}
|
|
685
|
+
buildMessageHistory(messages, lastMessageId) {
|
|
686
|
+
if (!lastMessageId)
|
|
687
|
+
return [];
|
|
688
|
+
const visited = new Set();
|
|
689
|
+
const historyIds = [];
|
|
690
|
+
let current = lastMessageId;
|
|
691
|
+
while (current && !visited.has(current)) {
|
|
692
|
+
historyIds.unshift(current);
|
|
693
|
+
visited.add(current);
|
|
694
|
+
current = messages[current]?.previousMessageId ?? null;
|
|
695
|
+
}
|
|
696
|
+
const history = historyIds.flatMap((id) => messages[id] ?? []);
|
|
697
|
+
return history;
|
|
698
|
+
}
|
|
699
|
+
async deleteAllSessions() {
|
|
700
|
+
const result = await this.sessionRepository.deleteAll();
|
|
701
|
+
return result;
|
|
702
|
+
}
|
|
703
|
+
async updateSessionTitle(userId, sessionId, title) {
|
|
704
|
+
const session = await this.sessionRepository.getOneById(sessionId, userId);
|
|
705
|
+
if (!session) {
|
|
706
|
+
throw new not_found_error_1.NotFoundError('Session not found');
|
|
707
|
+
}
|
|
708
|
+
return await this.sessionRepository.updateChatTitle(sessionId, title);
|
|
709
|
+
}
|
|
710
|
+
async deleteSession(userId, sessionId) {
|
|
711
|
+
const session = await this.sessionRepository.getOneById(sessionId, userId);
|
|
712
|
+
if (!session) {
|
|
713
|
+
throw new not_found_error_1.NotFoundError('Session not found');
|
|
532
714
|
}
|
|
533
|
-
|
|
715
|
+
await this.sessionRepository.deleteChatHubSession(sessionId);
|
|
534
716
|
}
|
|
535
717
|
};
|
|
536
718
|
exports.ChatHubService = ChatHubService;
|
|
@@ -538,12 +720,15 @@ exports.ChatHubService = ChatHubService = __decorate([
|
|
|
538
720
|
(0, di_1.Service)(),
|
|
539
721
|
__metadata("design:paramtypes", [backend_common_1.Logger,
|
|
540
722
|
credentials_service_1.CredentialsService,
|
|
541
|
-
|
|
723
|
+
execution_service_1.ExecutionService,
|
|
724
|
+
dynamic_node_parameters_service_1.DynamicNodeParametersService,
|
|
542
725
|
db_1.ExecutionRepository,
|
|
543
726
|
workflow_execution_service_1.WorkflowExecutionService,
|
|
544
727
|
db_1.WorkflowRepository,
|
|
545
728
|
db_1.ProjectRepository,
|
|
546
729
|
db_1.SharedWorkflowRepository,
|
|
547
|
-
active_executions_1.ActiveExecutions
|
|
730
|
+
active_executions_1.ActiveExecutions,
|
|
731
|
+
chat_session_repository_1.ChatHubSessionRepository,
|
|
732
|
+
chat_message_repository_1.ChatHubMessageRepository])
|
|
548
733
|
], ChatHubService);
|
|
549
734
|
//# sourceMappingURL=chat-hub.service.js.map
|