incognitolm commited on
Commit
35318fb
·
1 Parent(s): a16dc1f

msg versions

Browse files
Files changed (2) hide show
  1. server/chatStream.js +25 -6
  2. server/wsHandler.js +33 -6
server/chatStream.js CHANGED
@@ -130,33 +130,52 @@ export async function streamChat(ws, {
130
  // Otherwise extract text
131
  const hasImages = userMessage.some(item => item.type === 'image_url');
132
  if (hasImages) {
133
- normalizedUserMessage = userMessage;
 
 
 
 
 
 
 
 
 
 
134
  } else {
135
- normalizedUserMessage = userMessage.filter(b => b.type === "text").map(b => b.text).join("\n") || "";
 
136
  }
137
  }
138
 
139
  // Build messages array: only include user message if it's provided
 
 
140
  const messages = [
141
  { role: "system", content: SYSTEM_PROMPT },
142
  ...history.map(normalizeMessage).filter(Boolean),
143
  ];
144
 
145
  // Add user message only if content was provided (not for edit-only requests)
146
- if (userMessage !== undefined && userMessage !== null && userMessage !== '') {
147
  messages.push({ role: "user", content: normalizedUserMessage });
148
  }
149
 
150
  // Validate messages before API call
 
151
  for (let i = 0; i < messages.length; i++) {
152
  const msg = messages[i];
153
  if (msg.role !== "assistant" || !msg.tool_calls) {
154
  if (typeof msg.content !== "string" && !Array.isArray(msg.content)) {
155
- console.error(`Message ${i} has invalid content:`, msg);
156
- throw new Error(`Message ${i} missing or invalid content property`);
157
  }
158
  }
159
  }
 
 
 
 
 
 
160
 
161
  try {
162
  const stream = await client.chat.completions.create({
@@ -179,7 +198,7 @@ export async function streamChat(ws, {
179
  ];
180
 
181
  // Include user message in follow-up only if it was in the original request
182
- if (userMessage !== undefined && userMessage !== null && userMessage !== '') {
183
  followUpMessages.push({ role: "user", content: normalizedUserMessage });
184
  }
185
 
 
130
  // Otherwise extract text
131
  const hasImages = userMessage.some(item => item.type === 'image_url');
132
  if (hasImages) {
133
+ // Keep the full array - may need to add placeholder text if no text items
134
+ const textItems = userMessage.filter(item => item.type === 'text' && item.text?.trim());
135
+ if (textItems.length === 0) {
136
+ // Add a text placeholder to accompany images
137
+ normalizedUserMessage = [
138
+ { type: 'text', text: '[Image(s) attached]' },
139
+ ...userMessage.filter(item => item.type === 'image_url'),
140
+ ];
141
+ } else {
142
+ normalizedUserMessage = userMessage;
143
+ }
144
  } else {
145
+ // Text only - extract
146
+ normalizedUserMessage = userMessage.filter(b => b.type === "text").map(b => b.text).join("\n").trim() || "";
147
  }
148
  }
149
 
150
  // Build messages array: only include user message if it's provided
151
+ const hasUserMessage = userMessage !== undefined && userMessage !== null && userMessage !== '' &&
152
+ !(Array.isArray(userMessage) && userMessage.length === 0);
153
  const messages = [
154
  { role: "system", content: SYSTEM_PROMPT },
155
  ...history.map(normalizeMessage).filter(Boolean),
156
  ];
157
 
158
  // Add user message only if content was provided (not for edit-only requests)
159
+ if (hasUserMessage) {
160
  messages.push({ role: "user", content: normalizedUserMessage });
161
  }
162
 
163
  // Validate messages before API call
164
+ const invalidMessages = [];
165
  for (let i = 0; i < messages.length; i++) {
166
  const msg = messages[i];
167
  if (msg.role !== "assistant" || !msg.tool_calls) {
168
  if (typeof msg.content !== "string" && !Array.isArray(msg.content)) {
169
+ invalidMessages.push({index: i, role: msg.role, contentType: typeof msg.content});
 
170
  }
171
  }
172
  }
173
+
174
+ if (invalidMessages.length > 0) {
175
+ const details = invalidMessages.map(m => `${m.index}:${m.role}(${m.contentType})`).join(',');
176
+ console.error(`streamChat: Invalid message content types: ${details}`);
177
+ throw new Error(`Message validation failed: invalid content types [${details}]`);
178
+ }
179
 
180
  try {
181
  const stream = await client.chat.completions.create({
 
198
  ];
199
 
200
  // Include user message in follow-up only if it was in the original request
201
+ if (hasUserMessage) {
202
  followUpMessages.push({ role: "user", content: normalizedUserMessage });
203
  }
204
 
server/wsHandler.js CHANGED
@@ -216,7 +216,9 @@ const handlers = {
216
  const finalText = text || fullText;
217
 
218
  // Only create user entry if content was actually provided
219
- const userEntry = (content !== undefined && content !== null && content !== '')
 
 
220
  ? buildEntry('user', content)
221
  : null;
222
 
@@ -263,7 +265,11 @@ const handlers = {
263
 
264
  safeSend(ws, { type: aborted ? 'chat:aborted' : 'chat:done', sessionId, name: newName, history: extractFlatHistory(newRootMessage) });
265
  },
266
- onError(err) { activeStreams.delete(ws); safeSend(ws, { type: 'chat:error', error: String(err), sessionId }); },
 
 
 
 
267
  });
268
  },
269
 
@@ -274,14 +280,18 @@ const handlers = {
274
  const session = client.userId
275
  ? sessionStore.getUserSession(client.userId, sessionId)
276
  : sessionStore.getTempSession(client.tempId, sessionId);
277
- if (!session) return;
278
 
279
  const rootMessage = session.history?.[0];
280
- if (!rootMessage) return;
281
 
282
  const flatHistory = extractFlatHistory(rootMessage);
283
  const targetMsg = flatHistory[messageIndex];
284
- if (!targetMsg) return;
 
 
 
 
285
 
286
  // Find the target message in the tree and add new version
287
  const newRoot = validateAndRepairTree(JSON.parse(JSON.stringify(rootMessage)));
@@ -312,6 +322,13 @@ const handlers = {
312
  // Send back the updated message with its ID and the full flat history
313
  const updatedFlatHistory = extractFlatHistory(newRoot);
314
  const updatedTargetMsg = updatedFlatHistory[messageIndex];
 
 
 
 
 
 
 
315
  safeSend(ws, { type: 'chat:messageEdited', sessionId, messageId: targetMsg.id, messageIndex, message: updatedTargetMsg, history: updatedFlatHistory });
316
  },
317
 
@@ -441,7 +458,6 @@ function extractFlatHistory(rootMessage) {
441
  // Helper to ensure message has valid content
442
  const ensureValidContent = (msg) => {
443
  if (msg.content === undefined || msg.content === null) {
444
- console.warn(`Message ${msg.id} has missing content, defaulting to empty string`);
445
  msg.content = '';
446
  }
447
  return msg;
@@ -449,6 +465,17 @@ function extractFlatHistory(rootMessage) {
449
 
450
  const history = [ensureValidContent(rootMessage)];
451
  const currentVerIdx = rootMessage.currentVersionIdx ?? 0;
 
 
 
 
 
 
 
 
 
 
 
452
  const currentTail = rootMessage.versions[currentVerIdx]?.tail;
453
 
454
  if (currentTail && Array.isArray(currentTail)) {
 
216
  const finalText = text || fullText;
217
 
218
  // Only create user entry if content was actually provided
219
+ const hasContent = content !== undefined && content !== null && content !== '' &&
220
+ !(Array.isArray(content) && content.length === 0);
221
+ const userEntry = hasContent
222
  ? buildEntry('user', content)
223
  : null;
224
 
 
265
 
266
  safeSend(ws, { type: aborted ? 'chat:aborted' : 'chat:done', sessionId, name: newName, history: extractFlatHistory(newRootMessage) });
267
  },
268
+ onError(err) {
269
+ activeStreams.delete(ws);
270
+ console.error('streamChat error:', err);
271
+ safeSend(ws, { type: 'chat:error', error: String(err), sessionId });
272
+ },
273
  });
274
  },
275
 
 
280
  const session = client.userId
281
  ? sessionStore.getUserSession(client.userId, sessionId)
282
  : sessionStore.getTempSession(client.tempId, sessionId);
283
+ if (!session) return safeSend(ws, { type: 'error', message: 'Session not found' });
284
 
285
  const rootMessage = session.history?.[0];
286
+ if (!rootMessage) return safeSend(ws, { type: 'error', message: 'No history' });
287
 
288
  const flatHistory = extractFlatHistory(rootMessage);
289
  const targetMsg = flatHistory[messageIndex];
290
+ if (!targetMsg) {
291
+ console.error(`chat:editMessage: Message at index ${messageIndex} not found. History length: ${flatHistory.length}`);
292
+ return safeSend(ws, { type: 'error', message: 'Message not found' });
293
+ }
294
+ console.log(`chat:editMessage: Editing message ${targetMsg.id} at index ${messageIndex}`);
295
 
296
  // Find the target message in the tree and add new version
297
  const newRoot = validateAndRepairTree(JSON.parse(JSON.stringify(rootMessage)));
 
322
  // Send back the updated message with its ID and the full flat history
323
  const updatedFlatHistory = extractFlatHistory(newRoot);
324
  const updatedTargetMsg = updatedFlatHistory[messageIndex];
325
+
326
+ if (!updatedTargetMsg) {
327
+ console.error(`chat:editMessage: Updated message not found at index ${messageIndex}. Updated history length: ${updatedFlatHistory.length}`);
328
+ return safeSend(ws, { type: 'error', message: 'Failed to apply edit - message lost' });
329
+ }
330
+
331
+ console.log(`chat:editMessage: Edit complete. Message ${updatedTargetMsg.id} now has ${updatedTargetMsg.versions?.length ?? 0} versions`);
332
  safeSend(ws, { type: 'chat:messageEdited', sessionId, messageId: targetMsg.id, messageIndex, message: updatedTargetMsg, history: updatedFlatHistory });
333
  },
334
 
 
458
  // Helper to ensure message has valid content
459
  const ensureValidContent = (msg) => {
460
  if (msg.content === undefined || msg.content === null) {
 
461
  msg.content = '';
462
  }
463
  return msg;
 
465
 
466
  const history = [ensureValidContent(rootMessage)];
467
  const currentVerIdx = rootMessage.currentVersionIdx ?? 0;
468
+
469
+ if (!Array.isArray(rootMessage.versions)) {
470
+ console.warn(`extractFlatHistory: Root message ${rootMessage.id} missing versions array`);
471
+ return history;
472
+ }
473
+
474
+ if (currentVerIdx >= rootMessage.versions.length) {
475
+ console.warn(`extractFlatHistory: Root message currentVersionIdx ${currentVerIdx} out of bounds (${rootMessage.versions.length} versions)`);
476
+ return history;
477
+ }
478
+
479
  const currentTail = rootMessage.versions[currentVerIdx]?.tail;
480
 
481
  if (currentTail && Array.isArray(currentTail)) {