incognitolm commited on
Commit Β·
31f4a53
1
Parent(s): fa6106f
Message versions
Browse files- public/css/chat.css +16 -21
- public/css/input.css +16 -10
- public/index.html +3 -3
- public/js/app.js +5 -4
- public/js/chat.js +55 -46
- server/wsHandler.js +1 -4
public/css/chat.css
CHANGED
|
@@ -99,13 +99,20 @@
|
|
| 99 |
|
| 100 |
/* Version navigator */
|
| 101 |
.msg-version-nav {
|
|
|
|
|
|
|
|
|
|
| 102 |
display: flex;
|
| 103 |
align-items: center;
|
| 104 |
gap: 4px;
|
| 105 |
font-size: 12px;
|
| 106 |
color: var(--text-muted);
|
|
|
|
|
|
|
| 107 |
}
|
| 108 |
|
|
|
|
|
|
|
| 109 |
.msg-version-nav button {
|
| 110 |
display: flex; align-items: center; justify-content: center;
|
| 111 |
width: 20px; height: 20px; border-radius: 50%;
|
|
@@ -118,34 +125,22 @@
|
|
| 118 |
.msg-actions {
|
| 119 |
display: flex;
|
| 120 |
gap: 3px;
|
| 121 |
-
}
|
| 122 |
-
|
| 123 |
-
.msg-actions.msg-actions-right {
|
| 124 |
-
justify-content: flex-end;
|
| 125 |
-
}
|
| 126 |
-
|
| 127 |
-
.msg-actions.msg-actions-left {
|
| 128 |
-
justify-content: flex-start;
|
| 129 |
-
}
|
| 130 |
-
|
| 131 |
-
.msg-controls {
|
| 132 |
-
display: flex;
|
| 133 |
-
flex-direction: column;
|
| 134 |
-
gap: 4px;
|
| 135 |
opacity: 0;
|
| 136 |
transition: opacity var(--transition);
|
| 137 |
pointer-events: none;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
}
|
| 139 |
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
.msg-controls.msg-controls-left {
|
| 145 |
-
align-items: flex-start;
|
| 146 |
}
|
| 147 |
|
| 148 |
-
.msg-group:hover .msg-
|
| 149 |
opacity: 1;
|
| 150 |
pointer-events: auto;
|
| 151 |
}
|
|
|
|
| 99 |
|
| 100 |
/* Version navigator */
|
| 101 |
.msg-version-nav {
|
| 102 |
+
position: absolute;
|
| 103 |
+
bottom: -4px;
|
| 104 |
+
right: 0;
|
| 105 |
display: flex;
|
| 106 |
align-items: center;
|
| 107 |
gap: 4px;
|
| 108 |
font-size: 12px;
|
| 109 |
color: var(--text-muted);
|
| 110 |
+
opacity: 0;
|
| 111 |
+
transition: opacity var(--transition);
|
| 112 |
}
|
| 113 |
|
| 114 |
+
.msg-group:hover .msg-version-nav { opacity: 1; }
|
| 115 |
+
|
| 116 |
.msg-version-nav button {
|
| 117 |
display: flex; align-items: center; justify-content: center;
|
| 118 |
width: 20px; height: 20px; border-radius: 50%;
|
|
|
|
| 125 |
.msg-actions {
|
| 126 |
display: flex;
|
| 127 |
gap: 3px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
opacity: 0;
|
| 129 |
transition: opacity var(--transition);
|
| 130 |
pointer-events: none;
|
| 131 |
+
/* Default: left-aligned (assistant messages) */
|
| 132 |
+
position: absolute;
|
| 133 |
+
bottom: 4px;
|
| 134 |
+
left: 0;
|
| 135 |
}
|
| 136 |
|
| 137 |
+
/* Right-aligned (user messages) */
|
| 138 |
+
.msg-actions.msg-actions-right {
|
| 139 |
+
left: auto;
|
| 140 |
+
right: 0;
|
|
|
|
|
|
|
| 141 |
}
|
| 142 |
|
| 143 |
+
.msg-group:hover .msg-actions {
|
| 144 |
opacity: 1;
|
| 145 |
pointer-events: auto;
|
| 146 |
}
|
public/css/input.css
CHANGED
|
@@ -71,6 +71,22 @@
|
|
| 71 |
}
|
| 72 |
.attach-btn:hover { color: var(--text); background: var(--bg-hover); }
|
| 73 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
/* ββ Bottom input bar ββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
|
| 75 |
.bottom-input-bar {
|
| 76 |
flex-shrink: 0;
|
|
@@ -112,16 +128,6 @@
|
|
| 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;
|
|
|
|
| 71 |
}
|
| 72 |
.attach-btn:hover { color: var(--text); background: var(--bg-hover); }
|
| 73 |
|
| 74 |
+
/* Back button */
|
| 75 |
+
.back-row {
|
| 76 |
+
text-align: center;
|
| 77 |
+
margin-bottom: 8px;
|
| 78 |
+
}
|
| 79 |
+
.back-btn {
|
| 80 |
+
padding: 4px 8px;
|
| 81 |
+
font-size: 12px;
|
| 82 |
+
color: var(--text-muted);
|
| 83 |
+
background: none;
|
| 84 |
+
border: none;
|
| 85 |
+
cursor: pointer;
|
| 86 |
+
transition: color var(--transition);
|
| 87 |
+
}
|
| 88 |
+
.back-btn:hover { color: var(--text); }
|
| 89 |
+
|
| 90 |
/* ββ Bottom input bar ββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
|
| 91 |
.bottom-input-bar {
|
| 92 |
flex-shrink: 0;
|
|
|
|
| 128 |
}
|
| 129 |
.attach-btn-bottom:hover { color: var(--text); background: var(--bg-hover); }
|
| 130 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
.bottom-textarea-wrap {
|
| 132 |
flex: 1; min-width: 0;
|
| 133 |
display: flex; flex-direction: column; gap: 6px;
|
public/index.html
CHANGED
|
@@ -122,10 +122,10 @@
|
|
| 122 |
|
| 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>
|
|
|
|
| 122 |
|
| 123 |
<!-- Bottom input (active during chat) -->
|
| 124 |
<div id="bottom-input-bar" class="bottom-input-bar hidden">
|
| 125 |
+
<div id="back-row" class="back-row hidden">
|
| 126 |
+
<button id="back-btn" class="back-btn" title="Back to conversation">β Back</button>
|
| 127 |
+
</div>
|
| 128 |
<div class="bottom-input-wrap">
|
|
|
|
|
|
|
|
|
|
| 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/app.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
| 5 |
createNewSession, showWelcomeScreen,
|
| 6 |
switchSession, currentSessionId, onSessionChange,
|
| 7 |
} from './sessions.js';
|
| 8 |
-
import { submitMessage, renderSession, setActiveSession, getIsStreaming } from './chat.js';
|
| 9 |
import { openAuthModal, closeModal, openPasteEditor } from './modals.js';
|
| 10 |
import { openSettings, applyTheme } from './settings.js';
|
| 11 |
import { showNotification, autoResize, escHtml } from './ui.js';
|
|
@@ -128,7 +128,8 @@ function triggerBottomSend() {
|
|
| 128 |
if (!text && attachments.length === 0) return;
|
| 129 |
if (bottomInput) { bottomInput.value = ''; autoResize(bottomInput, 6); }
|
| 130 |
clearFilePreviewRow();
|
| 131 |
-
|
|
|
|
| 132 |
}
|
| 133 |
|
| 134 |
document.querySelectorAll('.tool-btn-sm').forEach(btn =>
|
|
@@ -136,8 +137,8 @@ document.querySelectorAll('.tool-btn-sm').forEach(btn =>
|
|
| 136 |
|
| 137 |
// ββ Core send βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 138 |
|
| 139 |
-
function doSend(text, attachments = []) {
|
| 140 |
-
submitMessage(text, attachments);
|
| 141 |
}
|
| 142 |
|
| 143 |
// ββ Attachments βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 5 |
createNewSession, showWelcomeScreen,
|
| 6 |
switchSession, currentSessionId, onSessionChange,
|
| 7 |
} from './sessions.js';
|
| 8 |
+
import { submitMessage, renderSession, setActiveSession, getIsStreaming, getReEditingIndex } from './chat.js';
|
| 9 |
import { openAuthModal, closeModal, openPasteEditor } from './modals.js';
|
| 10 |
import { openSettings, applyTheme } from './settings.js';
|
| 11 |
import { showNotification, autoResize, escHtml } from './ui.js';
|
|
|
|
| 128 |
if (!text && attachments.length === 0) return;
|
| 129 |
if (bottomInput) { bottomInput.value = ''; autoResize(bottomInput, 6); }
|
| 130 |
clearFilePreviewRow();
|
| 131 |
+
const editIndex = getReEditingIndex();
|
| 132 |
+
doSend(text || '', attachments, editIndex);
|
| 133 |
}
|
| 134 |
|
| 135 |
document.querySelectorAll('.tool-btn-sm').forEach(btn =>
|
|
|
|
| 137 |
|
| 138 |
// ββ Core send βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 139 |
|
| 140 |
+
function doSend(text, attachments = [], editIndex = undefined) {
|
| 141 |
+
submitMessage(text, attachments, false, editIndex);
|
| 142 |
}
|
| 143 |
|
| 144 |
// ββ Attachments βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
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 {
|
| 4 |
import {
|
| 5 |
renderMarkdown, attachCodeCopyListeners, attachSvgPanelListeners,
|
| 6 |
escHtml, showNotification, autoResize,
|
|
@@ -12,9 +12,9 @@ let streamingBubble = null;
|
|
| 12 |
let streamingText = '';
|
| 13 |
let autoScroll = true;
|
| 14 |
let pendingAssets = [];
|
|
|
|
| 15 |
|
| 16 |
-
|
| 17 |
-
let originalHistory = null;
|
| 18 |
|
| 19 |
export function setActiveSession(id) { activeSessionId = id; }
|
| 20 |
export function getIsStreaming() { return isStreaming; }
|
|
@@ -35,6 +35,11 @@ on('chat:messageEdited', (msg) => {
|
|
| 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,42 +51,7 @@ on('ws:connected', () => {
|
|
| 46 |
}
|
| 47 |
});
|
| 48 |
|
| 49 |
-
|
| 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,9 +140,9 @@ function appendUserMsg(box, msg, index) {
|
|
| 170 |
controls.className = 'msg-controls msg-controls-right';
|
| 171 |
controls.appendChild(buildActions([
|
| 172 |
{ icon: 'π', title: 'Copy', fn: () => copyText(text) },
|
| 173 |
-
{ icon: 'βοΈ', title: 'Edit', fn: () =>
|
| 174 |
], 'right'));
|
| 175 |
-
|
| 176 |
wrap.appendChild(controls);
|
| 177 |
|
| 178 |
box.appendChild(wrap);
|
|
@@ -384,6 +354,27 @@ function startAssistantEdit(wrap, index, msg) {
|
|
| 384 |
ta.setSelectionRange(ta.value.length, ta.value.length);
|
| 385 |
}
|
| 386 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 387 |
function makeUserEditTextarea(value) {
|
| 388 |
const ta = document.createElement('textarea');
|
| 389 |
ta.value = value;
|
|
@@ -495,7 +486,6 @@ function onChatDone(msg) {
|
|
| 495 |
pendingAssets = [];
|
| 496 |
}
|
| 497 |
}
|
| 498 |
-
if (editingMessageIndex !== null) exitEditMode();
|
| 499 |
}
|
| 500 |
|
| 501 |
function onChatAborted(msg) {
|
|
@@ -560,11 +550,19 @@ function appendAsset(asset) {
|
|
| 560 |
|
| 561 |
// ββ Submit ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 562 |
|
| 563 |
-
export function submitMessage(text, attachments = [], regenerate = false) {
|
| 564 |
if (!text.trim() && attachments.length === 0 && !regenerate) return;
|
| 565 |
if (isStreaming) { send({ type: 'chat:stop' }); return; }
|
| 566 |
if (!activeSessionId) return;
|
| 567 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 568 |
const images = attachments.filter(a => a.type === 'image');
|
| 569 |
const textFiles= attachments.filter(a => a.type === 'text');
|
| 570 |
|
|
@@ -664,10 +662,21 @@ function openImageModal(src) {
|
|
| 664 |
import('./modals.js').then(m => m.openImageModal(src));
|
| 665 |
}
|
| 666 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 667 |
// Scroll tracking
|
| 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);
|
|
|
|
| 1 |
// chat.js β Chat rendering, streaming, versioning, editing
|
| 2 |
import { send, on, off } from './ws.js';
|
| 3 |
+
import { currentSessionId, getCurrentSession } from './sessions.js';
|
| 4 |
import {
|
| 5 |
renderMarkdown, attachCodeCopyListeners, attachSvgPanelListeners,
|
| 6 |
escHtml, showNotification, autoResize,
|
|
|
|
| 12 |
let streamingText = '';
|
| 13 |
let autoScroll = true;
|
| 14 |
let pendingAssets = [];
|
| 15 |
+
let reEditingIndex = null;
|
| 16 |
|
| 17 |
+
export function getReEditingIndex() { return reEditingIndex; }
|
|
|
|
| 18 |
|
| 19 |
export function setActiveSession(id) { activeSessionId = id; }
|
| 20 |
export function getIsStreaming() { return isStreaming; }
|
|
|
|
| 35 |
// Update the session history
|
| 36 |
const s = sessions.find(s => s.id === msg.sessionId);
|
| 37 |
if (s) s.history = msg.history;
|
| 38 |
+
// If edited message is user, regenerate the response
|
| 39 |
+
const editedMsg = msg.history[msg.messageIndex];
|
| 40 |
+
if (editedMsg && editedMsg.role === 'user') {
|
| 41 |
+
submitMessage('', [], true);
|
| 42 |
+
}
|
| 43 |
}
|
| 44 |
});
|
| 45 |
on('chat:versionSelected', (msg) => { if (msg.sessionId === activeSessionId) renderHistory(msg.history); });
|
|
|
|
| 51 |
}
|
| 52 |
});
|
| 53 |
|
| 54 |
+
// ββ Views βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
export function renderSession(session) {
|
| 57 |
if (!session || !session.history?.length) { showWelcome(); return; }
|
|
|
|
| 140 |
controls.className = 'msg-controls msg-controls-right';
|
| 141 |
controls.appendChild(buildActions([
|
| 142 |
{ icon: 'π', title: 'Copy', fn: () => copyText(text) },
|
| 143 |
+
{ icon: 'βοΈ', title: 'Edit', fn: () => startUserReEdit(wrap, index, msg, text) },
|
| 144 |
], 'right'));
|
| 145 |
+
if (msg.versions?.length > 1) controls.appendChild(buildVersionNav(msg, index));
|
| 146 |
wrap.appendChild(controls);
|
| 147 |
|
| 148 |
box.appendChild(wrap);
|
|
|
|
| 354 |
ta.setSelectionRange(ta.value.length, ta.value.length);
|
| 355 |
}
|
| 356 |
|
| 357 |
+
function startUserReEdit(wrap, index, msg, text) {
|
| 358 |
+
reEditingIndex = index;
|
| 359 |
+
// Truncate the displayed history to this message
|
| 360 |
+
const session = getCurrentSession();
|
| 361 |
+
if (session) {
|
| 362 |
+
const truncated = session.history.slice(0, index + 1);
|
| 363 |
+
renderHistory(truncated);
|
| 364 |
+
}
|
| 365 |
+
// Put the text in bottom input
|
| 366 |
+
const bottomInput = document.getElementById('bottom-input');
|
| 367 |
+
if (bottomInput) {
|
| 368 |
+
bottomInput.value = text;
|
| 369 |
+
autoResize(bottomInput);
|
| 370 |
+
bottomInput.focus();
|
| 371 |
+
}
|
| 372 |
+
// Show back button
|
| 373 |
+
document.getElementById('back-row')?.classList.remove('hidden');
|
| 374 |
+
// Clear attachments
|
| 375 |
+
clearFilePreviewRow();
|
| 376 |
+
}
|
| 377 |
+
|
| 378 |
function makeUserEditTextarea(value) {
|
| 379 |
const ta = document.createElement('textarea');
|
| 380 |
ta.value = value;
|
|
|
|
| 486 |
pendingAssets = [];
|
| 487 |
}
|
| 488 |
}
|
|
|
|
| 489 |
}
|
| 490 |
|
| 491 |
function onChatAborted(msg) {
|
|
|
|
| 550 |
|
| 551 |
// ββ Submit ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 552 |
|
| 553 |
+
export function submitMessage(text, attachments = [], regenerate = false, editIndex = undefined) {
|
| 554 |
if (!text.trim() && attachments.length === 0 && !regenerate) return;
|
| 555 |
if (isStreaming) { send({ type: 'chat:stop' }); return; }
|
| 556 |
if (!activeSessionId) return;
|
| 557 |
|
| 558 |
+
if (editIndex !== undefined) {
|
| 559 |
+
// Send edit
|
| 560 |
+
send({ type: 'chat:editMessage', sessionId: activeSessionId, messageIndex: editIndex, newContent: text });
|
| 561 |
+
reEditingIndex = null;
|
| 562 |
+
document.getElementById('back-row')?.classList.add('hidden');
|
| 563 |
+
return;
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
const images = attachments.filter(a => a.type === 'image');
|
| 567 |
const textFiles= attachments.filter(a => a.type === 'text');
|
| 568 |
|
|
|
|
| 662 |
import('./modals.js').then(m => m.openImageModal(src));
|
| 663 |
}
|
| 664 |
|
| 665 |
+
// Back button for re-editing
|
| 666 |
+
document.getElementById('back-btn')?.addEventListener('click', () => {
|
| 667 |
+
reEditingIndex = null;
|
| 668 |
+
// Restore full history
|
| 669 |
+
const session = getCurrentSession();
|
| 670 |
+
if (session) renderHistory(session.history);
|
| 671 |
+
// Clear input
|
| 672 |
+
const bottomInput = document.getElementById('bottom-input');
|
| 673 |
+
if (bottomInput) bottomInput.value = '';
|
| 674 |
+
// Hide back
|
| 675 |
+
document.getElementById('back-row')?.classList.add('hidden');
|
| 676 |
+
});
|
| 677 |
+
|
| 678 |
// Scroll tracking
|
| 679 |
document.getElementById('chat-view')?.addEventListener('scroll', e => {
|
| 680 |
const el = e.target;
|
| 681 |
autoScroll = el.scrollHeight - el.scrollTop - el.clientHeight < 60;
|
| 682 |
+
}, true);
|
|
|
|
|
|
server/wsHandler.js
CHANGED
|
@@ -218,10 +218,7 @@ const handlers = {
|
|
| 218 |
m.versions.unshift({ content: newContent, tail: history.slice(messageIndex + 1), timestamp: Date.now() });
|
| 219 |
m.currentVersionIdx = 0;
|
| 220 |
m.content = newContent;
|
| 221 |
-
|
| 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 });
|
|
|
|
| 218 |
m.versions.unshift({ content: newContent, tail: history.slice(messageIndex + 1), timestamp: Date.now() });
|
| 219 |
m.currentVersionIdx = 0;
|
| 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 });
|