incognitolm commited on
Commit
e70ae7e
Β·
1 Parent(s): f8048ee
Files changed (3) hide show
  1. public/js/chat.js +11 -27
  2. server/chatStream.js +1 -1
  3. server/wsHandler.js +0 -71
public/js/chat.js CHANGED
@@ -26,16 +26,7 @@ on('chat:aborted', (msg) => { if (msg.sessionId === activeSessionId) on
26
  on('chat:error', (msg) => { if (msg.sessionId === activeSessionId) onChatError(msg.error); });
27
  on('chat:asset', (msg) => { if (msg.sessionId === activeSessionId) appendAsset(msg.asset); });
28
  on('chat:toolCall', (msg) => { if (msg.sessionId === activeSessionId) handleLiveToolCall(msg.call); });
29
- on('chat:messageEdited', (msg) => {
30
- if (msg.sessionId === activeSessionId) {
31
- renderHistory(msg.history);
32
- // Update the session history
33
- const s = sessions.find(s => s.id === msg.sessionId);
34
- if (s) s.history = msg.history;
35
- // After any edit, continue the conversation from the edited branch.
36
- submitMessage('', [], true);
37
- }
38
- });
39
  on('chat:versionSelected', (msg) => { if (msg.sessionId === activeSessionId) renderHistory(msg.history); });
40
 
41
  // Reconnect: reload current session instead of resetting to welcome
@@ -130,14 +121,13 @@ function appendUserMsg(box, msg, index) {
130
 
131
  wrap.appendChild(bubble);
132
 
133
- const controls = document.createElement('div');
134
- controls.className = 'msg-controls msg-controls-right';
135
- controls.appendChild(buildActions([
136
  { icon: 'πŸ“‹', title: 'Copy', fn: () => copyText(text) },
137
  { icon: '✏️', title: 'Edit', fn: () => startUserEdit(wrap, index, msg, text) },
138
  ], 'right'));
139
- if (msg.versions?.length > 1) controls.appendChild(buildVersionNav(msg, index));
140
- wrap.appendChild(controls);
141
 
142
  box.appendChild(wrap);
143
  }
@@ -159,14 +149,13 @@ function appendAssistantMsg(box, msg, index) {
159
 
160
  wrap.appendChild(bubble);
161
 
162
- const controls = document.createElement('div');
163
- controls.className = 'msg-controls msg-controls-left';
164
- controls.appendChild(buildActions([
165
  { icon: 'πŸ“‹', title: 'Copy', fn: () => copyText(msg.content || '') },
166
  { icon: '✏️', title: 'Edit', fn: () => startAssistantEdit(wrap, index, msg) },
167
  ], 'left'));
168
- if (msg.versions?.length > 1) controls.appendChild(buildVersionNav(msg, index));
169
- wrap.appendChild(controls);
170
 
171
  box.appendChild(wrap);
172
  }
@@ -523,16 +512,11 @@ function appendAsset(asset) {
523
 
524
  // ── Submit ────────────────────────────────────────────────────────────────
525
 
526
- export function submitMessage(text, attachments = [], regenerate = false) {
527
- if (!text.trim() && attachments.length === 0 && !regenerate) return;
528
  if (isStreaming) { send({ type: 'chat:stop' }); return; }
529
  if (!activeSessionId) return;
530
 
531
- if (regenerate) {
532
- send({ type: 'chat:regenerate', sessionId: activeSessionId, tools: getActiveTools(), clientId: localStorage.getItem('ipai_client_id') || '' });
533
- return;
534
- }
535
-
536
  const images = attachments.filter(a => a.type === 'image');
537
  const textFiles= attachments.filter(a => a.type === 'text');
538
 
 
26
  on('chat:error', (msg) => { if (msg.sessionId === activeSessionId) onChatError(msg.error); });
27
  on('chat:asset', (msg) => { if (msg.sessionId === activeSessionId) appendAsset(msg.asset); });
28
  on('chat:toolCall', (msg) => { if (msg.sessionId === activeSessionId) handleLiveToolCall(msg.call); });
29
+ on('chat:messageEdited', (msg) => { if (msg.sessionId === activeSessionId) renderHistory(msg.history); });
 
 
 
 
 
 
 
 
 
30
  on('chat:versionSelected', (msg) => { if (msg.sessionId === activeSessionId) renderHistory(msg.history); });
31
 
32
  // Reconnect: reload current session instead of resetting to welcome
 
121
 
122
  wrap.appendChild(bubble);
123
 
124
+ // User actions appear on the RIGHT side
125
+ wrap.appendChild(buildActions([
 
126
  { icon: 'πŸ“‹', title: 'Copy', fn: () => copyText(text) },
127
  { icon: '✏️', title: 'Edit', fn: () => startUserEdit(wrap, index, msg, text) },
128
  ], 'right'));
129
+
130
+ if (msg.versions?.length > 1) wrap.appendChild(buildVersionNav(msg, index));
131
 
132
  box.appendChild(wrap);
133
  }
 
149
 
150
  wrap.appendChild(bubble);
151
 
152
+ // Assistant actions appear on the LEFT side
153
+ wrap.appendChild(buildActions([
 
154
  { icon: 'πŸ“‹', title: 'Copy', fn: () => copyText(msg.content || '') },
155
  { icon: '✏️', title: 'Edit', fn: () => startAssistantEdit(wrap, index, msg) },
156
  ], 'left'));
157
+
158
+ if (msg.versions?.length > 1) wrap.appendChild(buildVersionNav(msg, index));
159
 
160
  box.appendChild(wrap);
161
  }
 
512
 
513
  // ── Submit ────────────────────────────────────────────────────────────────
514
 
515
+ export function submitMessage(text, attachments = []) {
516
+ if (!text.trim() && attachments.length === 0) return;
517
  if (isStreaming) { send({ type: 'chat:stop' }); return; }
518
  if (!activeSessionId) return;
519
 
 
 
 
 
 
520
  const images = attachments.filter(a => a.type === 'image');
521
  const textFiles= attachments.filter(a => a.type === 'text');
522
 
server/chatStream.js CHANGED
@@ -126,7 +126,7 @@ export async function streamChat(ws, {
126
  const messages = [
127
  { role: "system", content: SYSTEM_PROMPT },
128
  ...history.map(normalizeMessage).filter(Boolean),
129
- ...(typeof userMessage === 'undefined' ? [] : [{ role: "user", content: userMessage }]),
130
  ];
131
 
132
  try {
 
126
  const messages = [
127
  { role: "system", content: SYSTEM_PROMPT },
128
  ...history.map(normalizeMessage).filter(Boolean),
129
+ { role: "user", content: userMessage },
130
  ];
131
 
132
  try {
server/wsHandler.js CHANGED
@@ -185,7 +185,6 @@ const handlers = {
185
  });
186
  const asstEntry = buildEntry('assistant', finalText, mergedCalls);
187
  const newHistory = [...(session.history || []), userEntry, asstEntry];
188
- attachCurrentVersionTail(newHistory);
189
 
190
  // Use session name from XML tag; fall back to existing name or default
191
  let newName = session.name;
@@ -205,61 +204,6 @@ const handlers = {
205
  });
206
  },
207
 
208
- 'chat:regenerate': async (ws, msg, client) => {
209
- const { sessionId, tools } = msg;
210
- if (!client.userId) {
211
- if (!sessionStore.tempCanSend(client.tempId)) return safeSend(ws, { type: 'chat:limitReached' });
212
- sessionStore.tempBump(client.tempId);
213
- }
214
- const session = client.userId
215
- ? sessionStore.getUserSession(client.userId, sessionId)
216
- : sessionStore.getTempSession(client.tempId, sessionId);
217
- if (!session) return safeSend(ws, { type: 'error', message: 'Session not found' });
218
-
219
- if (activeStreams.has(ws)) activeStreams.get(ws).abort();
220
- const abort = new AbortController();
221
- activeStreams.set(ws, abort);
222
- safeSend(ws, { type: 'chat:start', sessionId });
223
-
224
- let fullText = '';
225
- const assetsCollected = [], toolCallsCollected = [];
226
-
227
- await streamChat(ws, {
228
- history: session.history || [], tools: tools || {},
229
- accessToken: client.accessToken, clientId: msg.clientId, abortSignal: abort.signal,
230
- onToken(t) { fullText += t; safeSend(ws, { type: 'chat:token', token: t, sessionId }); },
231
- onToolCall(call) {
232
- safeSend(ws, { type: 'chat:toolCall', call, sessionId });
233
- if (call.state === 'resolved' || call.state === 'canceled') toolCallsCollected.push(call);
234
- },
235
- onNewAsset(asset) { safeSend(ws, { type: 'chat:asset', asset, sessionId }); assetsCollected.push(asset); },
236
- async onDone(text, toolCalls, aborted, sessionNameFromTag) {
237
- activeStreams.delete(ws);
238
- const finalText = text || fullText;
239
-
240
- const resolvedMap = new Map(toolCallsCollected.map(c => [c.id, c]));
241
- const mergedCalls = (toolCalls || []).map(c => {
242
- const resolved = resolvedMap.get(c.id) || {};
243
- return { ...c, state: resolved.state || 'resolved', result: resolved.result };
244
- });
245
- const asstEntry = buildEntry('assistant', finalText, mergedCalls);
246
- const newHistory = [...(session.history || []), asstEntry];
247
- attachCurrentVersionTail(newHistory);
248
-
249
- let newName = session.name;
250
- if (sessionNameFromTag) {
251
- newName = sessionNameFromTag;
252
- }
253
-
254
- if (client.userId)
255
- await sessionStore.updateUserSession(client.userId, client.accessToken, sessionId, { history: newHistory, name: newName });
256
- else sessionStore.updateTempSession(client.tempId, sessionId, { history: newHistory, name: newName });
257
- safeSend(ws, { type: aborted ? 'chat:aborted' : 'chat:done', sessionId, name: newName, history: newHistory });
258
- },
259
- onError(err) { activeStreams.delete(ws); safeSend(ws, { type: 'chat:error', error: String(err), sessionId }); },
260
- });
261
- },
262
-
263
  'chat:stop': (ws) => { if (activeStreams.has(ws)) { activeStreams.get(ws).abort(); activeStreams.delete(ws); } },
264
 
265
  'chat:editMessage': async (ws, msg, client) => {
@@ -275,7 +219,6 @@ const handlers = {
275
  m.currentVersionIdx = m.versions.length - 1;
276
  m.content = newContent;
277
  const newHistory = history.slice(0, messageIndex + 1);
278
- attachCurrentVersionTail(newHistory);
279
  if (client.userId) await sessionStore.updateUserSession(client.userId, client.accessToken, sessionId, { history: newHistory });
280
  else sessionStore.updateTempSession(client.tempId, sessionId, { history: newHistory });
281
  safeSend(ws, { type: 'chat:messageEdited', sessionId, messageIndex, message: m, history: newHistory });
@@ -293,7 +236,6 @@ const handlers = {
293
  const v = m.versions[versionIdx];
294
  m.currentVersionIdx = versionIdx; m.content = v.content;
295
  const newHistory = [...history.slice(0, messageIndex + 1), ...(v.tail || [])];
296
- attachCurrentVersionTail(newHistory);
297
  if (client.userId) await sessionStore.updateUserSession(client.userId, client.accessToken, sessionId, { history: newHistory });
298
  else sessionStore.updateTempSession(client.tempId, sessionId, { history: newHistory });
299
  safeSend(ws, { type: 'chat:versionSelected', sessionId, history: newHistory });
@@ -328,19 +270,6 @@ const handlers = {
328
  },
329
  };
330
 
331
- function attachCurrentVersionTail(history) {
332
- for (let i = history.length - 1; i >= 0; i--) {
333
- const msg = history[i];
334
- if (msg?.versions && Number.isInteger(msg.currentVersionIdx)) {
335
- const current = msg.versions[msg.currentVersionIdx];
336
- if (current) {
337
- current.tail = history.slice(i + 1);
338
- }
339
- break;
340
- }
341
- }
342
- }
343
-
344
  function ser(s) { return { id: s.id, name: s.name, created: s.created, history: s.history || [], model: s.model }; }
345
 
346
  function buildEntry(role, content, toolCalls = []) {
 
185
  });
186
  const asstEntry = buildEntry('assistant', finalText, mergedCalls);
187
  const newHistory = [...(session.history || []), userEntry, asstEntry];
 
188
 
189
  // Use session name from XML tag; fall back to existing name or default
190
  let newName = session.name;
 
204
  });
205
  },
206
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  'chat:stop': (ws) => { if (activeStreams.has(ws)) { activeStreams.get(ws).abort(); activeStreams.delete(ws); } },
208
 
209
  'chat:editMessage': async (ws, msg, client) => {
 
219
  m.currentVersionIdx = m.versions.length - 1;
220
  m.content = newContent;
221
  const newHistory = history.slice(0, messageIndex + 1);
 
222
  if (client.userId) await sessionStore.updateUserSession(client.userId, client.accessToken, sessionId, { history: newHistory });
223
  else sessionStore.updateTempSession(client.tempId, sessionId, { history: newHistory });
224
  safeSend(ws, { type: 'chat:messageEdited', sessionId, messageIndex, message: m, history: newHistory });
 
236
  const v = m.versions[versionIdx];
237
  m.currentVersionIdx = versionIdx; m.content = v.content;
238
  const newHistory = [...history.slice(0, messageIndex + 1), ...(v.tail || [])];
 
239
  if (client.userId) await sessionStore.updateUserSession(client.userId, client.accessToken, sessionId, { history: newHistory });
240
  else sessionStore.updateTempSession(client.tempId, sessionId, { history: newHistory });
241
  safeSend(ws, { type: 'chat:versionSelected', sessionId, history: newHistory });
 
270
  },
271
  };
272
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  function ser(s) { return { id: s.id, name: s.name, created: s.created, history: s.history || [], model: s.model }; }
274
 
275
  function buildEntry(role, content, toolCalls = []) {