From f287a5388f62399132ee22a54cd554aff5ee2c68 Mon Sep 17 00:00:00 2001 From: Shi Bai Date: Fri, 28 Feb 2025 16:08:43 -0800 Subject: [PATCH] Add history chat thread remove button in the chat side nav - Added a trash icon to the right of the chat thread name to remove a thread history. - Added 2 icon size constants in SidebarThreadSelector.tsx to avoid the usage of magic numbers. --- .../contrib/void/browser/chatThreadService.ts | 35 +++++++++++++++++++ .../react/src/sidebar-tsx/SidebarChat.tsx | 30 ++++++++++++++++ .../src/sidebar-tsx/SidebarThreadSelector.tsx | 16 +++++++-- 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index d92fb772..3c1f1a2f 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -156,6 +156,7 @@ export interface IChatThreadService { getCurrentThread(): ChatThreads[string]; openNewThread(): void; switchToThread(threadId: string): void; + deleteThreadById(threadId: string): void; // you can edit multiple messages // the one you're currently editing is "focused", and we add items to that one when you press cmd+L. @@ -281,6 +282,40 @@ class ChatThreadService extends Disposable implements IChatThreadService { this._storageService.store(THREAD_STORAGE_KEY, JSON.stringify(threads), StorageScope.APPLICATION, StorageTarget.USER) } + public deleteThreadById(threadId: string): void { + const { allThreads, currentThreadId } = this.state + const newThreads = { ...allThreads } + if (threadId in newThreads) { + delete newThreads[threadId] + } + + // If we're deleting the current thread, switch to another thread first + const needsThreadSwitch = threadId === currentThreadId + let newCurrentThreadId = currentThreadId + + if (needsThreadSwitch) { + // Find another thread to switch to + const remainingThreadIds = Object.keys(newThreads) + if (remainingThreadIds.length > 0) { + // Switch to the most recently modified thread + newCurrentThreadId = remainingThreadIds.sort((threadId1, threadId2) => + newThreads[threadId2].lastModified > newThreads[threadId1].lastModified ? 1 : -1 + )[0] + } else { + // If no threads left, create a new one + const newThread = newThreadObject() + newThreads[newThread.id] = newThread + newCurrentThreadId = newThread.id + } + } + + this._storeAllThreads(newThreads) + this._setState({ + allThreads: newThreads, + currentThreadId: newCurrentThreadId + }, true) + } + // this should be the only place this.state = ... appears besides constructor private _setState(state: Partial, affectsCurrent: boolean) { this.state = { diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 979ae67b..9cf7cc1a 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -138,6 +138,36 @@ export const IconLoading = ({ className = '' }: { className?: string }) => { } +export const IconTrash = ({ + size, + className = '', + onClick +}: { + size: number, + className?: string, + onClick?: (event: React.MouseEvent) => void +}) => { + return ( + + + + ); +}; interface VoidChatAreaProps { // Required diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx index 6da3d5b9..1fcb946a 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx @@ -6,8 +6,10 @@ import React from "react"; import { useAccessor, useChatThreadsState } from '../util/services.js'; import { ISidebarStateService } from '../../../sidebarStateService.js'; -import { IconX } from './SidebarChat.js'; +import { IconX, IconTrash } from './SidebarChat.js'; +const X_ICON_SIZE = 16 +const TRASH_ICON_SIZE = 14 const truncate = (s: string) => { let len = s.length @@ -45,7 +47,7 @@ export const SidebarThreadSelector = () => { onClick={() => sidebarStateService.setState({ isHistoryOpen: false })} > @@ -110,6 +112,16 @@ export const SidebarThreadSelector = () => { >
{`${firstMsg}`}
{`\u00A0(${numMessages})`}
+ { + // Prevent the click event from bubbling up to the parent button + // to prevent the deleted thread from being switched to. + e.stopPropagation(); + chatThreadsService.deleteThreadById(pastThread.id); + }} + /> );