incognitolm commited on
Commit Β·
fa6106f
1
Parent(s): 55c54a4
Message Versions - NEW
Browse filesUses regular input bar for prompt editing for simplified tool/attachment management
- public/css/input.css +10 -0
- public/index.html +3 -0
- public/js/chat.js +46 -10
- server/wsHandler.js +6 -3
public/css/input.css
CHANGED
|
@@ -112,6 +112,16 @@
|
|
| 112 |
}
|
| 113 |
.attach-btn-bottom:hover { color: var(--text); background: var(--bg-hover); }
|
| 114 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
.bottom-textarea-wrap {
|
| 116 |
flex: 1; min-width: 0;
|
| 117 |
display: flex; flex-direction: column; gap: 6px;
|
|
|
|
| 112 |
}
|
| 113 |
.attach-btn-bottom:hover { color: var(--text); background: var(--bg-hover); }
|
| 114 |
|
| 115 |
+
.back-btn-bottom {
|
| 116 |
+
flex-shrink: 0;
|
| 117 |
+
display: flex; align-items: center; justify-content: center;
|
| 118 |
+
width: 30px; height: 30px; border-radius: 50%;
|
| 119 |
+
color: var(--text-muted);
|
| 120 |
+
margin-bottom: 4px;
|
| 121 |
+
transition: color var(--transition), background var(--transition);
|
| 122 |
+
}
|
| 123 |
+
.back-btn-bottom:hover { color: var(--text); background: var(--bg-hover); }
|
| 124 |
+
|
| 125 |
.bottom-textarea-wrap {
|
| 126 |
flex: 1; min-width: 0;
|
| 127 |
display: flex; flex-direction: column; gap: 6px;
|
public/index.html
CHANGED
|
@@ -123,6 +123,9 @@
|
|
| 123 |
<!-- Bottom input (active during chat) -->
|
| 124 |
<div id="bottom-input-bar" class="bottom-input-bar hidden">
|
| 125 |
<div class="bottom-input-wrap">
|
|
|
|
|
|
|
|
|
|
| 126 |
<button id="bottom-attach-btn" class="attach-btn-bottom" title="Attach">
|
| 127 |
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
| 128 |
</button>
|
|
|
|
| 123 |
<!-- Bottom input (active during chat) -->
|
| 124 |
<div id="bottom-input-bar" class="bottom-input-bar hidden">
|
| 125 |
<div class="bottom-input-wrap">
|
| 126 |
+
<button id="bottom-back-btn" class="back-btn-bottom hidden" title="Back to conversation">
|
| 127 |
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
|
| 128 |
+
</button>
|
| 129 |
<button id="bottom-attach-btn" class="attach-btn-bottom" title="Attach">
|
| 130 |
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
| 131 |
</button>
|
public/js/chat.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
// chat.js β Chat rendering, streaming, versioning, editing
|
| 2 |
import { send, on, off } from './ws.js';
|
| 3 |
-
import { currentSessionId } from './sessions.js';
|
| 4 |
import {
|
| 5 |
renderMarkdown, attachCodeCopyListeners, attachSvgPanelListeners,
|
| 6 |
escHtml, showNotification, autoResize,
|
|
@@ -13,6 +13,9 @@ let streamingText = '';
|
|
| 13 |
let autoScroll = true;
|
| 14 |
let pendingAssets = [];
|
| 15 |
|
|
|
|
|
|
|
|
|
|
| 16 |
export function setActiveSession(id) { activeSessionId = id; }
|
| 17 |
export function getIsStreaming() { return isStreaming; }
|
| 18 |
|
|
@@ -32,11 +35,6 @@ on('chat:messageEdited', (msg) => {
|
|
| 32 |
// Update the session history
|
| 33 |
const s = sessions.find(s => s.id === msg.sessionId);
|
| 34 |
if (s) s.history = msg.history;
|
| 35 |
-
// If edited message is user, regenerate the response
|
| 36 |
-
const editedMsg = msg.history[msg.messageIndex];
|
| 37 |
-
if (editedMsg && editedMsg.role === 'user') {
|
| 38 |
-
submitMessage('', [], true);
|
| 39 |
-
}
|
| 40 |
}
|
| 41 |
});
|
| 42 |
on('chat:versionSelected', (msg) => { if (msg.sessionId === activeSessionId) renderHistory(msg.history); });
|
|
@@ -48,7 +46,42 @@ on('ws:connected', () => {
|
|
| 48 |
}
|
| 49 |
});
|
| 50 |
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
export function renderSession(session) {
|
| 54 |
if (!session || !session.history?.length) { showWelcome(); return; }
|
|
@@ -137,9 +170,9 @@ function appendUserMsg(box, msg, index) {
|
|
| 137 |
controls.className = 'msg-controls msg-controls-right';
|
| 138 |
controls.appendChild(buildActions([
|
| 139 |
{ icon: 'π', title: 'Copy', fn: () => copyText(text) },
|
| 140 |
-
{ icon: 'βοΈ', title: 'Edit', fn: () =>
|
| 141 |
], 'right'));
|
| 142 |
-
|
| 143 |
wrap.appendChild(controls);
|
| 144 |
|
| 145 |
box.appendChild(wrap);
|
|
@@ -462,6 +495,7 @@ function onChatDone(msg) {
|
|
| 462 |
pendingAssets = [];
|
| 463 |
}
|
| 464 |
}
|
|
|
|
| 465 |
}
|
| 466 |
|
| 467 |
function onChatAborted(msg) {
|
|
@@ -634,4 +668,6 @@ function openImageModal(src) {
|
|
| 634 |
document.getElementById('chat-view')?.addEventListener('scroll', e => {
|
| 635 |
const el = e.target;
|
| 636 |
autoScroll = el.scrollHeight - el.scrollTop - el.clientHeight < 60;
|
| 637 |
-
}, true);
|
|
|
|
|
|
|
|
|
| 1 |
// chat.js β Chat rendering, streaming, versioning, editing
|
| 2 |
import { send, on, off } from './ws.js';
|
| 3 |
+
import { sessions, currentSessionId } from './sessions.js';
|
| 4 |
import {
|
| 5 |
renderMarkdown, attachCodeCopyListeners, attachSvgPanelListeners,
|
| 6 |
escHtml, showNotification, autoResize,
|
|
|
|
| 13 |
let autoScroll = true;
|
| 14 |
let pendingAssets = [];
|
| 15 |
|
| 16 |
+
let editingMessageIndex = null;
|
| 17 |
+
let originalHistory = null;
|
| 18 |
+
|
| 19 |
export function setActiveSession(id) { activeSessionId = id; }
|
| 20 |
export function getIsStreaming() { return isStreaming; }
|
| 21 |
|
|
|
|
| 35 |
// Update the session history
|
| 36 |
const s = sessions.find(s => s.id === msg.sessionId);
|
| 37 |
if (s) s.history = msg.history;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
}
|
| 39 |
});
|
| 40 |
on('chat:versionSelected', (msg) => { if (msg.sessionId === activeSessionId) renderHistory(msg.history); });
|
|
|
|
| 46 |
}
|
| 47 |
});
|
| 48 |
|
| 49 |
+
function enterEditMode(messageIndex) {
|
| 50 |
+
const session = sessions.find(s => s.id === activeSessionId);
|
| 51 |
+
if (!session) return;
|
| 52 |
+
editingMessageIndex = messageIndex;
|
| 53 |
+
originalHistory = [...session.history];
|
| 54 |
+
const truncatedHistory = session.history.slice(0, messageIndex + 1);
|
| 55 |
+
session.history = truncatedHistory;
|
| 56 |
+
renderHistory(truncatedHistory);
|
| 57 |
+
// Move content to bottom input
|
| 58 |
+
const msg = truncatedHistory[messageIndex];
|
| 59 |
+
const bottomInput = document.getElementById('bottom-input');
|
| 60 |
+
if (bottomInput) {
|
| 61 |
+
bottomInput.value = msg.content;
|
| 62 |
+
autoResize(bottomInput);
|
| 63 |
+
bottomInput.focus();
|
| 64 |
+
}
|
| 65 |
+
// Show back button
|
| 66 |
+
document.getElementById('bottom-back-btn')?.classList.remove('hidden');
|
| 67 |
+
// Clear attachments
|
| 68 |
+
clearFilePreviewRow();
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
function exitEditMode() {
|
| 72 |
+
if (originalHistory) {
|
| 73 |
+
const session = sessions.find(s => s.id === activeSessionId);
|
| 74 |
+
if (session) session.history = originalHistory;
|
| 75 |
+
renderHistory(originalHistory);
|
| 76 |
+
}
|
| 77 |
+
editingMessageIndex = null;
|
| 78 |
+
originalHistory = null;
|
| 79 |
+
// Clear input
|
| 80 |
+
const bottomInput = document.getElementById('bottom-input');
|
| 81 |
+
if (bottomInput) bottomInput.value = '';
|
| 82 |
+
// Hide back button
|
| 83 |
+
document.getElementById('bottom-back-btn')?.classList.add('hidden');
|
| 84 |
+
}
|
| 85 |
|
| 86 |
export function renderSession(session) {
|
| 87 |
if (!session || !session.history?.length) { showWelcome(); return; }
|
|
|
|
| 170 |
controls.className = 'msg-controls msg-controls-right';
|
| 171 |
controls.appendChild(buildActions([
|
| 172 |
{ icon: 'π', title: 'Copy', fn: () => copyText(text) },
|
| 173 |
+
{ icon: 'βοΈ', title: 'Edit', fn: () => enterEditMode(index) },
|
| 174 |
], 'right'));
|
| 175 |
+
// No versions for user messages
|
| 176 |
wrap.appendChild(controls);
|
| 177 |
|
| 178 |
box.appendChild(wrap);
|
|
|
|
| 495 |
pendingAssets = [];
|
| 496 |
}
|
| 497 |
}
|
| 498 |
+
if (editingMessageIndex !== null) exitEditMode();
|
| 499 |
}
|
| 500 |
|
| 501 |
function onChatAborted(msg) {
|
|
|
|
| 668 |
document.getElementById('chat-view')?.addEventListener('scroll', e => {
|
| 669 |
const el = e.target;
|
| 670 |
autoScroll = el.scrollHeight - el.scrollTop - el.clientHeight < 60;
|
| 671 |
+
}, true);
|
| 672 |
+
|
| 673 |
+
document.getElementById('bottom-back-btn')?.addEventListener('click', exitEditMode);
|
server/wsHandler.js
CHANGED
|
@@ -215,10 +215,13 @@ const handlers = {
|
|
| 215 |
const history = session.history || [];
|
| 216 |
const m = history[messageIndex]; if (!m) return;
|
| 217 |
if (!m.versions) m.versions = [{ content: m.content, tail: history.slice(messageIndex + 1), timestamp: m.timestamp || Date.now() }];
|
| 218 |
-
m.versions.
|
| 219 |
-
m.currentVersionIdx =
|
| 220 |
m.content = newContent;
|
| 221 |
-
|
|
|
|
|
|
|
|
|
|
| 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 });
|
|
|
|
| 215 |
const history = session.history || [];
|
| 216 |
const m = history[messageIndex]; if (!m) return;
|
| 217 |
if (!m.versions) m.versions = [{ content: m.content, tail: history.slice(messageIndex + 1), timestamp: m.timestamp || Date.now() }];
|
| 218 |
+
m.versions.unshift({ content: newContent, tail: history.slice(messageIndex + 1), timestamp: Date.now() });
|
| 219 |
+
m.currentVersionIdx = 0;
|
| 220 |
m.content = newContent;
|
| 221 |
+
let newHistory = history;
|
| 222 |
+
if (m.role === 'user') {
|
| 223 |
+
newHistory = history.slice(0, messageIndex + 1);
|
| 224 |
+
}
|
| 225 |
if (client.userId) await sessionStore.updateUserSession(client.userId, client.accessToken, sessionId, { history: newHistory });
|
| 226 |
else sessionStore.updateTempSession(client.tempId, sessionId, { history: newHistory });
|
| 227 |
safeSend(ws, { type: 'chat:messageEdited', sessionId, messageIndex, message: m, history: newHistory });
|