zhlajiex commited on
Commit
afc2629
·
1 Parent(s): 3185ed5

Fix: Resolve 'Link Severed' race condition by preventing double-termination of streams and improving error handling

Browse files
backend/controllers/ai.js CHANGED
@@ -181,6 +181,7 @@ exports.chat = asyncHandler(async (req, res, next) => {
181
  }
182
  } catch (e) { console.error("Gradio Error", e); }
183
 
 
184
  await Message.create({ sessionId: session._id, sender: 'user', content: message || "[SIGNAL]", attachmentUrl });
185
  await Message.create({ sessionId: session._id, sender: 'ai', content: fullAIResponse || "[EMPTY_SIGNAL]", modelUsed: activeModelName });
186
  user.usage.requestsToday += 1;
@@ -248,13 +249,15 @@ exports.chat = asyncHandler(async (req, res, next) => {
248
  console.log(`[DEBUG] Calling API: ${apiUrl} for model: ${apiModelId}`); const response = await axios.post(apiUrl, { model: apiModelId, messages: apiMessages, stream: true, temperature: 0.7 }, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, responseType: 'stream' });
249
 
250
  let fullAIResponse = "";
 
 
251
  response.data.on('data', chunk => {
252
  const lines = chunk.toString().split('\n');
253
  for (const line of lines) {
254
  if (line.startsWith('data: ')) {
255
- const dataStr = line.slice(6);
256
  if (dataStr === '[DONE]') {
257
- res.write(`data: ${JSON.stringify({ done: true, sessionId: session._id })}\n\n`);
258
  return;
259
  }
260
  try {
@@ -270,6 +273,7 @@ exports.chat = asyncHandler(async (req, res, next) => {
270
  });
271
 
272
  response.data.on('end', async () => {
 
273
  await Message.create({ sessionId: session._id, sender: 'user', content: message || "[SIGNAL]", attachmentUrl });
274
  await Message.create({ sessionId: session._id, sender: 'ai', content: fullAIResponse, modelUsed: activeModelName });
275
  user.usage.requestsToday += 1;
@@ -278,11 +282,21 @@ exports.chat = asyncHandler(async (req, res, next) => {
278
  res.end();
279
  });
280
 
 
 
 
 
 
 
 
 
281
  } catch (err) {
282
  console.error("[DEBUG] Chat Controller Catch:", err.message);
283
- res.write(`data: ${JSON.stringify({ error: "NEURAL_LINK_SEVERED", details: err.message })}\n\n`);
284
- res.write(`data: ${JSON.stringify({ done: true })}\n\n`);
285
- res.end();
 
 
286
  }
287
  });
288
 
 
181
  }
182
  } catch (e) { console.error("Gradio Error", e); }
183
 
184
+ if (res.writableEnded) return;
185
  await Message.create({ sessionId: session._id, sender: 'user', content: message || "[SIGNAL]", attachmentUrl });
186
  await Message.create({ sessionId: session._id, sender: 'ai', content: fullAIResponse || "[EMPTY_SIGNAL]", modelUsed: activeModelName });
187
  user.usage.requestsToday += 1;
 
249
  console.log(`[DEBUG] Calling API: ${apiUrl} for model: ${apiModelId}`); const response = await axios.post(apiUrl, { model: apiModelId, messages: apiMessages, stream: true, temperature: 0.7 }, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, responseType: 'stream' });
250
 
251
  let fullAIResponse = "";
252
+ let isStreamEnded = false;
253
+
254
  response.data.on('data', chunk => {
255
  const lines = chunk.toString().split('\n');
256
  for (const line of lines) {
257
  if (line.startsWith('data: ')) {
258
+ const dataStr = line.slice(6).trim();
259
  if (dataStr === '[DONE]') {
260
+ isStreamEnded = true;
261
  return;
262
  }
263
  try {
 
273
  });
274
 
275
  response.data.on('end', async () => {
276
+ if (res.writableEnded) return;
277
  await Message.create({ sessionId: session._id, sender: 'user', content: message || "[SIGNAL]", attachmentUrl });
278
  await Message.create({ sessionId: session._id, sender: 'ai', content: fullAIResponse, modelUsed: activeModelName });
279
  user.usage.requestsToday += 1;
 
282
  res.end();
283
  });
284
 
285
+ response.data.on('error', (err) => {
286
+ console.error("Stream Error:", err);
287
+ if (!res.writableEnded) {
288
+ res.write(`data: ${JSON.stringify({ error: "STREAM_ERROR", details: err.message })}\n\n`);
289
+ res.end();
290
+ }
291
+ });
292
+
293
  } catch (err) {
294
  console.error("[DEBUG] Chat Controller Catch:", err.message);
295
+ if (!res.writableEnded) {
296
+ res.write(`data: ${JSON.stringify({ error: "NEURAL_LINK_SEVERED", details: err.message })}\n\n`);
297
+ res.write(`data: ${JSON.stringify({ done: true })}\n\n`);
298
+ res.end();
299
+ }
300
  }
301
  });
302
 
backend/public/chat.html CHANGED
@@ -397,6 +397,12 @@
397
  if (line.startsWith('data: ')) {
398
  try {
399
  const data = JSON.parse(line.slice(6));
 
 
 
 
 
 
400
  if (data.message) {
401
  fullText += data.message;
402
  aiNode.querySelector('.prose').innerHTML = marked.parse(fullText);
 
397
  if (line.startsWith('data: ')) {
398
  try {
399
  const data = JSON.parse(line.slice(6));
400
+ if (data.error) {
401
+ aiNode.querySelector('.prose').innerHTML += `<div class="text-red-500 font-bold mt-2">!! ${data.error}: ${data.details || ''}</div>`;
402
+ isProcessing = false;
403
+ clearTimeout(safetyTimeout);
404
+ return;
405
+ }
406
  if (data.message) {
407
  fullText += data.message;
408
  aiNode.querySelector('.prose').innerHTML = marked.parse(fullText);