incognitolm commited on
Commit Β·
e70ae7e
1
Parent(s): f8048ee
Revert
Browse files- public/js/chat.js +11 -27
- server/chatStream.js +1 -1
- 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 |
-
|
| 134 |
-
|
| 135 |
-
controls.appendChild(buildActions([
|
| 136 |
{ icon: 'π', title: 'Copy', fn: () => copyText(text) },
|
| 137 |
{ icon: 'βοΈ', title: 'Edit', fn: () => startUserEdit(wrap, index, msg, text) },
|
| 138 |
], 'right'));
|
| 139 |
-
|
| 140 |
-
wrap.appendChild(
|
| 141 |
|
| 142 |
box.appendChild(wrap);
|
| 143 |
}
|
|
@@ -159,14 +149,13 @@ function appendAssistantMsg(box, msg, index) {
|
|
| 159 |
|
| 160 |
wrap.appendChild(bubble);
|
| 161 |
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
controls.appendChild(buildActions([
|
| 165 |
{ icon: 'π', title: 'Copy', fn: () => copyText(msg.content || '') },
|
| 166 |
{ icon: 'βοΈ', title: 'Edit', fn: () => startAssistantEdit(wrap, index, msg) },
|
| 167 |
], 'left'));
|
| 168 |
-
|
| 169 |
-
wrap.appendChild(
|
| 170 |
|
| 171 |
box.appendChild(wrap);
|
| 172 |
}
|
|
@@ -523,16 +512,11 @@ function appendAsset(asset) {
|
|
| 523 |
|
| 524 |
// ββ Submit ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 525 |
|
| 526 |
-
export function submitMessage(text, attachments = []
|
| 527 |
-
if (!text.trim() && attachments.length === 0
|
| 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 |
-
|
| 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 = []) {
|