incognitolm commited on
Commit
fa6106f
Β·
1 Parent(s): 55c54a4

Message Versions - NEW

Browse files

Uses regular input bar for prompt editing for simplified tool/attachment management

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
- // ── Views ─────────────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: () => startUserEdit(wrap, index, msg, text) },
141
  ], 'right'));
142
- if (msg.versions?.length > 1) controls.appendChild(buildVersionNav(msg, index));
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.push({ content: newContent, tail: history.slice(messageIndex + 1), timestamp: Date.now() });
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 });
 
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 });