diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts
index 7a206021..a72070c7 100644
--- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts
+++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts
@@ -91,7 +91,7 @@ const defaultMessageState: UserMessageState = {
// a 'thread' means a chat message history
-type ThreadType = {
+export type ThreadType = {
id: string; // store the id here too
createdAt: string; // ISO string
lastModified: string; // ISO string
@@ -177,6 +177,7 @@ export interface IChatThreadService {
getCurrentThread(): ThreadType;
openNewThread(): void;
+ deleteThread(threadId: string): void;
switchToThread(threadId: string): void;
// exposed getters/setters
@@ -1389,6 +1390,19 @@ We only need to do it for files that were edited since `from`, ie files between
}
+ deleteThread(threadId: string): void {
+ const { allThreads: currentThreads } = this.state
+
+ // delete the thread
+ const newThreads = { ...currentThreads };
+ delete newThreads[threadId];
+
+ // store the updated threads
+ this._storeAllThreads(newThreads);
+ this._setState({ ...this.state, allThreads: newThreads }, true)
+ }
+
+
private _addMessageToThread(threadId: string, message: ChatMessage) {
const { allThreads } = this.state
const oldThread = allThreads[threadId]
diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx
index 1b1d7e44..a6858032 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx
+++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx
@@ -37,7 +37,7 @@ export const IconShell1 = ({ onClick, Icon, disabled, className, ...props }: Ico
size-[18px]
p-[2px]
flex items-center justify-center
- text-sm bg-void-bg-3 text-void-fg-3
+ text-sm text-void-fg-3
hover:brightness-110
disabled:opacity-50 disabled:cursor-not-allowed
${className}
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 45f59403..85e2aa10 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
@@ -14,7 +14,7 @@ import { IDisposable } from '../../../../../../../base/common/lifecycle.js';
import { ErrorDisplay } from './ErrorDisplay.js';
import { BlockCode, TextAreaFns, VoidCustomDropdownBox, VoidInputBox2, VoidSlider, VoidSwitch } from '../util/inputs.js';
import { ModelDropdown, } from '../void-settings-tsx/ModelDropdown.js';
-import { SidebarThreadSelector } from './SidebarThreadSelector.js';
+import { OldSidebarThreadSelector, PastThreadsList } from './SidebarThreadSelector.js';
import { VOID_CTRL_L_ACTION_ID } from '../../../actionIDs.js';
import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js';
import { ChatMode, displayInfoOfProviderName, FeatureName, isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js';
@@ -2631,61 +2631,101 @@ export const SidebarChat = () => {
}
}, [onSubmit, onAbort, isRunning])
- const inputForm =
-
- {previousMessages.length > 0 &&
-
- }
-
-
-
{ textAreaRef.current?.focus() }}
- >
- { chatThreadsService.setCurrentlyFocusedMessageIdx(undefined) }}
- ref={textAreaRef}
- fnsRef={textAreaFnsRef}
- multiline={true}
- />
-
+
+
+ const inputChatArea =
{ textAreaRef.current?.focus() }}
+ >
+ { chatThreadsService.setCurrentlyFocusedMessageIdx(undefined) }}
+ ref={textAreaRef}
+ fnsRef={textAreaFnsRef}
+ multiline={true}
+ />
+
+
+
+
+ const isLandingPage = previousMessages.length === 0
+
+
+ const threadPageInput =
+
+
+
+
+ {inputChatArea}
- return (
-
- {/* History selector */}
-
-
-
-
-
-
-
-
-
- {messagesHTML}
-
-
-
- {inputForm}
-
-
+ const landingPageInput =
+
+ const landingPageContent =
+
+ {landingPageInput}
+
+
+
+ Previous Threads
+
+
+
+
+
+
+ // const threadPageContent =
+ // {/* Thread content */}
+ //
+ //
+ //
+ // {messagesHTML}
+ //
+ //
+ //
+ // {inputForm}
+ //
+ //
+ //
+ const threadPageContent =
+
+
+ {messagesHTML}
+
+
+ {threadPageInput}
+
+
+
+
+ return (isLandingPage ?
+ landingPageContent
+ : threadPageContent
)
}
+
+
+
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 96909236..42d2fa6d 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
@@ -3,35 +3,20 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
-import React from "react";
+import { useState } from 'react';
+import { IconShell1 } from '../markdown/ApplyBlockHoverButtons.js';
import { useAccessor, useChatThreadsState } from '../util/services.js';
-import { ISidebarStateService } from '../../../sidebarStateService.js';
import { IconX } from './SidebarChat.js';
+import { Check, Trash2, X } from 'lucide-react';
+import { ThreadType } from '../../../chatThreadService.js';
-const truncate = (s: string) => {
- let len = s.length
- const TRUNC_AFTER = 16
- if (len >= TRUNC_AFTER)
- s = s.substring(0, TRUNC_AFTER) + '...'
- return s
-}
+export const OldSidebarThreadSelector = () => {
-export const SidebarThreadSelector = () => {
- const threadsState = useChatThreadsState()
-
const accessor = useAccessor()
- const chatThreadsService = accessor.get('IChatThreadService')
const sidebarStateService = accessor.get('ISidebarStateService')
- const { allThreads } = threadsState
-
- // sorted by most recent to least recent
- const sortedThreadIds = Object.keys(allThreads ?? {})
- .sort((threadId1, threadId2) => (allThreads[threadId1]?.lastModified ?? 0) > (allThreads[threadId2]?.lastModified ?? 0) ? -1 : 1)
- .filter(threadId => (allThreads![threadId]?.messages.length ?? 0) !== 0)
-
return (
@@ -52,72 +37,300 @@ export const SidebarThreadSelector = () => {
{/* a list of all the past threads */}
-
+ {/*
*/}
)
}
+
+
+
+
+
+
+const truncate = (s: string) => {
+ let len = s.length
+ const TRUNC_AFTER = 16
+ if (len >= TRUNC_AFTER)
+ s = s.substring(0, TRUNC_AFTER) + '...'
+ return s
+}
+
+
+
+const OldPastThreadsList = () => {
+
+ const accessor = useAccessor()
+ const chatThreadsService = accessor.get('IChatThreadService')
+ const sidebarStateService = accessor.get('ISidebarStateService')
+
+ const threadsState = useChatThreadsState()
+ const { allThreads } = threadsState
+
+ // sorted by most recent to least recent
+ const sortedThreadIds = Object.keys(allThreads ?? {})
+ .sort((threadId1, threadId2) => (allThreads[threadId1]?.lastModified ?? 0) > (allThreads[threadId2]?.lastModified ?? 0) ? -1 : 1)
+ .filter(threadId => (allThreads![threadId]?.messages.length ?? 0) !== 0)
+
+
+ return
+}
+
+
+const numInitialThreads = 3
+
+export const PastThreadsList = ({ className = '' }: { className?: string }) => {
+ const [showAll, setShowAll] = useState(false);
+
+ const [hoveredIdx, setHoveredIdx] = useState
(null)
+
+ const threadsState = useChatThreadsState()
+ const { allThreads } = threadsState
+
+ if (!allThreads) {
+ return {`Error accessing chat history.`}
;
+ }
+
+ // sorted by most recent to least recent
+ const sortedThreadIds = Object.keys(allThreads ?? {})
+ .sort((threadId1, threadId2) => (allThreads[threadId1]?.lastModified ?? 0) > (allThreads[threadId2]?.lastModified ?? 0) ? -1 : 1)
+ .filter(threadId => (allThreads![threadId]?.messages.length ?? 0) !== 0)
+
+ // Get only first 5 threads if not showing all
+ const hasMoreThreads = sortedThreadIds.length > numInitialThreads;
+ const displayThreads = showAll ? sortedThreadIds : sortedThreadIds.slice(0, numInitialThreads);
+
+ return (
+
+ {displayThreads.length === 0
+ ? <>> // No chats yet... Suggestion: Tell me about my codebase Suggestion: Create a new .voidrules file in the root of my repo
+ : displayThreads.map((threadId, i) => {
+ const pastThread = allThreads[threadId];
+ if (!pastThread) {
+ return
{`Error accessing chat history.`}
;
+ }
+
+ return (
+
+ );
+ })
+ }
+
+ {hasMoreThreads && !showAll && (
+
setShowAll(true)}
+ >
+ Show {sortedThreadIds.length - numInitialThreads} more...
+
+ )}
+ {hasMoreThreads && showAll && (
+
setShowAll(false)}
+ >
+ Show less
+
+ )}
+
+ );
+};
+
+
+
+
+
+// Format date to display as today, yesterday, or date
+const formatDate = (date: Date) => {
+ const now = new Date();
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
+ const yesterday = new Date(today);
+ yesterday.setDate(yesterday.getDate() - 1);
+
+ if (date >= today) {
+ return 'Today';
+ } else if (date >= yesterday) {
+ return 'Yesterday';
+ } else {
+ return `${date.toLocaleString('default', { month: 'short' })} ${date.getDate()}`;
+ }
+};
+
+// Format time to 12-hour format
+const formatTime = (date: Date) => {
+ return date.toLocaleString('en-US', {
+ hour: 'numeric',
+ minute: '2-digit',
+ hour12: true
+ });
+};
+
+
+const TrashButton = ({ threadId }: { threadId: string }) => {
+
+ const accessor = useAccessor()
+ const chatThreadsService = accessor.get('IChatThreadService')
+
+
+ const [isTrashPressed, setIsTrashPressed] = useState(false)
+
+ return (isTrashPressed ?
+
+ { setIsTrashPressed(false); }}
+ data-tooltip-id='void-tooltip'
+ data-tooltip-place='top'
+ data-tooltip-content='Cancel'
+ />
+ { chatThreadsService.deleteThread(threadId); setIsTrashPressed(false); }}
+ data-tooltip-id='void-tooltip'
+ data-tooltip-place='top'
+ data-tooltip-content='Confirm'
+ />
+
+ : { setIsTrashPressed(true); }}
+ data-tooltip-id='void-tooltip'
+ data-tooltip-place='top'
+ data-tooltip-content='Delete thread?'
+ />
+ )
+}
+
+const PastThreadElement = ({ pastThread, idx, hoveredIdx, setHoveredIdx }: { pastThread: ThreadType, idx: number, hoveredIdx: number | null, setHoveredIdx: (idx: number | null) => void }) => {
+
+
+ const accessor = useAccessor()
+ const chatThreadsService = accessor.get('IChatThreadService')
+ const sidebarStateService = accessor.get('ISidebarStateService')
+
+ let firstMsg = null;
+ const firstUserMsgIdx = pastThread.messages.findIndex((msg) => msg.role === 'user');
+
+ if (firstUserMsgIdx !== -1) {
+ const firsUsertMsgObj = pastThread.messages[firstUserMsgIdx];
+ firstMsg = firsUsertMsgObj.role === 'user' && firsUsertMsgObj.displayContent || '';
+ } else {
+ firstMsg = '""';
+ }
+
+ const numMessages = pastThread.messages.filter((msg) => msg.role === 'assistant' || msg.role === 'user').length;
+
+ const dateHTML =
+ {formatDate(new Date(pastThread.lastModified))}
+
+
+ return {
+ chatThreadsService.switchToThread(pastThread.id);
+ sidebarStateService.setState({ isHistoryOpen: false });
+ }}
+ onMouseEnter={() => setHoveredIdx(idx)}
+ onMouseLeave={() => setHoveredIdx(null)}
+ data-tooltip-id='void-tooltip'
+ data-tooltip-content={`${numMessages} messages`}
+ data-tooltip-place='top'
+ data-tooltip-delay-show={500}
+ >
+
+
+ {firstMsg}
+
+
+
+ {idx === hoveredIdx ?
+
+ : dateHTML
+ }
+
+
+
+}