spagestic commited on
Commit
4c5a050
·
1 Parent(s): b3e01f2

Chat history rename and delete are implemented

Browse files
Files changed (2) hide show
  1. assets/app.js +140 -10
  2. assets/server.css +62 -5
assets/app.js CHANGED
@@ -185,15 +185,20 @@ function persistActiveSession() {
185
 
186
  const sessions = loadSessions();
187
  const now = Date.now();
 
 
 
 
 
 
188
  const payload = {
189
  id: state.sessionId,
190
- title: sessionTitle(state.history),
191
  updatedAt: now,
192
  history: state.history,
193
  globeState: state.globeState,
194
  };
195
 
196
- const index = sessions.findIndex((session) => session.id === state.sessionId);
197
  if (index >= 0) {
198
  sessions[index] = { ...sessions[index], ...payload };
199
  } else {
@@ -204,6 +209,97 @@ function persistActiveSession() {
204
  saveSessions(sessions);
205
  }
206
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  function openHistoryDialog() {
208
  renderHistoryList();
209
  els.historyDialog.hidden = false;
@@ -226,13 +322,16 @@ function renderHistoryList() {
226
  }
227
 
228
  for (const session of sessions) {
229
- const button = document.createElement("button");
230
- button.type = "button";
231
- button.className = "history-item";
232
  if (session.id === state.sessionId) {
233
- button.classList.add("is-active");
234
  }
235
 
 
 
 
 
236
  const title = document.createElement("span");
237
  title.className = "history-item-title";
238
  title.textContent = session.title || "Untitled chat";
@@ -241,10 +340,41 @@ function renderHistoryList() {
241
  meta.className = "history-item-meta";
242
  meta.textContent = formatSessionDate(session.updatedAt || session.createdAt);
243
 
244
- button.appendChild(title);
245
- button.appendChild(meta);
246
- button.addEventListener("click", () => loadSession(session.id));
247
- els.historyList.appendChild(button);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  }
249
  }
250
 
 
185
 
186
  const sessions = loadSessions();
187
  const now = Date.now();
188
+ const index = sessions.findIndex((session) => session.id === state.sessionId);
189
+ const existing = index >= 0 ? sessions[index] : null;
190
+ const title = existing?.titleManuallySet
191
+ ? existing.title
192
+ : sessionTitle(state.history);
193
+
194
  const payload = {
195
  id: state.sessionId,
196
+ title,
197
  updatedAt: now,
198
  history: state.history,
199
  globeState: state.globeState,
200
  };
201
 
 
202
  if (index >= 0) {
203
  sessions[index] = { ...sessions[index], ...payload };
204
  } else {
 
209
  saveSessions(sessions);
210
  }
211
 
212
+ function updateSessionTitle(sessionId, title) {
213
+ const trimmed = String(title || "").trim();
214
+ if (!trimmed) {
215
+ return false;
216
+ }
217
+
218
+ const sessions = loadSessions();
219
+ const index = sessions.findIndex((session) => session.id === sessionId);
220
+ if (index < 0) {
221
+ return false;
222
+ }
223
+
224
+ sessions[index] = {
225
+ ...sessions[index],
226
+ title: trimmed,
227
+ titleManuallySet: true,
228
+ updatedAt: Date.now(),
229
+ };
230
+ sessions.sort((left, right) => right.updatedAt - left.updatedAt);
231
+ saveSessions(sessions);
232
+
233
+ if (!els.historyDialog.hidden) {
234
+ renderHistoryList();
235
+ }
236
+ return true;
237
+ }
238
+
239
+ function deleteSession(sessionId) {
240
+ const sessions = loadSessions().filter((session) => session.id !== sessionId);
241
+ saveSessions(sessions);
242
+
243
+ if (sessionId === state.sessionId) {
244
+ state.sessionId = crypto.randomUUID();
245
+ state.history = [];
246
+ state.globeState = emptyGlobeState();
247
+ resetForm();
248
+ els.chatInput.value = "";
249
+ renderMessages();
250
+ applyGlobeState(state.globeState);
251
+ showFormView();
252
+ }
253
+
254
+ renderHistoryList();
255
+ }
256
+
257
+ function startSessionRename(session, titleElement) {
258
+ const previousTitle = session.title || "Untitled chat";
259
+ const input = document.createElement("input");
260
+ input.type = "text";
261
+ input.className = "history-item-rename-input";
262
+ input.value = previousTitle;
263
+ input.setAttribute("aria-label", "Chat title");
264
+
265
+ titleElement.replaceWith(input);
266
+ input.focus();
267
+ input.select();
268
+
269
+ let finished = false;
270
+
271
+ function finish(save) {
272
+ if (finished) {
273
+ return;
274
+ }
275
+ finished = true;
276
+
277
+ if (save) {
278
+ const newTitle = input.value.trim();
279
+ if (newTitle && newTitle !== previousTitle) {
280
+ if (updateSessionTitle(session.id, newTitle)) {
281
+ return;
282
+ }
283
+ }
284
+ }
285
+
286
+ renderHistoryList();
287
+ }
288
+
289
+ input.addEventListener("keydown", (event) => {
290
+ event.stopPropagation();
291
+ if (event.key === "Enter") {
292
+ event.preventDefault();
293
+ finish(true);
294
+ } else if (event.key === "Escape") {
295
+ event.preventDefault();
296
+ finish(false);
297
+ }
298
+ });
299
+
300
+ input.addEventListener("blur", () => finish(true));
301
+ }
302
+
303
  function openHistoryDialog() {
304
  renderHistoryList();
305
  els.historyDialog.hidden = false;
 
322
  }
323
 
324
  for (const session of sessions) {
325
+ const row = document.createElement("div");
326
+ row.className = "history-item-row";
 
327
  if (session.id === state.sessionId) {
328
+ row.classList.add("is-active");
329
  }
330
 
331
+ const mainButton = document.createElement("button");
332
+ mainButton.type = "button";
333
+ mainButton.className = "history-item-main";
334
+
335
  const title = document.createElement("span");
336
  title.className = "history-item-title";
337
  title.textContent = session.title || "Untitled chat";
 
340
  meta.className = "history-item-meta";
341
  meta.textContent = formatSessionDate(session.updatedAt || session.createdAt);
342
 
343
+ mainButton.appendChild(title);
344
+ mainButton.appendChild(meta);
345
+ mainButton.addEventListener("click", () => loadSession(session.id));
346
+
347
+ const actions = document.createElement("div");
348
+ actions.className = "history-item-actions";
349
+
350
+ const renameButton = document.createElement("button");
351
+ renameButton.type = "button";
352
+ renameButton.className = "history-item-action";
353
+ renameButton.textContent = "Rename";
354
+ renameButton.setAttribute("aria-label", "Rename chat");
355
+ renameButton.addEventListener("click", (event) => {
356
+ event.stopPropagation();
357
+ startSessionRename(session, title);
358
+ });
359
+
360
+ const deleteButton = document.createElement("button");
361
+ deleteButton.type = "button";
362
+ deleteButton.className = "history-item-action history-item-action-delete";
363
+ deleteButton.textContent = "Delete";
364
+ deleteButton.setAttribute("aria-label", "Delete chat");
365
+ deleteButton.addEventListener("click", (event) => {
366
+ event.stopPropagation();
367
+ if (confirm("Delete this chat? This cannot be undone.")) {
368
+ deleteSession(session.id);
369
+ }
370
+ });
371
+
372
+ actions.appendChild(renameButton);
373
+ actions.appendChild(deleteButton);
374
+
375
+ row.appendChild(mainButton);
376
+ row.appendChild(actions);
377
+ els.historyList.appendChild(row);
378
  }
379
  }
380
 
assets/server.css CHANGED
@@ -697,24 +697,69 @@ button:disabled {
697
  font-size: 0.9rem;
698
  }
699
 
700
- .history-item {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
701
  display: flex;
702
  flex-direction: column;
703
  align-items: flex-start;
704
  gap: 4px;
705
- width: 100%;
 
706
  text-align: left;
707
  padding: 10px 12px;
 
 
 
708
  }
709
 
710
- .history-item.is-active {
711
- border-color: #f59e0b;
712
- background: rgba(245, 158, 11, 0.08);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
713
  }
714
 
715
  .history-item-title {
716
  font-weight: 600;
717
  color: #f9fafb;
 
 
 
 
718
  }
719
 
720
  .history-item-meta {
@@ -722,6 +767,18 @@ button:disabled {
722
  color: #9ca3af;
723
  }
724
 
 
 
 
 
 
 
 
 
 
 
 
 
725
  @media (max-width: 900px) {
726
  .app-shell {
727
  height: auto;
 
697
  font-size: 0.9rem;
698
  }
699
 
700
+ .history-item-row {
701
+ display: flex;
702
+ align-items: stretch;
703
+ gap: 8px;
704
+ border-radius: 8px;
705
+ border: 1px solid rgba(148, 163, 184, 0.25);
706
+ background: rgba(17, 24, 39, 0.9);
707
+ }
708
+
709
+ .history-item-row.is-active {
710
+ border-color: #f59e0b;
711
+ background: rgba(245, 158, 11, 0.08);
712
+ }
713
+
714
+ .history-item-main {
715
  display: flex;
716
  flex-direction: column;
717
  align-items: flex-start;
718
  gap: 4px;
719
+ flex: 1;
720
+ min-width: 0;
721
  text-align: left;
722
  padding: 10px 12px;
723
+ border: none;
724
+ background: transparent;
725
+ color: inherit;
726
  }
727
 
728
+ .history-item-main:hover {
729
+ background: rgba(148, 163, 184, 0.08);
730
+ }
731
+
732
+ .history-item-actions {
733
+ display: flex;
734
+ flex-direction: column;
735
+ justify-content: center;
736
+ gap: 4px;
737
+ flex-shrink: 0;
738
+ padding: 6px 8px 6px 0;
739
+ }
740
+
741
+ .history-item-action {
742
+ font-size: 0.75rem;
743
+ padding: 4px 8px;
744
+ white-space: nowrap;
745
+ }
746
+
747
+ .history-item-action-delete {
748
+ color: #fca5a5;
749
+ border-color: rgba(248, 113, 113, 0.35);
750
+ }
751
+
752
+ .history-item-action-delete:hover {
753
+ background: rgba(248, 113, 113, 0.12);
754
  }
755
 
756
  .history-item-title {
757
  font-weight: 600;
758
  color: #f9fafb;
759
+ width: 100%;
760
+ overflow: hidden;
761
+ text-overflow: ellipsis;
762
+ white-space: nowrap;
763
  }
764
 
765
  .history-item-meta {
 
767
  color: #9ca3af;
768
  }
769
 
770
+ .history-item-rename-input {
771
+ width: 100%;
772
+ font-weight: 600;
773
+ font-size: inherit;
774
+ font-family: inherit;
775
+ color: #f9fafb;
776
+ background: rgba(2, 6, 23, 0.6);
777
+ border: 1px solid rgba(245, 158, 11, 0.5);
778
+ border-radius: 4px;
779
+ padding: 2px 6px;
780
+ }
781
+
782
  @media (max-width: 900px) {
783
  .app-shell {
784
  height: auto;