mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
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:
parent
8438f80834
commit
f287a5388f
3 changed files with 79 additions and 2 deletions
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue