cmcts-c-agent-embedding 1.1.2-cmcuni → 1.1.5-cmcuni

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/server.js CHANGED
@@ -1,401 +1,401 @@
1
- Error.stackTraceLimit = 0;
2
-
3
- import express from 'express';
4
- import cors from 'cors';
5
- import fetch from 'node-fetch';
6
- import path from 'path';
7
- import { fileURLToPath } from 'url';
8
- import dotenv from 'dotenv';
9
- import axios from 'axios';
10
- import multer from 'multer';
11
- import FormData from 'form-data';
12
- import { generateEmbedScript } from './src/utils/embedScript.js';
13
-
14
- dotenv.config();
15
-
16
- const __filename = fileURLToPath(import.meta.url);
17
- const __dirname = path.dirname(__filename);
18
-
19
- const API_HOST = process.env.API_HOST;
20
- const FLOWISE_API_KEY = process.env.FLOWISE_API_KEY;
21
-
22
- if (!API_HOST) {
23
- console.error('API_HOST is not set in environment variables');
24
- process.exit(1);
25
- }
26
-
27
- if (!FLOWISE_API_KEY) {
28
- console.error('FLOWISE_API_KEY is not set in environment variables');
29
- process.exit(1);
30
- }
31
-
32
- const parseChatflows = () => {
33
- try {
34
- const chatflows = new Map();
35
-
36
- // Get all environment variables that don't start with special prefixes
37
- const chatflowVars = Object.entries(process.env).filter(([key]) => {
38
- return (
39
- !key.startsWith('_') &&
40
- !key.startsWith('npm_') &&
41
- !key.startsWith('yarn_') &&
42
- !key.startsWith('VSCODE_') &&
43
- key !== 'API_HOST' &&
44
- key !== 'FLOWISE_API_KEY' &&
45
- key !== 'PORT' &&
46
- key !== 'HOST' &&
47
- key !== 'BASE_URL' &&
48
- key !== 'NODE_ENV'
49
- );
50
- });
51
-
52
- if (chatflowVars.length === 0) {
53
- console.error('No chatflow configurations found in environment variables');
54
- process.exit(1);
55
- }
56
-
57
- const defaultDomains = process.env.NODE_ENV === 'production' ? [] : ['http://localhost:5678'];
58
-
59
- for (const [identifier, value] of chatflowVars) {
60
- const parts = value.split(',').map((s) => s.trim());
61
- const chatflowId = parts[0];
62
- const configuredDomains = parts.length > 1 ? parts.slice(1) : [];
63
-
64
- const domains = [...new Set([...defaultDomains, ...configuredDomains])];
65
-
66
- if (!chatflowId) {
67
- console.error(`Missing chatflow ID for ${identifier}`);
68
- continue;
69
- }
70
-
71
- if (domains.includes('*')) {
72
- console.error(`\x1b[31mError: Wildcard (*) domains are not allowed in ${identifier}. This flow will not be accessible.\x1b[0m`);
73
- continue;
74
- }
75
-
76
- chatflows.set(identifier, { chatflowId, domains });
77
- }
78
-
79
- if (chatflows.size === 0) {
80
- console.error('No valid chatflow configurations found');
81
- process.exit(1);
82
- }
83
-
84
- return chatflows;
85
- } catch (error) {
86
- console.error('Failed to parse chatflow configurations:', error);
87
- process.exit(1);
88
- }
89
- };
90
-
91
- const chatflows = parseChatflows();
92
-
93
- const getChatflowDetails = (identifier) => {
94
- let chatflow = chatflows.get(identifier);
95
-
96
- if (!chatflow) {
97
- const lowerIdentifier = identifier.toLowerCase();
98
- for (const [key, value] of chatflows.entries()) {
99
- if (key.toLowerCase() === lowerIdentifier) {
100
- chatflow = value;
101
- break;
102
- }
103
- }
104
- }
105
-
106
- if (!chatflow) {
107
- throw new Error(`Chatflow not found: ${identifier}`);
108
- }
109
- return chatflow;
110
- };
111
-
112
- const isValidUUID = (str) => {
113
- const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
114
- return uuidPattern.test(str);
115
- };
116
-
117
- const isValidChatflowConfig = (value) => {
118
- if (!value) return false;
119
- const parts = value.split(',').map((s) => s.trim());
120
- return isValidUUID(parts[0]);
121
- };
122
-
123
- console.info('\x1b[36m%s\x1b[0m', 'Configured chatflows:');
124
- chatflows.forEach((config, identifier) => {
125
- if (isValidChatflowConfig(config.chatflowId)) {
126
- console.info('\x1b[36m%s\x1b[0m', ` ${identifier}: ${config.chatflowId} (${config.domains.join(', ')})`);
127
- }
128
- });
129
-
130
- const isValidDomain = (origin, domains) => {
131
- if (!origin) return true;
132
- return domains.includes(origin);
133
- };
134
-
135
- const app = express();
136
- app.use(express.json({ limit: '50mb' }));
137
- app.use(express.urlencoded({ limit: '50mb', extended: true }));
138
-
139
- app.use(
140
- cors({
141
- origin: true,
142
- credentials: true,
143
- methods: ['GET', 'POST', 'PUT', 'OPTIONS'],
144
- allowedHeaders: ['*'],
145
- }),
146
- );
147
-
148
- app.get('/', (_, res) => {
149
- res.sendFile(path.join(__dirname, 'public', 'index.html'));
150
- });
151
-
152
- app.get('/web.js', (req, res) => {
153
- const origin = req.headers.origin;
154
-
155
- const allAllowedDomains = Array.from(chatflows.values()).flatMap((config) => config.domains);
156
-
157
- if (!isValidDomain(origin, allAllowedDomains)) {
158
- return res.status(403).send('Access Denied');
159
- }
160
-
161
- res.set({
162
- 'Content-Type': 'application/javascript',
163
- 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
164
- Pragma: 'no-cache',
165
- Expires: '0',
166
- });
167
- res.sendFile(path.join(__dirname, 'dist', 'web.js'));
168
- });
169
-
170
- const validateApiKey = (req, res, next) => {
171
- if (req.path === '/web.js' || req.path === '/' || req.method === 'OPTIONS') {
172
- return next();
173
- }
174
-
175
- if (req.path.includes('/get-upload-file')) {
176
- return next();
177
- }
178
-
179
- let identifier;
180
- const pathParts = req.path.split('/').filter(Boolean);
181
-
182
- if (pathParts.length >= 3) {
183
- identifier = pathParts[3];
184
- } else {
185
- identifier = req.query.chatflowId?.split('/')[0];
186
- }
187
-
188
- if (!identifier) {
189
- return res.status(400).json({ error: 'Bad Request' });
190
- }
191
-
192
- let chatflow;
193
- try {
194
- chatflow = getChatflowDetails(identifier);
195
- req.chatflow = chatflow;
196
- } catch (error) {
197
- return res.status(404).json({ error: 'Not Found' });
198
- }
199
-
200
- const origin = req.headers.origin;
201
- const userAgent = req.headers['user-agent'];
202
- const acceptLanguage = req.headers['accept-language'];
203
- const accept = req.headers['accept'];
204
- const secChUa = req.headers['sec-ch-ua'];
205
- const secChUaPlatform = req.headers['sec-ch-ua-platform'];
206
- const secChUaMobile = req.headers['sec-ch-ua-mobile'];
207
- const secFetchMode = req.headers['sec-fetch-mode'];
208
- const secFetchSite = req.headers['sec-fetch-site'];
209
-
210
- if (
211
- userAgent &&
212
- acceptLanguage &&
213
- accept &&
214
- secChUa &&
215
- secChUaPlatform &&
216
- secChUaMobile &&
217
- ['?0', '?1'].includes(secChUaMobile) &&
218
- secFetchMode === 'cors' &&
219
- secFetchSite &&
220
- ['same-origin', 'same-site', 'cross-site'].includes(secFetchSite)
221
- ) {
222
- if (isValidDomain(origin, chatflow.domains)) {
223
- return next();
224
- }
225
- }
226
-
227
- const authHeader = req.headers.authorization;
228
- if (authHeader && authHeader.startsWith('Bearer ') && authHeader.split(' ')[1] === FLOWISE_API_KEY) {
229
- return next();
230
- }
231
-
232
- return res.status(401).json({ error: 'Unauthorized' });
233
- };
234
-
235
- app.use(validateApiKey);
236
-
237
- const proxyEndpoints = {
238
- prediction: {
239
- method: 'POST',
240
- path: '/api/v1/prediction/:identifier',
241
- target: '/api/v1/prediction',
242
- },
243
- config: {
244
- method: 'GET',
245
- path: '/api/v1/public-chatbotConfig/:identifier',
246
- target: '/api/v1/public-chatbotConfig',
247
- },
248
- streaming: {
249
- method: 'GET',
250
- path: '/api/v1/chatflows-streaming/:identifier',
251
- target: '/api/v1/chatflows-streaming',
252
- },
253
- files: {
254
- method: 'GET',
255
- path: '/api/v1/get-upload-file',
256
- target: '/api/v1/get-upload-file',
257
- },
258
- };
259
-
260
- const handleProxy = async (req, res, targetPath) => {
261
- try {
262
- let identifier = req.query.chatflowId?.split('/')[0] || req.path.split('/').pop() || null;
263
-
264
- if (!identifier) {
265
- return res.status(400).json({ error: 'Bad Request' });
266
- }
267
-
268
- const chatflow = getChatflowDetails(identifier);
269
- if (!chatflow) {
270
- return res.status(404).json({ error: 'Not Found' });
271
- }
272
-
273
- if (req.query.chatId && req.query.fileName) {
274
- const url = `${API_HOST}${targetPath}?chatflowId=${chatflow.chatflowId}&chatId=${req.query.chatId}&fileName=${req.query.fileName}`;
275
-
276
- const response = await fetch(url, {
277
- method: req.method,
278
- headers: {
279
- Authorization: `Bearer ${FLOWISE_API_KEY}`,
280
- },
281
- });
282
-
283
- if (!response.ok) {
284
- console.error(`File proxy error: ${response.status} ${response.statusText}`);
285
- return res.status(response.status).json({ error: `File proxy error: ${response.statusText}` });
286
- }
287
-
288
- const contentType = response.headers.get('content-type');
289
- if (contentType) {
290
- res.setHeader('Content-Type', contentType);
291
- }
292
-
293
- return response.body.pipe(res);
294
- }
295
-
296
- let finalPath = `${targetPath}/${chatflow.chatflowId}`;
297
- const url = `${API_HOST}${finalPath}`;
298
-
299
- const response = await fetch(url, {
300
- method: req.method,
301
- headers: {
302
- ...(req.method !== 'GET' && { 'Content-Type': 'application/json' }),
303
- Authorization: `Bearer ${FLOWISE_API_KEY}`,
304
- },
305
- body: req.method !== 'GET' ? JSON.stringify(req.body) : undefined,
306
- });
307
-
308
- if (!response.ok) {
309
- console.error(`Proxy error: ${response.status} ${response.statusText}`);
310
- return res.status(response.status).json({ error: `Proxy error: ${response.statusText}` });
311
- }
312
-
313
- const contentType = response.headers.get('content-type');
314
-
315
- if (contentType?.includes('image/') || contentType?.includes('audio/') || contentType?.includes('application/octet-stream')) {
316
- res.setHeader('Content-Type', contentType);
317
- return response.body.pipe(res);
318
- }
319
-
320
- if (contentType?.includes('text/event-stream')) {
321
- res.setHeader('Content-Type', 'text/event-stream');
322
- res.setHeader('Cache-Control', 'no-cache');
323
- res.setHeader('Connection', 'keep-alive');
324
- return response.body.pipe(res);
325
- }
326
-
327
- if (contentType?.includes('application/json')) {
328
- const data = await response.json();
329
- return res.json(data);
330
- }
331
-
332
- return response.body.pipe(res);
333
- } catch (error) {
334
- console.error('Proxy error:', error);
335
- res.status(500).json({ error: 'Internal Server Error' });
336
- }
337
- };
338
-
339
- Object.values(proxyEndpoints).forEach(({ method, path, target }) => {
340
- app[method.toLowerCase()](path, (req, res) => {
341
- return handleProxy(req, res, target);
342
- });
343
- });
344
-
345
- const storage = multer.memoryStorage();
346
- const upload = multer({ storage: storage });
347
-
348
- app.post('/api/v1/attachments/:identifier/:chatId', upload.array('files'), async (req, res) => {
349
- try {
350
- const chatId = req.params.chatId;
351
- if (!chatId) {
352
- return res.status(400).json({ error: 'Bad Request' });
353
- }
354
-
355
- if (!req.files || req.files.length === 0) {
356
- return res.status(400).json({ error: 'Bad Request' });
357
- }
358
-
359
- const form = new FormData();
360
- req.files.forEach((file) => {
361
- form.append('files', file.buffer, {
362
- filename: file.originalname,
363
- contentType: file.mimetype,
364
- });
365
- });
366
-
367
- const chatflow = req.chatflow;
368
- const targetUrl = `${API_HOST}/api/v1/attachments/${chatflow.chatflowId}/${chatId}`;
369
-
370
- const response = await axios.post(targetUrl, form, {
371
- headers: {
372
- ...form.getHeaders(),
373
- Authorization: `Bearer ${FLOWISE_API_KEY}`,
374
- },
375
- });
376
-
377
- res.json(response.data);
378
- } catch (error) {
379
- console.error('Attachment upload error:', error);
380
- res.status(500).json({ error: 'Internal Server Error' });
381
- }
382
- });
383
-
384
- app.use((_req, res) => {
385
- res.status(404).json({ error: 'Not Found' });
386
- });
387
-
388
- const PORT = process.env.PORT || 3001;
389
- const HOST = process.env.HOST || '0.0.0.0';
390
-
391
- const server = app.listen(PORT, HOST, () => {
392
- const addr = server.address();
393
- if (!addr || typeof addr === 'string') return;
394
-
395
- const baseUrl =
396
- process.env.BASE_URL || process.env.NODE_ENV === 'production'
397
- ? `https://${process.env.HOST || 'localhost'}`
398
- : `http://${HOST === '0.0.0.0' ? 'localhost' : HOST}:${addr.port}`;
399
-
400
- generateEmbedScript(baseUrl);
401
- });
1
+ Error.stackTraceLimit = 0;
2
+
3
+ import express from 'express';
4
+ import cors from 'cors';
5
+ import fetch from 'node-fetch';
6
+ import path from 'path';
7
+ import { fileURLToPath } from 'url';
8
+ import dotenv from 'dotenv';
9
+ import axios from 'axios';
10
+ import multer from 'multer';
11
+ import FormData from 'form-data';
12
+ import { generateEmbedScript } from './src/utils/embedScript.js';
13
+
14
+ dotenv.config();
15
+
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = path.dirname(__filename);
18
+
19
+ const API_HOST = process.env.API_HOST;
20
+ const FLOWISE_API_KEY = process.env.FLOWISE_API_KEY;
21
+
22
+ if (!API_HOST) {
23
+ console.error('API_HOST is not set in environment variables');
24
+ process.exit(1);
25
+ }
26
+
27
+ if (!FLOWISE_API_KEY) {
28
+ console.error('FLOWISE_API_KEY is not set in environment variables');
29
+ process.exit(1);
30
+ }
31
+
32
+ const parseChatflows = () => {
33
+ try {
34
+ const chatflows = new Map();
35
+
36
+ // Get all environment variables that don't start with special prefixes
37
+ const chatflowVars = Object.entries(process.env).filter(([key]) => {
38
+ return (
39
+ !key.startsWith('_') &&
40
+ !key.startsWith('npm_') &&
41
+ !key.startsWith('yarn_') &&
42
+ !key.startsWith('VSCODE_') &&
43
+ key !== 'API_HOST' &&
44
+ key !== 'FLOWISE_API_KEY' &&
45
+ key !== 'PORT' &&
46
+ key !== 'HOST' &&
47
+ key !== 'BASE_URL' &&
48
+ key !== 'NODE_ENV'
49
+ );
50
+ });
51
+
52
+ if (chatflowVars.length === 0) {
53
+ console.error('No chatflow configurations found in environment variables');
54
+ process.exit(1);
55
+ }
56
+
57
+ const defaultDomains = process.env.NODE_ENV === 'production' ? [] : ['http://localhost:5678'];
58
+
59
+ for (const [identifier, value] of chatflowVars) {
60
+ const parts = value.split(',').map((s) => s.trim());
61
+ const chatflowId = parts[0];
62
+ const configuredDomains = parts.length > 1 ? parts.slice(1) : [];
63
+
64
+ const domains = [...new Set([...defaultDomains, ...configuredDomains])];
65
+
66
+ if (!chatflowId) {
67
+ console.error(`Missing chatflow ID for ${identifier}`);
68
+ continue;
69
+ }
70
+
71
+ if (domains.includes('*')) {
72
+ console.error(`\x1b[31mError: Wildcard (*) domains are not allowed in ${identifier}. This flow will not be accessible.\x1b[0m`);
73
+ continue;
74
+ }
75
+
76
+ chatflows.set(identifier, { chatflowId, domains });
77
+ }
78
+
79
+ if (chatflows.size === 0) {
80
+ console.error('No valid chatflow configurations found');
81
+ process.exit(1);
82
+ }
83
+
84
+ return chatflows;
85
+ } catch (error) {
86
+ console.error('Failed to parse chatflow configurations:', error);
87
+ process.exit(1);
88
+ }
89
+ };
90
+
91
+ const chatflows = parseChatflows();
92
+
93
+ const getChatflowDetails = (identifier) => {
94
+ let chatflow = chatflows.get(identifier);
95
+
96
+ if (!chatflow) {
97
+ const lowerIdentifier = identifier.toLowerCase();
98
+ for (const [key, value] of chatflows.entries()) {
99
+ if (key.toLowerCase() === lowerIdentifier) {
100
+ chatflow = value;
101
+ break;
102
+ }
103
+ }
104
+ }
105
+
106
+ if (!chatflow) {
107
+ throw new Error(`Chatflow not found: ${identifier}`);
108
+ }
109
+ return chatflow;
110
+ };
111
+
112
+ const isValidUUID = (str) => {
113
+ const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
114
+ return uuidPattern.test(str);
115
+ };
116
+
117
+ const isValidChatflowConfig = (value) => {
118
+ if (!value) return false;
119
+ const parts = value.split(',').map((s) => s.trim());
120
+ return isValidUUID(parts[0]);
121
+ };
122
+
123
+ console.info('\x1b[36m%s\x1b[0m', 'Configured chatflows:');
124
+ chatflows.forEach((config, identifier) => {
125
+ if (isValidChatflowConfig(config.chatflowId)) {
126
+ console.info('\x1b[36m%s\x1b[0m', ` ${identifier}: ${config.chatflowId} (${config.domains.join(', ')})`);
127
+ }
128
+ });
129
+
130
+ const isValidDomain = (origin, domains) => {
131
+ if (!origin) return true;
132
+ return domains.includes(origin);
133
+ };
134
+
135
+ const app = express();
136
+ app.use(express.json({ limit: '50mb' }));
137
+ app.use(express.urlencoded({ limit: '50mb', extended: true }));
138
+
139
+ app.use(
140
+ cors({
141
+ origin: true,
142
+ credentials: true,
143
+ methods: ['GET', 'POST', 'PUT', 'OPTIONS'],
144
+ allowedHeaders: ['*'],
145
+ }),
146
+ );
147
+
148
+ app.get('/', (_, res) => {
149
+ res.sendFile(path.join(__dirname, 'public', 'index.html'));
150
+ });
151
+
152
+ app.get('/web.js', (req, res) => {
153
+ const origin = req.headers.origin;
154
+
155
+ const allAllowedDomains = Array.from(chatflows.values()).flatMap((config) => config.domains);
156
+
157
+ if (!isValidDomain(origin, allAllowedDomains)) {
158
+ return res.status(403).send('Access Denied');
159
+ }
160
+
161
+ res.set({
162
+ 'Content-Type': 'application/javascript',
163
+ 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
164
+ Pragma: 'no-cache',
165
+ Expires: '0',
166
+ });
167
+ res.sendFile(path.join(__dirname, 'dist', 'web.js'));
168
+ });
169
+
170
+ const validateApiKey = (req, res, next) => {
171
+ if (req.path === '/web.js' || req.path === '/' || req.method === 'OPTIONS') {
172
+ return next();
173
+ }
174
+
175
+ if (req.path.includes('/get-upload-file')) {
176
+ return next();
177
+ }
178
+
179
+ let identifier;
180
+ const pathParts = req.path.split('/').filter(Boolean);
181
+
182
+ if (pathParts.length >= 3) {
183
+ identifier = pathParts[3];
184
+ } else {
185
+ identifier = req.query.chatflowId?.split('/')[0];
186
+ }
187
+
188
+ if (!identifier) {
189
+ return res.status(400).json({ error: 'Bad Request' });
190
+ }
191
+
192
+ let chatflow;
193
+ try {
194
+ chatflow = getChatflowDetails(identifier);
195
+ req.chatflow = chatflow;
196
+ } catch (error) {
197
+ return res.status(404).json({ error: 'Not Found' });
198
+ }
199
+
200
+ const origin = req.headers.origin;
201
+ const userAgent = req.headers['user-agent'];
202
+ const acceptLanguage = req.headers['accept-language'];
203
+ const accept = req.headers['accept'];
204
+ const secChUa = req.headers['sec-ch-ua'];
205
+ const secChUaPlatform = req.headers['sec-ch-ua-platform'];
206
+ const secChUaMobile = req.headers['sec-ch-ua-mobile'];
207
+ const secFetchMode = req.headers['sec-fetch-mode'];
208
+ const secFetchSite = req.headers['sec-fetch-site'];
209
+
210
+ if (
211
+ userAgent &&
212
+ acceptLanguage &&
213
+ accept &&
214
+ secChUa &&
215
+ secChUaPlatform &&
216
+ secChUaMobile &&
217
+ ['?0', '?1'].includes(secChUaMobile) &&
218
+ secFetchMode === 'cors' &&
219
+ secFetchSite &&
220
+ ['same-origin', 'same-site', 'cross-site'].includes(secFetchSite)
221
+ ) {
222
+ if (isValidDomain(origin, chatflow.domains)) {
223
+ return next();
224
+ }
225
+ }
226
+
227
+ const authHeader = req.headers.authorization;
228
+ if (authHeader && authHeader.startsWith('Bearer ') && authHeader.split(' ')[1] === FLOWISE_API_KEY) {
229
+ return next();
230
+ }
231
+
232
+ return res.status(401).json({ error: 'Unauthorized' });
233
+ };
234
+
235
+ app.use(validateApiKey);
236
+
237
+ const proxyEndpoints = {
238
+ prediction: {
239
+ method: 'POST',
240
+ path: '/api/v1/prediction/:identifier',
241
+ target: '/api/v1/prediction',
242
+ },
243
+ config: {
244
+ method: 'GET',
245
+ path: '/api/v1/public-chatbotConfig/:identifier',
246
+ target: '/api/v1/public-chatbotConfig',
247
+ },
248
+ streaming: {
249
+ method: 'GET',
250
+ path: '/api/v1/chatflows-streaming/:identifier',
251
+ target: '/api/v1/chatflows-streaming',
252
+ },
253
+ files: {
254
+ method: 'GET',
255
+ path: '/api/v1/get-upload-file',
256
+ target: '/api/v1/get-upload-file',
257
+ },
258
+ };
259
+
260
+ const handleProxy = async (req, res, targetPath) => {
261
+ try {
262
+ let identifier = req.query.chatflowId?.split('/')[0] || req.path.split('/').pop() || null;
263
+
264
+ if (!identifier) {
265
+ return res.status(400).json({ error: 'Bad Request' });
266
+ }
267
+
268
+ const chatflow = getChatflowDetails(identifier);
269
+ if (!chatflow) {
270
+ return res.status(404).json({ error: 'Not Found' });
271
+ }
272
+
273
+ if (req.query.chatId && req.query.fileName) {
274
+ const url = `${API_HOST}${targetPath}?chatflowId=${chatflow.chatflowId}&chatId=${req.query.chatId}&fileName=${req.query.fileName}`;
275
+
276
+ const response = await fetch(url, {
277
+ method: req.method,
278
+ headers: {
279
+ Authorization: `Bearer ${FLOWISE_API_KEY}`,
280
+ },
281
+ });
282
+
283
+ if (!response.ok) {
284
+ console.error(`File proxy error: ${response.status} ${response.statusText}`);
285
+ return res.status(response.status).json({ error: `File proxy error: ${response.statusText}` });
286
+ }
287
+
288
+ const contentType = response.headers.get('content-type');
289
+ if (contentType) {
290
+ res.setHeader('Content-Type', contentType);
291
+ }
292
+
293
+ return response.body.pipe(res);
294
+ }
295
+
296
+ let finalPath = `${targetPath}/${chatflow.chatflowId}`;
297
+ const url = `${API_HOST}${finalPath}`;
298
+
299
+ const response = await fetch(url, {
300
+ method: req.method,
301
+ headers: {
302
+ ...(req.method !== 'GET' && { 'Content-Type': 'application/json' }),
303
+ Authorization: `Bearer ${FLOWISE_API_KEY}`,
304
+ },
305
+ body: req.method !== 'GET' ? JSON.stringify(req.body) : undefined,
306
+ });
307
+
308
+ if (!response.ok) {
309
+ console.error(`Proxy error: ${response.status} ${response.statusText}`);
310
+ return res.status(response.status).json({ error: `Proxy error: ${response.statusText}` });
311
+ }
312
+
313
+ const contentType = response.headers.get('content-type');
314
+
315
+ if (contentType?.includes('image/') || contentType?.includes('audio/') || contentType?.includes('application/octet-stream')) {
316
+ res.setHeader('Content-Type', contentType);
317
+ return response.body.pipe(res);
318
+ }
319
+
320
+ if (contentType?.includes('text/event-stream')) {
321
+ res.setHeader('Content-Type', 'text/event-stream');
322
+ res.setHeader('Cache-Control', 'no-cache');
323
+ res.setHeader('Connection', 'keep-alive');
324
+ return response.body.pipe(res);
325
+ }
326
+
327
+ if (contentType?.includes('application/json')) {
328
+ const data = await response.json();
329
+ return res.json(data);
330
+ }
331
+
332
+ return response.body.pipe(res);
333
+ } catch (error) {
334
+ console.error('Proxy error:', error);
335
+ res.status(500).json({ error: 'Internal Server Error' });
336
+ }
337
+ };
338
+
339
+ Object.values(proxyEndpoints).forEach(({ method, path, target }) => {
340
+ app[method.toLowerCase()](path, (req, res) => {
341
+ return handleProxy(req, res, target);
342
+ });
343
+ });
344
+
345
+ const storage = multer.memoryStorage();
346
+ const upload = multer({ storage: storage });
347
+
348
+ app.post('/api/v1/attachments/:identifier/:chatId', upload.array('files'), async (req, res) => {
349
+ try {
350
+ const chatId = req.params.chatId;
351
+ if (!chatId) {
352
+ return res.status(400).json({ error: 'Bad Request' });
353
+ }
354
+
355
+ if (!req.files || req.files.length === 0) {
356
+ return res.status(400).json({ error: 'Bad Request' });
357
+ }
358
+
359
+ const form = new FormData();
360
+ req.files.forEach((file) => {
361
+ form.append('files', file.buffer, {
362
+ filename: file.originalname,
363
+ contentType: file.mimetype,
364
+ });
365
+ });
366
+
367
+ const chatflow = req.chatflow;
368
+ const targetUrl = `${API_HOST}/api/v1/attachments/${chatflow.chatflowId}/${chatId}`;
369
+
370
+ const response = await axios.post(targetUrl, form, {
371
+ headers: {
372
+ ...form.getHeaders(),
373
+ Authorization: `Bearer ${FLOWISE_API_KEY}`,
374
+ },
375
+ });
376
+
377
+ res.json(response.data);
378
+ } catch (error) {
379
+ console.error('Attachment upload error:', error);
380
+ res.status(500).json({ error: 'Internal Server Error' });
381
+ }
382
+ });
383
+
384
+ app.use((_req, res) => {
385
+ res.status(404).json({ error: 'Not Found' });
386
+ });
387
+
388
+ const PORT = process.env.PORT || 3001;
389
+ const HOST = process.env.HOST || '0.0.0.0';
390
+
391
+ const server = app.listen(PORT, HOST, () => {
392
+ const addr = server.address();
393
+ if (!addr || typeof addr === 'string') return;
394
+
395
+ const baseUrl =
396
+ process.env.BASE_URL || process.env.NODE_ENV === 'production'
397
+ ? `https://${process.env.HOST || 'localhost'}`
398
+ : `http://${HOST === '0.0.0.0' ? 'localhost' : HOST}:${addr.port}`;
399
+
400
+ generateEmbedScript(baseUrl);
401
+ });