incognitolm commited on
Commit
6cf0e88
·
1 Parent(s): 1baccaa

Update wsHandler.js

Browse files
Files changed (1) hide show
  1. server/wsHandler.js +103 -20
server/wsHandler.js CHANGED
@@ -12,6 +12,27 @@ import {
12
  import { streamChat, extractSessionName } from './chatStream.js';
13
  import crypto from 'crypto';
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  const activeStreams = new Map();
16
 
17
  initGuestRequestLimiter().catch(err => console.error('Failed to initialize guest request limiter:', err));
@@ -171,7 +192,7 @@ const handlers = {
171
  const assetsCollected = [], toolCallsCollected = [];
172
 
173
  await streamChat(ws, {
174
- history: session.history || [], userMessage: content, tools: tools || {},
175
  accessToken: client.accessToken, clientId: msg.clientId, abortSignal: abort.signal,
176
  onToken(t) { fullText += t; safeSend(ws, { type: 'chat:token', token: t, sessionId }); },
177
  onToolCall(call) {
@@ -190,14 +211,17 @@ const handlers = {
190
  return { ...c, state: resolved.state || 'resolved', result: resolved.result };
191
  });
192
  const asstEntry = buildEntry('assistant', finalText, mergedCalls);
193
- const newHistory = [...(session.history || []), userEntry, asstEntry];
194
 
195
- // Update tails for all versioned messages to include new messages
196
- for (let i = 0; i < newHistory.length; i++) {
197
  const msg = newHistory[i];
 
198
  if (msg.versions) {
199
  for (const version of msg.versions) {
200
- version.tail = newHistory.slice(i + 1);
 
 
201
  }
202
  }
203
  }
@@ -228,15 +252,24 @@ const handlers = {
228
  ? sessionStore.getUserSession(client.userId, sessionId)
229
  : sessionStore.getTempSession(client.tempId, sessionId);
230
  if (!session) return;
231
- const history = session.history || [];
232
- const m = history[messageIndex]; if (!m) return;
233
- if (!m.versions) m.versions = [{ content: m.content, tail: history.slice(messageIndex + 1), timestamp: m.timestamp || Date.now() }];
234
- m.versions.push({ content: newContent, tail: history.slice(messageIndex + 1), timestamp: Date.now() });
 
 
 
 
 
 
235
  m.currentVersionIdx = m.versions.length - 1;
236
  m.content = newContent;
237
  const newHistory = history.slice(0, messageIndex + 1);
238
- if (client.userId) await sessionStore.updateUserSession(client.userId, client.accessToken, sessionId, { history: newHistory });
239
- else sessionStore.updateTempSession(client.tempId, sessionId, { history: newHistory });
 
 
 
240
  safeSend(ws, { type: 'chat:messageEdited', sessionId, messageIndex, message: m, history: newHistory });
241
  },
242
 
@@ -246,14 +279,17 @@ const handlers = {
246
  ? sessionStore.getUserSession(client.userId, sessionId)
247
  : sessionStore.getTempSession(client.tempId, sessionId);
248
  if (!session) return;
249
- const history = session.history || [];
250
  const m = history[messageIndex];
251
  if (!m?.versions || versionIdx < 0 || versionIdx >= m.versions.length) return;
252
- const v = m.versions[versionIdx];
253
- m.currentVersionIdx = versionIdx; m.content = v.content;
254
- const newHistory = [...history.slice(0, messageIndex + 1), ...(v.tail || [])];
255
- if (client.userId) await sessionStore.updateUserSession(client.userId, client.accessToken, sessionId, { history: newHistory });
256
- else sessionStore.updateTempSession(client.tempId, sessionId, { history: newHistory });
 
 
 
257
  safeSend(ws, { type: 'chat:versionSelected', sessionId, history: newHistory });
258
  },
259
 
@@ -288,6 +324,10 @@ const handlers = {
288
 
289
  function ser(s) { return { id: s.id, name: s.name, created: s.created, history: s.history || [], model: s.model }; }
290
 
 
 
 
 
291
  function buildEntry(role, content, toolCalls = []) {
292
  const normalizedCalls = toolCalls.map(c => ({
293
  id: c.id,
@@ -296,7 +336,50 @@ function buildEntry(role, content, toolCalls = []) {
296
  state: c.state || 'resolved',
297
  result: c.result,
298
  }));
299
- return { role, content, timestamp: Date.now(),
300
- versions: [{ content, tail: [], timestamp: Date.now() }], currentVersionIdx: 0,
301
- ...(normalizedCalls.length ? { toolCalls: normalizedCalls } : {}) };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  }
 
12
  import { streamChat, extractSessionName } from './chatStream.js';
13
  import crypto from 'crypto';
14
 
15
+ /**
16
+ * ─ Message Structure Documentation ─
17
+ *
18
+ * Each message in session.history contains:
19
+ * {
20
+ * id: "msg-123...", // Unique identifier for this message
21
+ * role: "user" | "assistant",
22
+ * content: string | array,
23
+ * timestamp: number,
24
+ * versions: [
25
+ * {
26
+ * content: string | array,
27
+ * nextMessageId: "msg-456..." | null, // ID of next message when this version is active
28
+ * timestamp: number
29
+ * },
30
+ * ...
31
+ * ],
32
+ * currentVersionIdx: 0, // Which version is currently active
33
+ * toolCalls?: [...], // For assistant messages with tool calls
34
+ * }\n *\n * VERSION HISTORY: Changed from storing full message arrays in \"tail\" field\n * to storing only nextMessageId references. This reduces payload size significantly\n * while maintaining backward compatibility through normalizeHistoryStructure().\n *\n * HISTORY RECONSTRUCTION: When switching versions, the message chain is rebuilt\n * by following nextMessageId pointers from the selected version.\n */
35
+
36
  const activeStreams = new Map();
37
 
38
  initGuestRequestLimiter().catch(err => console.error('Failed to initialize guest request limiter:', err));
 
192
  const assetsCollected = [], toolCallsCollected = [];
193
 
194
  await streamChat(ws, {
195
+ history: normalizeHistoryStructure(session.history || []), userMessage: content, tools: tools || {},
196
  accessToken: client.accessToken, clientId: msg.clientId, abortSignal: abort.signal,
197
  onToken(t) { fullText += t; safeSend(ws, { type: 'chat:token', token: t, sessionId }); },
198
  onToolCall(call) {
 
211
  return { ...c, state: resolved.state || 'resolved', result: resolved.result };
212
  });
213
  const asstEntry = buildEntry('assistant', finalText, mergedCalls);
214
+ let newHistory = normalizeHistoryStructure([...(session.history || []), userEntry, asstEntry]);
215
 
216
+ // Update nextMessageId pointers for all versioned messages to link to the next message
217
+ for (let i = 0; i < newHistory.length - 1; i++) {
218
  const msg = newHistory[i];
219
+ const nextMsg = newHistory[i + 1];
220
  if (msg.versions) {
221
  for (const version of msg.versions) {
222
+ if (!version.nextMessageId) {
223
+ version.nextMessageId = nextMsg.id;
224
+ }
225
  }
226
  }
227
  }
 
252
  ? sessionStore.getUserSession(client.userId, sessionId)
253
  : sessionStore.getTempSession(client.tempId, sessionId);
254
  if (!session) return;
255
+ const history = normalizeHistoryStructure(session.history || []);
256
+ const m = history[messageIndex];
257
+ if (!m) return;
258
+ const currentVersionIdx = m.currentVersionIdx ?? 0;
259
+ const currentVersion = m.versions?.[currentVersionIdx];
260
+ const nextMessageId = currentVersion?.nextMessageId || (history[messageIndex + 1]?.id || null);
261
+ if (!m.versions) {
262
+ m.versions = [{ content: m.content, nextMessageId: nextMessageId, timestamp: m.timestamp || Date.now() }];
263
+ }
264
+ m.versions.push({ content: newContent, nextMessageId: nextMessageId, timestamp: Date.now() });
265
  m.currentVersionIdx = m.versions.length - 1;
266
  m.content = newContent;
267
  const newHistory = history.slice(0, messageIndex + 1);
268
+ if (client.userId) {
269
+ await sessionStore.updateUserSession(client.userId, client.accessToken, sessionId, { history: newHistory });
270
+ } else {
271
+ sessionStore.updateTempSession(client.tempId, sessionId, { history: newHistory });
272
+ }
273
  safeSend(ws, { type: 'chat:messageEdited', sessionId, messageIndex, message: m, history: newHistory });
274
  },
275
 
 
279
  ? sessionStore.getUserSession(client.userId, sessionId)
280
  : sessionStore.getTempSession(client.tempId, sessionId);
281
  if (!session) return;
282
+ const history = normalizeHistoryStructure(session.history || []);
283
  const m = history[messageIndex];
284
  if (!m?.versions || versionIdx < 0 || versionIdx >= m.versions.length) return;
285
+ m.currentVersionIdx = versionIdx;
286
+ m.content = m.versions[versionIdx].content;
287
+ const newHistory = reconstructHistoryFromVersion(history, messageIndex, versionIdx);
288
+ if (client.userId) {
289
+ await sessionStore.updateUserSession(client.userId, client.accessToken, sessionId, { history: newHistory });
290
+ } else {
291
+ sessionStore.updateTempSession(client.tempId, sessionId, { history: newHistory });
292
+ }
293
  safeSend(ws, { type: 'chat:versionSelected', sessionId, history: newHistory });
294
  },
295
 
 
324
 
325
  function ser(s) { return { id: s.id, name: s.name, created: s.created, history: s.history || [], model: s.model }; }
326
 
327
+ function generateMessageId() {
328
+ return `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
329
+ }
330
+
331
  function buildEntry(role, content, toolCalls = []) {
332
  const normalizedCalls = toolCalls.map(c => ({
333
  id: c.id,
 
336
  state: c.state || 'resolved',
337
  result: c.result,
338
  }));
339
+ return {
340
+ id: generateMessageId(),
341
+ role,
342
+ content,
343
+ timestamp: Date.now(),
344
+ versions: [{ content, nextMessageId: null, timestamp: Date.now() }],
345
+ currentVersionIdx: 0,
346
+ ...(normalizedCalls.length ? { toolCalls: normalizedCalls } : {})
347
+ };
348
+ }
349
+
350
+ function normalizeHistoryStructure(history) {
351
+ if (!history || !Array.isArray(history)) return [];
352
+ const normalized = history.map((msg, idx) => {
353
+ if (!msg.id) msg.id = `msg-${msg.timestamp || Date.now()}-${idx}`;
354
+ if (msg.versions && Array.isArray(msg.versions)) {
355
+ msg.versions = msg.versions.map(v => {
356
+ if (v.tail !== undefined && !v.nextMessageId) {
357
+ v.nextMessageId = v.tail && v.tail.length > 0 ? v.tail[0].id : null;
358
+ }
359
+ if (!v.nextMessageId) v.nextMessageId = null;
360
+ return v;
361
+ });
362
+ }
363
+ return msg;
364
+ });
365
+ return normalized;
366
+ }
367
+
368
+ function reconstructHistoryFromVersion(history, messageIndex, versionIdx) {
369
+ const baseHistory = history.slice(0, messageIndex + 1);
370
+ const branchMessage = baseHistory[messageIndex];
371
+ if (!branchMessage?.versions || versionIdx >= branchMessage.versions.length) {
372
+ return baseHistory;
373
+ }
374
+ const version = branchMessage.versions[versionIdx];
375
+ if (!version.nextMessageId) return baseHistory;
376
+ const reconstructed = [...baseHistory];
377
+ let currentId = version.nextMessageId;
378
+ while (currentId) {
379
+ const nextMsg = history.find(m => m.id === currentId);
380
+ if (!nextMsg) break;
381
+ reconstructed.push(nextMsg);
382
+ currentId = nextMsg.versions?.[nextMsg.currentVersionIdx]?.nextMessageId || null;
383
+ }
384
+ return reconstructed;
385
  }