From 4ebfa91a8312df3b4ec8ce8fffd5b6b6cc8db4ec Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Mon, 24 Feb 2025 00:46:03 -0800 Subject: [PATCH 1/6] fix URI staleness bug in local storage --- .../contrib/void/browser/chatThreadService.ts | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index d92fb772..3a5bc403 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -227,12 +227,32 @@ class ChatThreadService extends Disposable implements IChatThreadService { } + // !!! this is important for properly restoring URIs from storage + private _convertThreadDataFromStorage(threadsStr: string): ChatThreads { + return JSON.parse(threadsStr, (key, value) => { + if (value && typeof value === 'object' && value.$mid === 1) { //$mid is the MarshalledId. $mid === 1 means it is a URI + return URI.from(value); + } + return value; + }); + } private _readAllThreads(): ChatThreads { - const threadsStr = this._storageService.get(THREAD_STORAGE_KEY, StorageScope.APPLICATION) - const threads: ChatThreads = threadsStr ? JSON.parse(threadsStr) : {} + const threadsStr = this._storageService.get(THREAD_STORAGE_KEY, StorageScope.APPLICATION); + if (!threadsStr) { + return {}; + } + return this._convertThreadDataFromStorage(threadsStr); + } - return threads + private _storeAllThreads(threads: ChatThreads) { + const serializedThreads = JSON.stringify(threads); + this._storageService.store( + THREAD_STORAGE_KEY, + serializedThreads, + StorageScope.APPLICATION, + StorageTarget.USER + ); } @@ -277,9 +297,6 @@ class ChatThreadService extends Disposable implements IChatThreadService { } - private _storeAllThreads(threads: ChatThreads) { - this._storageService.store(THREAD_STORAGE_KEY, JSON.stringify(threads), StorageScope.APPLICATION, StorageTarget.USER) - } // this should be the only place this.state = ... appears besides constructor private _setState(state: Partial, affectsCurrent: boolean) { @@ -652,4 +669,3 @@ class ChatThreadService extends Disposable implements IChatThreadService { } registerSingleton(IChatThreadService, ChatThreadService, InstantiationType.Eager); - From df6de2d7fc603622be6837eea071aead3e82388c Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Mon, 24 Feb 2025 03:51:59 -0800 Subject: [PATCH 2/6] styles for tool use --- .../react/src/sidebar-tsx/SidebarChat.tsx | 218 +++++++++++------- .../void/browser/react/src/util/inputs.tsx | 29 ++- .../void/browser/react/src/util/services.tsx | 2 + .../src/void-settings-tsx/ModelDropdown.tsx | 3 +- .../void/browser/react/tailwind.config.js | 2 +- .../contrib/void/common/toolsService.ts | 4 +- 6 files changed, 161 insertions(+), 97 deletions(-) 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 96e22bd4..1fda7cfb 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 @@ -543,12 +543,13 @@ export const SelectedFiles = ( } -type ToolReusltToComponent = { [T in ToolName]: (props: { message: ToolMessage }) => React.ReactNode } +type ToolResultToComponent = { [T in ToolName]: (props: { message: ToolMessage }) => React.ReactNode } interface ToolResultProps { actionTitle: string; actionParam: string; actionNumResults?: number; children?: React.ReactNode; + onClick?: () => void; } const ToolResult = ({ @@ -556,26 +557,31 @@ const ToolResult = ({ actionParam, actionNumResults, children, + onClick, }: ToolResultProps) => { const [isExpanded, setIsExpanded] = useState(false); const isDropdown = !!children + const isClickable = !!isDropdown || !!onClick return (
-
+
children && setIsExpanded(!isExpanded)} + className={`flex items-center min-h-[24px] ${isClickable ? 'cursor-pointer hover:brightness-125 transition-all duration-150' : ''} ${!isDropdown ? 'mx-1' : ''}`} + onClick={() => { + if (children) { setIsExpanded(v => !v); } + if (onClick) { onClick(); } + }} > - {isDropdown && ( + {isDropdown && ( )} -
+
{actionTitle} - {`"`}{actionParam}{`"`} + {actionParam} {actionNumResults !== undefined && ( {`(`}{actionNumResults}{` result`}{actionNumResults !== 1 ? 's' : ''}{`)`} @@ -584,7 +590,8 @@ const ToolResult = ({
{children}
@@ -595,90 +602,127 @@ const ToolResult = ({ -const toolResultToComponent: ToolReusltToComponent = { - 'read_file': ({ message }) => ( - - ), - 'list_dir': ({ message }) => ( - -
- {message.result.children?.map((item, i) => ( -
- {item.name} - {item.isDirectory && '/'} -
- ))} - {message.result.hasNextPage && ( -
- {message.result.itemsRemaining} more items... -
- )} -
-
- ), - 'pathname_search': ({ message }) => ( - -
- {Array.isArray(message.result.uris) ? - message.result.uris.map((uri, i) => ( -
- { + + const accessor = useAccessor() + const commandService = accessor.get('ICommandService') + + return ( + { commandService.executeCommand('vscode.open', message.result.uri, { preview: true }) }} + /> + ) + }, + 'list_dir': ({ message }) => { + const accessor = useAccessor() + const commandService = accessor.get('ICommandService') + const explorerService = accessor.get('IExplorerService') + // message.result.hasNextPage = true + // message.result.itemsRemaining = 400 + return ( + +
+ {message.result.children?.map((child, i) => ( +
{ + commandService.executeCommand('workbench.view.explorer'); + explorerService.select(child.uri, true); + }} + > + + {`${child.name}${child.isDirectory ? '/' : ''}`} +
+ ))} + {message.result.hasNextPage && ( +
+ {message.result.itemsRemaining} more items... +
+ )} +
+
+ ) + }, + 'pathname_search': ({ message }) => { + + const accessor = useAccessor() + const commandService = accessor.get('ICommandService') + + return ( + +
+ {Array.isArray(message.result.uris) ? + message.result.uris.map((uri, i) => ( + + )) : +
{message.result.uris}
+ } + {message.result.hasNextPage && ( +
+ More results available...
- )) : -
{message.result.uris}
- } - {message.result.hasNextPage && ( -
- More results available... -
- )} -
- - ), - 'search': ({ message }) => ( - -
- {typeof message.result.uris === 'string' ? - message.result.uris : - message.result.uris.map((uri, i) => ( -
- + + ) + }, + 'search': ({ message }) => { + + const accessor = useAccessor() + const commandService = accessor.get('ICommandService') + + return ( + +
+ {Array.isArray(message.result.uris) ? + message.result.uris.map((uri, i) => ( + + )) : +
{message.result.uris}
+ } + {message.result.hasNextPage && ( +
+ More results available...
- )) - } - {message.result.hasNextPage && ( -
- More results available... -
- )} -
- - ) + )} +
+ + ) + } }; diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx index be327655..7f871160 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx @@ -310,6 +310,7 @@ export const VoidCustomDropdownBox = ({ selectedOption, onChangeOption, getOptionDropdownName, + getOptionDropdownDetail, getOptionDisplayName, getOptionsEqual, className, @@ -321,6 +322,7 @@ export const VoidCustomDropdownBox = ({ selectedOption: T | undefined; onChangeOption: (newValue: T) => void; getOptionDropdownName: (option: T) => string; + getOptionDropdownDetail?: (option: T) => string; getOptionDisplayName: (option: T) => string; getOptionsEqual: (a: T, b: T) => boolean; className?: string; @@ -420,12 +422,21 @@ export const VoidCustomDropdownBox = ({ className="opacity-0 pointer-events-none absolute -left-[999999px] -top-[999999px] flex flex-col" aria-hidden="true" > - {options.map((option) => ( -
-
- {getOptionDropdownName(option)} -
- ))} + {options.map((option) => { + const optionName = getOptionDropdownName(option); + const optionDetail = getOptionDropdownDetail?.(option) || ''; + + return ( +
+
+ + {optionName} + {optionDetail} + ______ + +
+ ) + })}
{/* Select Button */} @@ -473,6 +484,7 @@ export const VoidCustomDropdownBox = ({ {options.map((option) => { const thisOptionIsSelected = getOptionsEqual(option, selectedOption); const optionName = getOptionDropdownName(option); + const optionDetail = getOptionDropdownDetail?.(option) || ''; return (
({ )}
- {optionName} + + {optionName} + {optionDetail} +
); })} diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx index 5e164428..68de21ec 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx @@ -15,6 +15,7 @@ import { VoidQuickEditState } from '../../../quickEditStateService.js' import { RefreshModelStateOfProvider } from '../../../../../../../workbench/contrib/void/common/refreshModelService.js' import { ServicesAccessor } from '../../../../../../../editor/browser/editorExtensions.js'; +import { IExplorerService } from '../../../../../../../workbench/contrib/files/browser/files.js' import { IModelService } from '../../../../../../../editor/common/services/model.js'; import { IClipboardService } from '../../../../../../../platform/clipboard/common/clipboardService.js'; import { IContextViewService, IContextMenuService } from '../../../../../../../platform/contextview/browser/contextView.js'; @@ -226,6 +227,7 @@ const getReactAccessor = (accessor: ServicesAccessor) => { ILanguageFeaturesService: accessor.get(ILanguageFeaturesService), IKeybindingService: accessor.get(IKeybindingService), + IExplorerService: accessor.get(IExplorerService), IEnvironmentService: accessor.get(IEnvironmentService), IConfigurationService: accessor.get(IConfigurationService), IPathService: accessor.get(IPathService), diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx index 2931b671..7ff66b32 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx @@ -37,7 +37,8 @@ const ModelSelectBox = ({ options, featureName }: { options: ModelOption[], feat selectedOption={selectedOption} onChangeOption={onChangeOption} getOptionDisplayName={(option) => option.selection.modelName} - getOptionDropdownName={(option) => option.name} + getOptionDropdownName={(option) => option.selection.modelName} + getOptionDropdownDetail={(option) => option.selection.providerName } getOptionsEqual={(a, b) => optionsEqual([a], [b])} className='text-xs text-void-fg-3 px-1' matchInputWidth={false} diff --git a/src/vs/workbench/contrib/void/browser/react/tailwind.config.js b/src/vs/workbench/contrib/void/browser/react/tailwind.config.js index bc57116b..d7c0f7e0 100644 --- a/src/vs/workbench/contrib/void/browser/react/tailwind.config.js +++ b/src/vs/workbench/contrib/void/browser/react/tailwind.config.js @@ -46,7 +46,7 @@ module.exports = { "void-border-1": "var(--vscode-commandCenter-activeBorder)", "void-border-2": "var(--vscode-commandCenter-border)", "void-border-3": "var(--vscode-commandCenter-inactiveBorder)", - "void-border-3": "var(--vscode-settings-sashBorder)", + "void-border-4": "var(--vscode-editorGroup-border)", vscode: { diff --git a/src/vs/workbench/contrib/void/common/toolsService.ts b/src/vs/workbench/contrib/void/common/toolsService.ts index 09ce82a2..0b2b5a37 100644 --- a/src/vs/workbench/contrib/void/common/toolsService.ts +++ b/src/vs/workbench/contrib/void/common/toolsService.ts @@ -101,6 +101,7 @@ export type ToolCallReturnType = { } type DirectoryItem = { + uri: URI; name: string; isDirectory: boolean; isSymbolicLink: boolean; @@ -133,8 +134,9 @@ const computeDirectoryResult = async ( const children: DirectoryItem[] = listChildren.map(child => ({ name: child.name, + uri: child.resource, isDirectory: child.isDirectory, - isSymbolicLink: child.isSymbolicLink || false + isSymbolicLink: child.isSymbolicLink })); const hasNextPage = (originalChildrenLength - 1) > toChildIdx; From 1dffbfb06121a2b36251ed450ffeae47820ef814 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Mon, 24 Feb 2025 04:09:09 -0800 Subject: [PATCH 3/6] style --- src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx index 7f871160..27a0d596 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx @@ -514,7 +514,7 @@ export const VoidCustomDropdownBox = ({
{optionName} - {optionDetail} + {optionDetail}
); From 764b1a2ccfcf0ba3f7fdaac9bf70e34530206b53 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Mon, 24 Feb 2025 05:47:58 -0800 Subject: [PATCH 4/6] improve input box --- .../browser/react/src/markdown/BlockCode.tsx | 2 +- .../react/src/markdown/ChatMarkdownRender.tsx | 4 +- .../react/src/sidebar-tsx/SidebarChat.tsx | 154 +++++++----------- 3 files changed, 65 insertions(+), 95 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/BlockCode.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/BlockCode.tsx index 4c2cbea4..f7954b82 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/BlockCode.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/BlockCode.tsx @@ -13,7 +13,7 @@ export const BlockCode = ({ buttonsOnHover, ...codeEditorProps }: { buttonsOnHov return ( <> -
+
{buttonsOnHover === null ? null : (
diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx index 8168bed3..d2f24569 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx @@ -51,11 +51,13 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocation, tokenIdx }: tokenIdx: tokenIdx, }) : null - return + } /> +
} if (t.type === "heading") { 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 1fda7cfb..3d6fbcb7 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 @@ -187,12 +187,14 @@ export const VoidChatArea: React.FC = ({ return (
{ @@ -414,7 +416,7 @@ export const SelectedFiles = ( } return ( -
+
{allSelections.map((selection, i) => { @@ -424,94 +426,65 @@ export const SelectedFiles = ( const thisKey = `${isThisSelectionProspective}-${i}-${selections.length}` - const selectionHTML = (
- {/* selection summary */} -
{ + if (isThisSelectionProspective) { // add prospective selection to selections + if (type !== 'staging') return; // (never) + setSelections([...selections, selection]) + } else if (isThisSelectionAFile) { // open files + commandService.executeCommand('vscode.open', selection.fileURI, { + preview: true, + // preserveFocus: false, + }); + } else { // show text + setSelectionIsOpened(s => { + const newS = [...s] + newS[i] = !newS[i] + return newS + }); + } + }} > -
{ - if (isThisSelectionProspective) { // add prospective selection to selections - if (type !== 'staging') return; // (never) - setSelections([...selections, selection]) + { // file name and range + getBasename(selection.fileURI.fsPath) + + (isThisSelectionAFile ? '' : ` (${selection.range.startLineNumber}-${selection.range.endLineNumber})`) + } - } else if (isThisSelectionAFile) { // open files - commandService.executeCommand('vscode.open', selection.fileURI, { - preview: true, - // preserveFocus: false, - }); - } else { // show text - setSelectionIsOpened(s => { - const newS = [...s] - newS[i] = !newS[i] - return newS - }); - } - }} - > - - {/* file name */} - {getBasename(selection.fileURI.fsPath)} - {/* selection range */} - {!isThisSelectionAFile ? ` (${selection.range.startLineNumber}-${selection.range.endLineNumber})` : ''} - - - {/* X button */} - {type === 'staging' && !isThisSelectionProspective && - { - e.stopPropagation(); // don't open/close selection - if (type !== 'staging') return; - setSelections([...selections.slice(0, i), ...selections.slice(i + 1)]) - setSelectionIsOpened(o => [...o.slice(0, i), ...o.slice(i + 1)]) - }} - > - - } - - -
- - {/* clear all selections button */} - {/* {type !== 'staging' || selections.length === 0 || i !== selections.length - 1 - ? null - :
-
setIsClearHovered(true)} - onMouseLeave={() => setIsClearHovered(false)} - > - { setSelections([]) }} - /> -
-
- } */} + {type === 'staging' && !isThisSelectionProspective ? // X button + { + e.stopPropagation(); // don't open/close selection + if (type !== 'staging') return; + setSelections([...selections.slice(0, i), ...selections.slice(i + 1)]) + setSelectionIsOpened(o => [...o.slice(0, i), ...o.slice(i + 1)]) + }} + size={10} + /> + : <> + }
- {/* selection text */} - {isThisSelectionOpened && + + {/* code box */} + {isThisSelectionOpened ?
{ @@ -525,14 +498,9 @@ export const SelectedFiles = ( showScrollbars={true} />
+ : <> } -
) - - return - {/* divider between `selections` and `prospectiveSelections` */} - {/* {selections.length > 0 && i === selections.length &&
} */} - {selectionHTML} -
+
})} @@ -851,7 +819,7 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM > setIsDisabled(!text)} onFocus={() => { @@ -1101,7 +1069,7 @@ export const SidebarChat = () => { featureName="Ctrl+L" > Date: Mon, 24 Feb 2025 05:59:56 -0800 Subject: [PATCH 5/6] + --- .../void/browser/react/src/sidebar-tsx/SidebarChat.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 3d6fbcb7..8c6d54f8 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 @@ -763,7 +763,7 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM if (mode === 'display') { chatbubbleContents = <> - {chatMessage.displayContent} + {chatMessage.displayContent} } else if (mode === 'edit') { @@ -862,7 +862,7 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM className={` relative ${mode === 'edit' ? 'px-2 w-full max-w-full' - : role === 'user' ? `my-0.5 px-2 self-end w-fit max-w-full whitespace-pre-wrap` // user words should be pre + : role === 'user' ? `px-2 self-end w-fit max-w-full whitespace-pre-wrap` // user words should be pre : role === 'assistant' ? `px-2 self-start w-full max-w-full` : '' } `} @@ -875,7 +875,7 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM text-left rounded-lg max-w-full ${mode === 'edit' ? '' - : role === 'user' ? 'p-2 bg-void-bg-1 text-void-fg-1 overflow-x-auto' + : role === 'user' ? 'p-2 flex flex-col gap-1 bg-void-bg-1 text-void-fg-1 overflow-x-auto' : role === 'assistant' ? 'px-2 overflow-x-auto' : '' } `} From 1b437993714720bd31b10f441d8848f278370a06 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Tue, 25 Feb 2025 18:48:17 -0800 Subject: [PATCH 6/6] prepare merg --- src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx index 27a0d596..4cd68e6d 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx @@ -688,7 +688,7 @@ export const VoidCodeEditor = ({ initValue, language, maxHeight, showScrollbars verticalScrollbarSize: 0, horizontal: 'auto', horizontalScrollbarSize: 8, - ignoreHorizontalScrollbarInContentHeight: true, + // ignoreHorizontalScrollbarInContentHeight: true, }, },