n8n 1.117.2 → 1.118.0
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/auth/auth.service.d.ts +2 -1
- package/dist/auth/auth.service.js +5 -2
- package/dist/auth/auth.service.js.map +1 -1
- package/dist/build.tsbuildinfo +1 -1
- package/dist/controllers/project.controller.d.ts +2 -0
- package/dist/controllers/users.controller.d.ts +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 +16 -4
- package/dist/environments.ee/source-control/source-control-export.service.ee.js.map +1 -1
- package/dist/environments.ee/source-control/source-control-import.service.ee.d.ts +10 -3
- package/dist/environments.ee/source-control/source-control-import.service.ee.js +49 -20
- 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.d.ts +8 -6
- package/dist/environments.ee/source-control/source-control-status.service.ee.js +25 -3
- package/dist/environments.ee/source-control/source-control-status.service.ee.js.map +1 -1
- package/dist/environments.ee/source-control/source-control.controller.ee.d.ts +8 -8
- package/dist/environments.ee/source-control/source-control.service.ee.d.ts +5 -6
- package/dist/environments.ee/source-control/source-control.service.ee.js +11 -8
- package/dist/environments.ee/source-control/source-control.service.ee.js.map +1 -1
- package/dist/environments.ee/source-control/types/exportable-project.d.ts +2 -0
- package/dist/environments.ee/source-control/types/exportable-variable.d.ts +7 -0
- package/dist/environments.ee/source-control/types/exportable-variable.js +3 -0
- package/dist/environments.ee/source-control/types/exportable-variable.js.map +1 -0
- package/dist/environments.ee/variables/variables.service.ee.d.ts +4 -1
- package/dist/environments.ee/variables/variables.service.ee.js +11 -4
- package/dist/environments.ee/variables/variables.service.ee.js.map +1 -1
- package/dist/executions/execution.service.d.ts +1 -0
- package/dist/executions/execution.service.js +15 -0
- package/dist/executions/execution.service.js.map +1 -1
- package/dist/modules/chat-hub/chat-hub.constants.d.ts +1 -0
- package/dist/modules/chat-hub/chat-hub.constants.js +13 -0
- package/dist/modules/chat-hub/chat-hub.constants.js.map +1 -0
- package/dist/modules/chat-hub/chat-hub.controller.js +26 -30
- package/dist/modules/chat-hub/chat-hub.controller.js.map +1 -1
- package/dist/modules/chat-hub/chat-hub.service.d.ts +17 -9
- package/dist/modules/chat-hub/chat-hub.service.js +308 -110
- package/dist/modules/chat-hub/chat-hub.service.js.map +1 -1
- package/dist/modules/chat-hub/chat-hub.types.d.ts +5 -2
- package/dist/modules/chat-hub/chat-message.repository.d.ts +3 -3
- package/dist/modules/chat-hub/chat-message.repository.js +18 -12
- package/dist/modules/chat-hub/chat-message.repository.js.map +1 -1
- package/dist/modules/chat-hub/chat-session.repository.d.ts +1 -1
- package/dist/modules/chat-hub/chat-session.repository.js +9 -8
- package/dist/modules/chat-hub/chat-session.repository.js.map +1 -1
- package/dist/modules/chat-hub/context-limits.d.ts +3 -3
- package/dist/modules/chat-hub/context-limits.js.map +1 -1
- package/dist/modules/chat-hub/dto/chat-models-request.dto.d.ts +1 -1
- package/dist/modules/chat-hub/stream-capturer.d.ts +3 -0
- package/dist/modules/chat-hub/stream-capturer.js +31 -0
- package/dist/modules/chat-hub/stream-capturer.js.map +1 -0
- package/dist/modules/mcp/mcp.controller.d.ts +1 -1
- package/dist/modules/mcp/mcp.controller.js +5 -20
- package/dist/modules/mcp/mcp.controller.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +3 -1
- package/dist/modules/mcp/mcp.service.js +7 -4
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/mcp.types.d.ts +5 -0
- package/dist/modules/mcp/tools/get-workflow-details.tool.d.ts +2 -1
- package/dist/modules/mcp/tools/get-workflow-details.tool.js +32 -5
- package/dist/modules/mcp/tools/get-workflow-details.tool.js.map +1 -1
- package/dist/modules/mcp/tools/search-workflows.tool.d.ts +2 -1
- package/dist/modules/mcp/tools/search-workflows.tool.js +39 -15
- package/dist/modules/mcp/tools/search-workflows.tool.js.map +1 -1
- package/dist/modules/provisioning.ee/provisioning.controller.ee.d.ts +1 -0
- package/dist/modules/provisioning.ee/provisioning.controller.ee.js +13 -0
- package/dist/modules/provisioning.ee/provisioning.controller.ee.js.map +1 -1
- package/dist/modules/provisioning.ee/provisioning.service.ee.d.ts +18 -2
- package/dist/modules/provisioning.ee/provisioning.service.ee.js +196 -2
- package/dist/modules/provisioning.ee/provisioning.service.ee.js.map +1 -1
- package/dist/public-api/v1/openapi.yml +41 -41
- package/dist/scaling/constants.d.ts +2 -2
- package/dist/scaling/pubsub/pubsub.event-map.d.ts +1 -0
- package/dist/scaling/pubsub/pubsub.types.d.ts +2 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.js +10 -3
- package/dist/server.js.map +1 -1
- package/dist/services/frontend.service.d.ts +6 -1
- package/dist/services/frontend.service.js +21 -0
- package/dist/services/frontend.service.js.map +1 -1
- package/dist/sso.ee/oidc/oidc.service.ee.d.ts +4 -1
- package/dist/sso.ee/oidc/oidc.service.ee.js +21 -5
- package/dist/sso.ee/oidc/oidc.service.ee.js.map +1 -1
- package/dist/sso.ee/saml/routes/saml.controller.ee.d.ts +4 -1
- package/dist/sso.ee/saml/routes/saml.controller.ee.js +9 -2
- package/dist/sso.ee/saml/routes/saml.controller.ee.js.map +1 -1
- package/dist/sso.ee/saml/saml-helpers.js +2 -0
- package/dist/sso.ee/saml/saml-helpers.js.map +1 -1
- package/dist/sso.ee/saml/saml.service.ee.js +9 -0
- package/dist/sso.ee/saml/saml.service.ee.js.map +1 -1
- package/dist/telemetry/index.d.ts +3 -2
- package/dist/telemetry/index.js +7 -2
- package/dist/telemetry/index.js.map +1 -1
- package/dist/workflows/workflow-finder.service.d.ts +1 -0
- package/dist/workflows/workflows.controller.d.ts +8 -1
- package/dist/workflows/workflows.controller.js +20 -2
- package/dist/workflows/workflows.controller.js.map +1 -1
- package/package.json +15 -15
|
@@ -17,16 +17,21 @@ const di_1 = require("@n8n/di");
|
|
|
17
17
|
const n8n_workflow_1 = require("n8n-workflow");
|
|
18
18
|
const uuid_1 = require("uuid");
|
|
19
19
|
const active_executions_1 = require("../../active-executions");
|
|
20
|
-
const
|
|
20
|
+
const credentials_finder_service_1 = require("../../credentials/credentials-finder.service");
|
|
21
21
|
const bad_request_error_1 = require("../../errors/response-errors/bad-request.error");
|
|
22
|
+
const forbidden_error_1 = require("../../errors/response-errors/forbidden.error");
|
|
22
23
|
const not_found_error_1 = require("../../errors/response-errors/not-found.error");
|
|
23
24
|
const execution_service_1 = require("../../executions/execution.service");
|
|
24
25
|
const dynamic_node_parameters_service_1 = require("../../services/dynamic-node-parameters.service");
|
|
25
26
|
const workflow_execute_additional_data_1 = require("../../workflow-execute-additional-data");
|
|
26
27
|
const workflow_execution_service_1 = require("../../workflows/workflow-execution.service");
|
|
28
|
+
const workflow_finder_service_1 = require("../../workflows/workflow-finder.service");
|
|
29
|
+
const workflow_service_1 = require("../../workflows/workflow.service");
|
|
30
|
+
const chat_hub_constants_1 = require("./chat-hub.constants");
|
|
27
31
|
const chat_message_repository_1 = require("./chat-message.repository");
|
|
28
32
|
const chat_session_repository_1 = require("./chat-session.repository");
|
|
29
33
|
const context_limits_1 = require("./context-limits");
|
|
34
|
+
const stream_capturer_1 = require("./stream-capturer");
|
|
30
35
|
const providerNodeTypeMapping = {
|
|
31
36
|
openai: {
|
|
32
37
|
name: '@n8n/n8n-nodes-langchain.lmChatOpenAi',
|
|
@@ -43,42 +48,60 @@ const providerNodeTypeMapping = {
|
|
|
43
48
|
};
|
|
44
49
|
const NODE_NAMES = {
|
|
45
50
|
CHAT_TRIGGER: 'When chat message received',
|
|
46
|
-
|
|
51
|
+
REPLY_AGENT: 'AI Agent',
|
|
52
|
+
TITLE_GENERATOR_AGENT: 'Title Generator Agent',
|
|
47
53
|
CHAT_MODEL: 'Chat Model',
|
|
48
54
|
MEMORY: 'Memory',
|
|
49
55
|
RESTORE_CHAT_MEMORY: 'Restore Chat Memory',
|
|
50
56
|
CLEAR_CHAT_MEMORY: 'Clear Chat Memory',
|
|
51
57
|
};
|
|
58
|
+
const JSONL_STREAM_HEADERS = {
|
|
59
|
+
'Content-Type': 'application/json-lines; charset=utf-8',
|
|
60
|
+
'Transfer-Encoding': 'chunked',
|
|
61
|
+
'Cache-Control': 'no-cache',
|
|
62
|
+
Connection: 'keep-alive',
|
|
63
|
+
};
|
|
52
64
|
let ChatHubService = class ChatHubService {
|
|
53
|
-
constructor(logger,
|
|
65
|
+
constructor(logger, executionService, nodeParametersService, executionRepository, workflowExecutionService, workflowService, workflowFinderService, workflowRepository, sharedWorkflowRepository, activeExecutions, sessionRepository, messageRepository, credentialsFinderService) {
|
|
54
66
|
this.logger = logger;
|
|
55
|
-
this.credentialsService = credentialsService;
|
|
56
67
|
this.executionService = executionService;
|
|
57
68
|
this.nodeParametersService = nodeParametersService;
|
|
58
69
|
this.executionRepository = executionRepository;
|
|
59
70
|
this.workflowExecutionService = workflowExecutionService;
|
|
71
|
+
this.workflowService = workflowService;
|
|
72
|
+
this.workflowFinderService = workflowFinderService;
|
|
60
73
|
this.workflowRepository = workflowRepository;
|
|
61
|
-
this.projectRepository = projectRepository;
|
|
62
74
|
this.sharedWorkflowRepository = sharedWorkflowRepository;
|
|
63
75
|
this.activeExecutions = activeExecutions;
|
|
64
76
|
this.sessionRepository = sessionRepository;
|
|
65
77
|
this.messageRepository = messageRepository;
|
|
78
|
+
this.credentialsFinderService = credentialsFinderService;
|
|
66
79
|
}
|
|
67
80
|
async getModels(user, credentialIds) {
|
|
68
81
|
const additionalData = await (0, workflow_execute_additional_data_1.getBase)({ userId: user.id });
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
82
|
+
const providers = ['openai', 'anthropic', 'google'];
|
|
83
|
+
const allCredentials = await this.credentialsFinderService.findCredentialsForUser(user, [
|
|
84
|
+
'credential:read',
|
|
85
|
+
]);
|
|
86
|
+
const responses = await Promise.all(providers.map(async (provider) => {
|
|
87
|
+
const credentials = {};
|
|
88
|
+
if (provider !== 'n8n') {
|
|
89
|
+
const credentialId = credentialIds[provider];
|
|
90
|
+
if (!credentialId) {
|
|
91
|
+
return [provider, { models: [] }];
|
|
92
|
+
}
|
|
93
|
+
if (!allCredentials.some((credential) => credential.id === credentialId)) {
|
|
94
|
+
return [
|
|
95
|
+
provider,
|
|
96
|
+
{ models: [], error: 'Could not retrieve models. Verify credentials.' },
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
credentials[api_types_1.PROVIDER_CREDENTIAL_TYPE_MAP[provider]] = { name: '', id: credentialId };
|
|
73
100
|
}
|
|
74
|
-
await this.credentialsService.getOne(user, credentialId, false);
|
|
75
101
|
try {
|
|
76
|
-
const credentials = {
|
|
77
|
-
[api_types_1.PROVIDER_CREDENTIAL_TYPE_MAP[provider]]: { name: '', id: credentialId },
|
|
78
|
-
};
|
|
79
102
|
return [
|
|
80
103
|
provider,
|
|
81
|
-
await this.fetchModelsForProvider(provider, credentials, additionalData),
|
|
104
|
+
await this.fetchModelsForProvider(user, provider, credentials, additionalData),
|
|
82
105
|
];
|
|
83
106
|
}
|
|
84
107
|
catch {
|
|
@@ -95,9 +118,10 @@ let ChatHubService = class ChatHubService {
|
|
|
95
118
|
openai: { models: [] },
|
|
96
119
|
anthropic: { models: [] },
|
|
97
120
|
google: { models: [] },
|
|
121
|
+
n8n: { models: [] },
|
|
98
122
|
});
|
|
99
123
|
}
|
|
100
|
-
async fetchModelsForProvider(provider, credentials, additionalData) {
|
|
124
|
+
async fetchModelsForProvider(user, provider, credentials, additionalData) {
|
|
101
125
|
switch (provider) {
|
|
102
126
|
case 'openai':
|
|
103
127
|
return await this.fetchOpenAiModels(credentials, additionalData);
|
|
@@ -105,18 +129,28 @@ let ChatHubService = class ChatHubService {
|
|
|
105
129
|
return await this.fetchAnthropicModels(credentials, additionalData);
|
|
106
130
|
case 'google':
|
|
107
131
|
return await this.fetchGoogleModels(credentials, additionalData);
|
|
132
|
+
case 'n8n':
|
|
133
|
+
return await this.fetchCustomAgentWorkflows(user);
|
|
108
134
|
}
|
|
109
135
|
}
|
|
110
136
|
async fetchOpenAiModels(credentials, additionalData) {
|
|
111
137
|
const resourceLocatorResults = await this.nodeParametersService.getResourceLocatorResults('searchModels', 'parameters.model', additionalData, providerNodeTypeMapping.openai, {}, credentials);
|
|
112
138
|
return {
|
|
113
|
-
models: resourceLocatorResults.results.map((result) => ({
|
|
139
|
+
models: resourceLocatorResults.results.map((result) => ({
|
|
140
|
+
provider: 'openai',
|
|
141
|
+
name: String(result.value),
|
|
142
|
+
model: String(result.value),
|
|
143
|
+
})),
|
|
114
144
|
};
|
|
115
145
|
}
|
|
116
146
|
async fetchAnthropicModels(credentials, additionalData) {
|
|
117
147
|
const resourceLocatorResults = await this.nodeParametersService.getResourceLocatorResults('searchModels', 'parameters.model', additionalData, providerNodeTypeMapping.anthropic, {}, credentials);
|
|
118
148
|
return {
|
|
119
|
-
models: resourceLocatorResults.results.map((result) => ({
|
|
149
|
+
models: resourceLocatorResults.results.map((result) => ({
|
|
150
|
+
provider: 'anthropic',
|
|
151
|
+
name: String(result.value),
|
|
152
|
+
model: String(result.value),
|
|
153
|
+
})),
|
|
120
154
|
};
|
|
121
155
|
}
|
|
122
156
|
async fetchGoogleModels(credentials, additionalData) {
|
|
@@ -159,27 +193,46 @@ let ChatHubService = class ChatHubService {
|
|
|
159
193
|
},
|
|
160
194
|
}, additionalData, providerNodeTypeMapping.google, {}, credentials);
|
|
161
195
|
return {
|
|
162
|
-
models: results.map((result) => ({
|
|
196
|
+
models: results.map((result) => ({
|
|
197
|
+
provider: 'google',
|
|
198
|
+
name: String(result.value),
|
|
199
|
+
model: String(result.value),
|
|
200
|
+
})),
|
|
163
201
|
};
|
|
164
202
|
}
|
|
165
|
-
async
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
return
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
203
|
+
async fetchCustomAgentWorkflows(user) {
|
|
204
|
+
const nodeTypes = [n8n_workflow_1.CHAT_TRIGGER_NODE_TYPE];
|
|
205
|
+
const workflows = await this.workflowService.getWorkflowsWithNodesIncluded(user, nodeTypes);
|
|
206
|
+
return {
|
|
207
|
+
models: workflows
|
|
208
|
+
.filter((workflow) => workflow.active)
|
|
209
|
+
.map((workflow) => ({
|
|
210
|
+
provider: 'n8n',
|
|
211
|
+
name: workflow.name ?? 'Unnamed workflow',
|
|
212
|
+
workflowId: workflow.id,
|
|
213
|
+
})),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
async createChatWorkflow(sessionId, projectId, history, humanMessage, credentials, model, generateConversationTitle, trx) {
|
|
217
|
+
return await (0, db_1.withTransaction)(this.workflowRepository.manager, trx, async (em) => {
|
|
218
|
+
const { nodes, connections, triggerToStartFrom } = this.prepareChatWorkflow({
|
|
219
|
+
sessionId,
|
|
220
|
+
history,
|
|
221
|
+
humanMessage,
|
|
222
|
+
credentials,
|
|
223
|
+
model,
|
|
224
|
+
generateConversationTitle,
|
|
225
|
+
});
|
|
173
226
|
const newWorkflow = new db_1.WorkflowEntity();
|
|
174
227
|
newWorkflow.versionId = (0, uuid_1.v4)();
|
|
175
228
|
newWorkflow.name = `Chat ${sessionId}`;
|
|
176
229
|
newWorkflow.active = false;
|
|
177
230
|
newWorkflow.nodes = nodes;
|
|
178
231
|
newWorkflow.connections = connections;
|
|
179
|
-
const workflow = await
|
|
180
|
-
await
|
|
232
|
+
const workflow = await em.save(newWorkflow);
|
|
233
|
+
await em.save(this.sharedWorkflowRepository.create({
|
|
181
234
|
role: 'workflow:owner',
|
|
182
|
-
projectId
|
|
235
|
+
projectId,
|
|
183
236
|
workflow,
|
|
184
237
|
}));
|
|
185
238
|
return {
|
|
@@ -189,11 +242,22 @@ let ChatHubService = class ChatHubService {
|
|
|
189
242
|
connections,
|
|
190
243
|
versionId: (0, uuid_1.v4)(),
|
|
191
244
|
},
|
|
192
|
-
startNodes,
|
|
193
245
|
triggerToStartFrom,
|
|
194
246
|
};
|
|
195
247
|
});
|
|
196
248
|
}
|
|
249
|
+
async ensureCredentials(user, model, credentials, trx) {
|
|
250
|
+
const allCredentials = await this.credentialsFinderService.findAllCredentialsForUser(user, ['credential:read'], trx);
|
|
251
|
+
const credentialId = this.pickCredentialId(model.provider, credentials);
|
|
252
|
+
if (!credentialId) {
|
|
253
|
+
throw new bad_request_error_1.BadRequestError('No credentials provided for the selected model provider');
|
|
254
|
+
}
|
|
255
|
+
const credential = allCredentials.find((c) => c.id === credentialId);
|
|
256
|
+
if (!credential) {
|
|
257
|
+
throw new forbidden_error_1.ForbiddenError("You don't have access to the provided credentials");
|
|
258
|
+
}
|
|
259
|
+
return credential;
|
|
260
|
+
}
|
|
197
261
|
async deleteChatWorkflow(workflowId) {
|
|
198
262
|
await this.workflowRepository.delete(workflowId);
|
|
199
263
|
}
|
|
@@ -203,8 +267,8 @@ let ChatHubService = class ChatHubService {
|
|
|
203
267
|
}
|
|
204
268
|
return undefined;
|
|
205
269
|
}
|
|
206
|
-
getAIOutput(execution) {
|
|
207
|
-
const agent = execution.data.resultData.runData[
|
|
270
|
+
getAIOutput(execution, nodeName) {
|
|
271
|
+
const agent = execution.data.resultData.runData[nodeName];
|
|
208
272
|
if (!agent || !Array.isArray(agent) || agent.length === 0)
|
|
209
273
|
return undefined;
|
|
210
274
|
const runIndex = agent.length - 1;
|
|
@@ -220,49 +284,131 @@ let ChatHubService = class ChatHubService {
|
|
|
220
284
|
}
|
|
221
285
|
return undefined;
|
|
222
286
|
}
|
|
223
|
-
|
|
287
|
+
pickCredentialId(provider, credentials) {
|
|
288
|
+
if (provider === 'n8n') {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
224
291
|
return credentials[api_types_1.PROVIDER_CREDENTIAL_TYPE_MAP[provider]]?.id ?? null;
|
|
225
292
|
}
|
|
226
293
|
async sendHumanMessage(res, user, payload) {
|
|
227
294
|
const { sessionId, messageId, replyId, message } = payload;
|
|
295
|
+
const provider = payload.model.provider;
|
|
228
296
|
const selectedModel = {
|
|
229
297
|
...payload.model,
|
|
230
|
-
credentialId: this.
|
|
298
|
+
credentialId: provider !== 'n8n' ? this.pickCredentialId(provider, payload.credentials) : null,
|
|
231
299
|
};
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
300
|
+
const workflow = await this.messageRepository.manager.transaction(async (trx) => {
|
|
301
|
+
const session = await this.getChatSession(user, sessionId, selectedModel, true, trx);
|
|
302
|
+
await this.ensurePreviousMessage(payload.previousMessageId, sessionId, trx);
|
|
303
|
+
const messages = Object.fromEntries((session.messages ?? []).map((m) => [m.id, m]));
|
|
304
|
+
const history = this.buildMessageHistory(messages, payload.previousMessageId);
|
|
305
|
+
await this.saveHumanMessage(payload, user, payload.previousMessageId, selectedModel, undefined, trx);
|
|
306
|
+
if (provider !== 'n8n') {
|
|
307
|
+
return await this.prepareBaseChatWorkflow(user, payload, sessionId, history, message, trx);
|
|
237
308
|
}
|
|
238
|
-
|
|
239
|
-
|
|
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);
|
|
309
|
+
return await this.prepareCustomAgentWorkflow(user, sessionId, payload.model.workflowId, message);
|
|
310
|
+
});
|
|
243
311
|
try {
|
|
244
312
|
await this.executeChatWorkflow(res, user, workflow, replyId, sessionId, messageId, selectedModel);
|
|
245
313
|
}
|
|
246
314
|
finally {
|
|
247
|
-
|
|
315
|
+
if (provider !== 'n8n') {
|
|
316
|
+
await this.deleteChatWorkflow(workflow.workflowData.id);
|
|
317
|
+
}
|
|
248
318
|
}
|
|
249
319
|
}
|
|
250
|
-
async
|
|
251
|
-
const
|
|
320
|
+
async prepareBaseChatWorkflow(user, payload, sessionId, history, message, trx) {
|
|
321
|
+
const credential = await this.ensureCredentials(user, payload.model, payload.credentials, trx);
|
|
322
|
+
return await this.createChatWorkflow(sessionId, credential.projectId, history, message, payload.credentials, payload.model, payload.previousMessageId === null, trx);
|
|
323
|
+
}
|
|
324
|
+
async prepareCustomAgentWorkflow(user, sessionId, workflowId, message) {
|
|
325
|
+
const workflowEntity = await this.workflowFinderService.findWorkflowForUser(workflowId, user, ['workflow:read'], { includeTags: false, includeParentFolder: false });
|
|
326
|
+
if (!workflowEntity) {
|
|
327
|
+
throw new bad_request_error_1.BadRequestError('Workflow not found');
|
|
328
|
+
}
|
|
329
|
+
const chatTriggers = workflowEntity.nodes.filter((node) => node.type === n8n_workflow_1.CHAT_TRIGGER_NODE_TYPE);
|
|
330
|
+
if (chatTriggers.length !== 1) {
|
|
331
|
+
throw new bad_request_error_1.BadRequestError('Workflow must have exactly one chat trigger');
|
|
332
|
+
}
|
|
333
|
+
const chatResponseNodes = workflowEntity.nodes.filter((node) => node.type === n8n_workflow_1.RESPOND_TO_CHAT_NODE_TYPE);
|
|
334
|
+
if (chatResponseNodes.length > 0) {
|
|
335
|
+
throw new bad_request_error_1.BadRequestError('Respond to Chat nodes are not supported in custom agent workflows');
|
|
336
|
+
}
|
|
337
|
+
const agents = workflowEntity.nodes.filter((node) => node.type === n8n_workflow_1.AGENT_LANGCHAIN_NODE_TYPE);
|
|
338
|
+
if (agents.length !== 1) {
|
|
339
|
+
throw new bad_request_error_1.BadRequestError('Workflow must have exactly one AI Agent node');
|
|
340
|
+
}
|
|
341
|
+
return {
|
|
342
|
+
workflowData: {
|
|
343
|
+
...workflowEntity,
|
|
344
|
+
pinData: {},
|
|
345
|
+
},
|
|
346
|
+
triggerToStartFrom: {
|
|
347
|
+
name: chatTriggers[0].name,
|
|
348
|
+
data: {
|
|
349
|
+
startTime: Date.now(),
|
|
350
|
+
executionTime: 0,
|
|
351
|
+
executionIndex: 0,
|
|
352
|
+
executionStatus: 'success',
|
|
353
|
+
data: {
|
|
354
|
+
main: [
|
|
355
|
+
[
|
|
356
|
+
{
|
|
357
|
+
json: {
|
|
358
|
+
sessionId,
|
|
359
|
+
action: 'sendMessage',
|
|
360
|
+
chatInput: message,
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
],
|
|
365
|
+
},
|
|
366
|
+
source: [null],
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
async ensurePreviousMessage(previousMessageId, sessionId, trx) {
|
|
372
|
+
if (!previousMessageId) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const previousMessage = await this.messageRepository.getOneById(previousMessageId, sessionId, [], trx);
|
|
376
|
+
if (!previousMessage) {
|
|
377
|
+
throw new bad_request_error_1.BadRequestError('The previous message does not exist in the session');
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
async editMessage(res, user, payload) {
|
|
381
|
+
const { sessionId, editId, messageId, replyId } = payload;
|
|
252
382
|
const selectedModel = {
|
|
253
383
|
...payload.model,
|
|
254
|
-
credentialId:
|
|
384
|
+
credentialId: payload.model.provider !== 'n8n'
|
|
385
|
+
? this.pickCredentialId(payload.model.provider, payload.credentials)
|
|
386
|
+
: null,
|
|
255
387
|
};
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
388
|
+
const workflow = await this.messageRepository.manager.transaction(async (trx) => {
|
|
389
|
+
const credential = await this.ensureCredentials(user, payload.model, payload.credentials, trx);
|
|
390
|
+
const session = await this.getChatSession(user, sessionId, undefined, false, trx);
|
|
391
|
+
const messageToEdit = await this.getChatMessage(session.id, editId, [], trx);
|
|
392
|
+
if (!['ai', 'human'].includes(messageToEdit.type)) {
|
|
393
|
+
throw new bad_request_error_1.BadRequestError('Only human and AI messages can be edited');
|
|
394
|
+
}
|
|
395
|
+
if (messageToEdit.type === 'ai') {
|
|
396
|
+
await this.messageRepository.updateChatMessage(editId, { content: payload.message }, trx);
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
if (messageToEdit.type === 'human') {
|
|
400
|
+
const { message } = payload;
|
|
401
|
+
const messages = Object.fromEntries((session.messages ?? []).map((m) => [m.id, m]));
|
|
402
|
+
const history = this.buildMessageHistory(messages, messageToEdit.previousMessageId);
|
|
403
|
+
const revisionOfMessageId = messageToEdit.revisionOfMessageId ?? messageToEdit.id;
|
|
404
|
+
await this.saveHumanMessage(payload, user, messageToEdit.previousMessageId, selectedModel, revisionOfMessageId, trx);
|
|
405
|
+
return await this.createChatWorkflow(session.id, credential.projectId, history, message, payload.credentials, payload.model, messageToEdit.previousMessageId === null, trx);
|
|
406
|
+
}
|
|
407
|
+
return null;
|
|
408
|
+
});
|
|
409
|
+
if (!workflow) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
266
412
|
try {
|
|
267
413
|
await this.executeChatWorkflow(res, user, workflow, replyId, sessionId, messageId, selectedModel);
|
|
268
414
|
}
|
|
@@ -274,27 +420,37 @@ let ChatHubService = class ChatHubService {
|
|
|
274
420
|
const { sessionId, retryId, replyId } = payload;
|
|
275
421
|
const selectedModel = {
|
|
276
422
|
...payload.model,
|
|
277
|
-
credentialId:
|
|
423
|
+
credentialId: payload.model.provider !== 'n8n'
|
|
424
|
+
? this.pickCredentialId(payload.model.provider, payload.credentials)
|
|
425
|
+
: null,
|
|
278
426
|
};
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
history.
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
427
|
+
const { workflow, retryOfMessageId, previousMessageId } = await this.messageRepository.manager.transaction(async (trx) => {
|
|
428
|
+
const credential = await this.ensureCredentials(user, payload.model, payload.credentials, trx);
|
|
429
|
+
const session = await this.getChatSession(user, sessionId, undefined, false, trx);
|
|
430
|
+
const messageToRetry = await this.getChatMessage(session.id, retryId, [], trx);
|
|
431
|
+
if (messageToRetry.type !== 'ai') {
|
|
432
|
+
throw new bad_request_error_1.BadRequestError('Can only retry AI messages');
|
|
433
|
+
}
|
|
434
|
+
const messages = Object.fromEntries((session.messages ?? []).map((m) => [m.id, m]));
|
|
435
|
+
const history = this.buildMessageHistory(messages, messageToRetry.previousMessageId);
|
|
436
|
+
const lastHumanMessage = history.filter((m) => m.type === 'human').pop();
|
|
437
|
+
if (!lastHumanMessage) {
|
|
438
|
+
throw new bad_request_error_1.BadRequestError('No human message found to base the retry on');
|
|
439
|
+
}
|
|
440
|
+
const lastHumanMessageIndex = history.indexOf(lastHumanMessage);
|
|
441
|
+
if (lastHumanMessageIndex !== -1) {
|
|
442
|
+
history.splice(lastHumanMessageIndex + 1);
|
|
443
|
+
}
|
|
444
|
+
const retryOfMessageId = messageToRetry.retryOfMessageId ?? messageToRetry.id;
|
|
445
|
+
const workflow = await this.createChatWorkflow(session.id, credential.projectId, history, lastHumanMessage ? lastHumanMessage.content : '', payload.credentials, payload.model, false, trx);
|
|
446
|
+
return {
|
|
447
|
+
workflow,
|
|
448
|
+
previousMessageId: lastHumanMessage.id,
|
|
449
|
+
retryOfMessageId,
|
|
450
|
+
};
|
|
451
|
+
});
|
|
296
452
|
try {
|
|
297
|
-
await this.executeChatWorkflow(res, user, workflow, replyId, sessionId,
|
|
453
|
+
await this.executeChatWorkflow(res, user, workflow, replyId, sessionId, previousMessageId, selectedModel, retryOfMessageId);
|
|
298
454
|
}
|
|
299
455
|
finally {
|
|
300
456
|
await this.deleteChatWorkflow(workflow.workflowData.id);
|
|
@@ -319,13 +475,22 @@ let ChatHubService = class ChatHubService {
|
|
|
319
475
|
await this.messageRepository.updateChatMessage(messageId, { status: 'cancelled' });
|
|
320
476
|
}
|
|
321
477
|
async executeChatWorkflow(res, user, workflow, replyId, sessionId, previousMessageId, selectedModel, retryOfMessageId) {
|
|
322
|
-
const { workflowData,
|
|
478
|
+
const { workflowData, triggerToStartFrom } = workflow;
|
|
323
479
|
this.logger.debug(`Starting execution of workflow "${workflowData.name}" with ID ${workflowData.id}`);
|
|
480
|
+
let partialMessage = '';
|
|
481
|
+
const onChunk = (chunk) => {
|
|
482
|
+
const data = (0, n8n_workflow_1.jsonParse)(chunk);
|
|
483
|
+
if (data && data.type === 'item' && typeof data.content === 'string') {
|
|
484
|
+
partialMessage += data.content;
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
const stream = (0, stream_capturer_1.captureResponseWrites)(res, onChunk);
|
|
488
|
+
stream.writeHead(200, JSONL_STREAM_HEADERS);
|
|
489
|
+
stream.flushHeaders();
|
|
324
490
|
const { executionId } = await this.workflowExecutionService.executeManually({
|
|
325
491
|
workflowData,
|
|
326
|
-
startNodes,
|
|
327
492
|
triggerToStartFrom,
|
|
328
|
-
}, user, undefined, true,
|
|
493
|
+
}, user, undefined, true, stream);
|
|
329
494
|
if (!executionId) {
|
|
330
495
|
throw new n8n_workflow_1.OperationalError('There was a problem starting the chat execution.');
|
|
331
496
|
}
|
|
@@ -334,7 +499,7 @@ let ChatHubService = class ChatHubService {
|
|
|
334
499
|
sessionId,
|
|
335
500
|
executionId,
|
|
336
501
|
previousMessageId,
|
|
337
|
-
message:
|
|
502
|
+
message: partialMessage,
|
|
338
503
|
selectedModel,
|
|
339
504
|
retryOfMessageId,
|
|
340
505
|
status: 'running',
|
|
@@ -356,9 +521,8 @@ let ChatHubService = class ChatHubService {
|
|
|
356
521
|
throw new n8n_workflow_1.OperationalError(`Could not find execution with ID ${executionId}`);
|
|
357
522
|
}
|
|
358
523
|
if (execution.status === 'canceled') {
|
|
359
|
-
const message = 'Generation cancelled.';
|
|
360
524
|
await this.messageRepository.updateChatMessage(replyId, {
|
|
361
|
-
content:
|
|
525
|
+
content: partialMessage || 'Generation cancelled.',
|
|
362
526
|
status: 'cancelled',
|
|
363
527
|
});
|
|
364
528
|
return;
|
|
@@ -376,14 +540,14 @@ let ChatHubService = class ChatHubService {
|
|
|
376
540
|
const message = this.getErrorMessage(execution) ?? 'Failed to generate a response';
|
|
377
541
|
throw new n8n_workflow_1.OperationalError(message);
|
|
378
542
|
}
|
|
379
|
-
const message = this.getAIOutput(execution);
|
|
380
|
-
if (!message) {
|
|
381
|
-
throw new n8n_workflow_1.OperationalError('No response generated');
|
|
382
|
-
}
|
|
383
543
|
await this.messageRepository.updateChatMessage(replyId, {
|
|
384
|
-
content:
|
|
544
|
+
content: partialMessage,
|
|
385
545
|
status: 'success',
|
|
386
546
|
});
|
|
547
|
+
const title = this.getAIOutput(execution, NODE_NAMES.TITLE_GENERATOR_AGENT);
|
|
548
|
+
if (title) {
|
|
549
|
+
await this.sessionRepository.updateChatTitle(sessionId, title);
|
|
550
|
+
}
|
|
387
551
|
}
|
|
388
552
|
catch (error) {
|
|
389
553
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
@@ -393,7 +557,7 @@ let ChatHubService = class ChatHubService {
|
|
|
393
557
|
});
|
|
394
558
|
}
|
|
395
559
|
}
|
|
396
|
-
prepareChatWorkflow(sessionId, history, humanMessage, credentials, model) {
|
|
560
|
+
prepareChatWorkflow({ sessionId, history, humanMessage, credentials, model, generateConversationTitle, }) {
|
|
397
561
|
const nodes = [
|
|
398
562
|
{
|
|
399
563
|
parameters: {
|
|
@@ -414,14 +578,16 @@ let ChatHubService = class ChatHubService {
|
|
|
414
578
|
text: "={{ $('When chat message received').item.json.chatInput }}",
|
|
415
579
|
options: {
|
|
416
580
|
enableStreaming: true,
|
|
417
|
-
maxTokensFromMemory:
|
|
581
|
+
maxTokensFromMemory: model.provider !== 'n8n'
|
|
582
|
+
? (0, context_limits_1.getMaxContextWindowTokens)(model.provider, model.model)
|
|
583
|
+
: undefined,
|
|
418
584
|
},
|
|
419
585
|
},
|
|
420
586
|
type: n8n_workflow_1.AGENT_LANGCHAIN_NODE_TYPE,
|
|
421
587
|
typeVersion: 3,
|
|
422
588
|
position: [600, 0],
|
|
423
589
|
id: (0, uuid_1.v4)(),
|
|
424
|
-
name: NODE_NAMES.
|
|
590
|
+
name: NODE_NAMES.REPLY_AGENT,
|
|
425
591
|
},
|
|
426
592
|
this.createModelNode(credentials, model),
|
|
427
593
|
{
|
|
@@ -472,6 +638,22 @@ let ChatHubService = class ChatHubService {
|
|
|
472
638
|
id: (0, uuid_1.v4)(),
|
|
473
639
|
name: NODE_NAMES.CLEAR_CHAT_MEMORY,
|
|
474
640
|
},
|
|
641
|
+
{
|
|
642
|
+
disabled: !generateConversationTitle,
|
|
643
|
+
parameters: {
|
|
644
|
+
promptType: 'define',
|
|
645
|
+
text: "={{ $('When chat message received').item.json.chatInput }}",
|
|
646
|
+
options: {
|
|
647
|
+
enableStreaming: false,
|
|
648
|
+
systemMessage: chat_hub_constants_1.CONVERSATION_TITLE_GENERATION_PROMPT,
|
|
649
|
+
},
|
|
650
|
+
},
|
|
651
|
+
type: n8n_workflow_1.AGENT_LANGCHAIN_NODE_TYPE,
|
|
652
|
+
typeVersion: 3,
|
|
653
|
+
position: [224, 360],
|
|
654
|
+
id: (0, uuid_1.v4)(),
|
|
655
|
+
name: NODE_NAMES.TITLE_GENERATOR_AGENT,
|
|
656
|
+
},
|
|
475
657
|
];
|
|
476
658
|
const connections = {
|
|
477
659
|
[NODE_NAMES.CHAT_TRIGGER]: {
|
|
@@ -480,23 +662,35 @@ let ChatHubService = class ChatHubService {
|
|
|
480
662
|
],
|
|
481
663
|
},
|
|
482
664
|
[NODE_NAMES.RESTORE_CHAT_MEMORY]: {
|
|
483
|
-
main: [
|
|
665
|
+
main: [
|
|
666
|
+
[
|
|
667
|
+
{ node: NODE_NAMES.REPLY_AGENT, type: n8n_workflow_1.NodeConnectionTypes.Main, index: 0 },
|
|
668
|
+
{ node: NODE_NAMES.TITLE_GENERATOR_AGENT, type: n8n_workflow_1.NodeConnectionTypes.Main, index: 0 },
|
|
669
|
+
],
|
|
670
|
+
],
|
|
484
671
|
},
|
|
485
672
|
[NODE_NAMES.CHAT_MODEL]: {
|
|
486
673
|
ai_languageModel: [
|
|
487
|
-
[
|
|
674
|
+
[
|
|
675
|
+
{ node: NODE_NAMES.REPLY_AGENT, type: n8n_workflow_1.NodeConnectionTypes.AiLanguageModel, index: 0 },
|
|
676
|
+
{
|
|
677
|
+
node: NODE_NAMES.TITLE_GENERATOR_AGENT,
|
|
678
|
+
type: n8n_workflow_1.NodeConnectionTypes.AiLanguageModel,
|
|
679
|
+
index: 0,
|
|
680
|
+
},
|
|
681
|
+
],
|
|
488
682
|
],
|
|
489
683
|
},
|
|
490
684
|
[NODE_NAMES.MEMORY]: {
|
|
491
685
|
ai_memory: [
|
|
492
686
|
[
|
|
493
|
-
{ node: NODE_NAMES.
|
|
687
|
+
{ node: NODE_NAMES.REPLY_AGENT, type: n8n_workflow_1.NodeConnectionTypes.AiMemory, index: 0 },
|
|
494
688
|
{ node: NODE_NAMES.RESTORE_CHAT_MEMORY, type: n8n_workflow_1.NodeConnectionTypes.AiMemory, index: 0 },
|
|
495
689
|
{ node: NODE_NAMES.CLEAR_CHAT_MEMORY, type: n8n_workflow_1.NodeConnectionTypes.AiMemory, index: 0 },
|
|
496
690
|
],
|
|
497
691
|
],
|
|
498
692
|
},
|
|
499
|
-
[NODE_NAMES.
|
|
693
|
+
[NODE_NAMES.REPLY_AGENT]: {
|
|
500
694
|
main: [
|
|
501
695
|
[
|
|
502
696
|
{
|
|
@@ -508,7 +702,6 @@ let ChatHubService = class ChatHubService {
|
|
|
508
702
|
],
|
|
509
703
|
},
|
|
510
704
|
};
|
|
511
|
-
const startNodes = [{ name: 'Restore Chat Memory', sourceData: null }];
|
|
512
705
|
const triggerToStartFrom = {
|
|
513
706
|
name: NODE_NAMES.CHAT_TRIGGER,
|
|
514
707
|
data: {
|
|
@@ -532,9 +725,9 @@ let ChatHubService = class ChatHubService {
|
|
|
532
725
|
source: [null],
|
|
533
726
|
},
|
|
534
727
|
};
|
|
535
|
-
return { nodes, connections,
|
|
728
|
+
return { nodes, connections, triggerToStartFrom };
|
|
536
729
|
}
|
|
537
|
-
async saveHumanMessage(payload, user, previousMessageId, selectedModel, revisionOfMessageId) {
|
|
730
|
+
async saveHumanMessage(payload, user, previousMessageId, selectedModel, revisionOfMessageId, trx) {
|
|
538
731
|
await this.messageRepository.createChatMessage({
|
|
539
732
|
id: payload.messageId,
|
|
540
733
|
sessionId: payload.sessionId,
|
|
@@ -545,7 +738,7 @@ let ChatHubService = class ChatHubService {
|
|
|
545
738
|
previousMessageId,
|
|
546
739
|
revisionOfMessageId,
|
|
547
740
|
...selectedModel,
|
|
548
|
-
});
|
|
741
|
+
}, trx);
|
|
549
742
|
}
|
|
550
743
|
async saveAIMessage({ id, sessionId, executionId, previousMessageId, message, selectedModel, retryOfMessageId, status, }) {
|
|
551
744
|
await this.messageRepository.createChatMessage({
|
|
@@ -561,8 +754,8 @@ let ChatHubService = class ChatHubService {
|
|
|
561
754
|
...selectedModel,
|
|
562
755
|
});
|
|
563
756
|
}
|
|
564
|
-
async getChatSession(user, sessionId, selectedModel, initialize = false,
|
|
565
|
-
const existing = await this.sessionRepository.getOneById(sessionId, user.id);
|
|
757
|
+
async getChatSession(user, sessionId, selectedModel, initialize = false, trx) {
|
|
758
|
+
const existing = await this.sessionRepository.getOneById(sessionId, user.id, trx);
|
|
566
759
|
if (existing) {
|
|
567
760
|
return existing;
|
|
568
761
|
}
|
|
@@ -572,20 +765,24 @@ let ChatHubService = class ChatHubService {
|
|
|
572
765
|
return await this.sessionRepository.createChatSession({
|
|
573
766
|
id: sessionId,
|
|
574
767
|
ownerId: user.id,
|
|
575
|
-
title:
|
|
768
|
+
title: 'New Chat',
|
|
576
769
|
...selectedModel,
|
|
577
|
-
});
|
|
770
|
+
}, trx);
|
|
578
771
|
}
|
|
579
|
-
async getChatMessage(sessionId, messageId, relations = []) {
|
|
580
|
-
const message = await this.messageRepository.getOneById(messageId, sessionId, relations);
|
|
772
|
+
async getChatMessage(sessionId, messageId, relations = [], trx) {
|
|
773
|
+
const message = await this.messageRepository.getOneById(messageId, sessionId, relations, trx);
|
|
581
774
|
if (!message) {
|
|
582
775
|
throw new not_found_error_1.NotFoundError('Chat message not found');
|
|
583
776
|
}
|
|
584
777
|
return message;
|
|
585
778
|
}
|
|
586
|
-
createModelNode(credentials,
|
|
779
|
+
createModelNode(credentials, conversationModel) {
|
|
780
|
+
if (conversationModel.provider === 'n8n') {
|
|
781
|
+
throw new n8n_workflow_1.OperationalError('Custom agent workflows do not require a model node');
|
|
782
|
+
}
|
|
783
|
+
const { provider, model } = conversationModel;
|
|
587
784
|
const common = {
|
|
588
|
-
position: [600,
|
|
785
|
+
position: [600, 500],
|
|
589
786
|
id: (0, uuid_1.v4)(),
|
|
590
787
|
name: 'Chat Model',
|
|
591
788
|
credentials,
|
|
@@ -719,16 +916,17 @@ exports.ChatHubService = ChatHubService;
|
|
|
719
916
|
exports.ChatHubService = ChatHubService = __decorate([
|
|
720
917
|
(0, di_1.Service)(),
|
|
721
918
|
__metadata("design:paramtypes", [backend_common_1.Logger,
|
|
722
|
-
credentials_service_1.CredentialsService,
|
|
723
919
|
execution_service_1.ExecutionService,
|
|
724
920
|
dynamic_node_parameters_service_1.DynamicNodeParametersService,
|
|
725
921
|
db_1.ExecutionRepository,
|
|
726
922
|
workflow_execution_service_1.WorkflowExecutionService,
|
|
923
|
+
workflow_service_1.WorkflowService,
|
|
924
|
+
workflow_finder_service_1.WorkflowFinderService,
|
|
727
925
|
db_1.WorkflowRepository,
|
|
728
|
-
db_1.ProjectRepository,
|
|
729
926
|
db_1.SharedWorkflowRepository,
|
|
730
927
|
active_executions_1.ActiveExecutions,
|
|
731
928
|
chat_session_repository_1.ChatHubSessionRepository,
|
|
732
|
-
chat_message_repository_1.ChatHubMessageRepository
|
|
929
|
+
chat_message_repository_1.ChatHubMessageRepository,
|
|
930
|
+
credentials_finder_service_1.CredentialsFinderService])
|
|
733
931
|
], ChatHubService);
|
|
734
932
|
//# sourceMappingURL=chat-hub.service.js.map
|