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.
This commit is contained in:
Shi Bai 2025-02-28 16:08:43 -08:00
parent 8438f80834
commit f287a5388f
3 changed files with 79 additions and 2 deletions

View file

@ -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<ThreadsState>, affectsCurrent: boolean) {
this.state = {

View file

@ -138,6 +138,36 @@ export const IconLoading = ({ className = '' }: { className?: string }) => {
}
export const IconTrash = ({
size,
className = '',
onClick
}: {
size: number,
className?: string,
onClick?: (event: React.MouseEvent<SVGElement, MouseEvent>) => void
}) => {
return (
<svg
className={className}
// These props are necessary to prevent the icon from being 'transparent'.
stroke="currentColor"
fill="currentColor"
strokeWidth="0"
viewBox="0 0 16 16"
width={size}
height={size}
xmlns="http://www.w3.org/2000/svg"
onClick={onClick}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 3h3v1h-1v9l-1 1H4l-1-1V4H2V3h3V2a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v1zM9 2H6v1h3V2zM4 13h7V4H4v9zm2-8H5v7h1V5zm1 0h1v7H7V5zm2 0h1v7H9V5z"
/>
</svg>
);
};
interface VoidChatAreaProps {
// Required

View file

@ -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 })}
>
<IconX
size={16}
size={X_ICON_SIZE}
className="p-[1px] stroke-[2] opacity-80 text-void-fg-3 hover:brightness-95"
/>
</button>
@ -110,6 +112,16 @@ export const SidebarThreadSelector = () => {
>
<div className='truncate'>{`${firstMsg}`}</div>
<div>{`\u00A0(${numMessages})`}</div>
<IconTrash
size={TRASH_ICON_SIZE}
className='ml-auto'
onClick={(e) => {
// 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);
}}
/>
</button>
</li>
);