From 8a6b75b6bbebcd56ae99bbe3a798f7444d0da9f8 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Sat, 15 Mar 2025 01:17:13 -0700 Subject: [PATCH] style updates progress --- .../contrib/void/browser/chatThreadService.ts | 223 +++++++++++++++++- .../contrib/void/browser/media/void.css | 2 +- .../src/markdown/ApplyBlockHoverButtons.tsx | 95 ++++++-- .../browser/react/src/markdown/BlockCode.tsx | 48 +--- .../react/src/markdown/ChatMarkdownRender.tsx | 63 ++--- .../react/src/sidebar-tsx/SidebarChat.tsx | 207 ++++++++-------- .../contrib/void/browser/toolsService.ts | 4 +- .../contrib/void/common/prompt/prompts.ts | 24 +- 8 files changed, 458 insertions(+), 208 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index 6cbff796..5cb20f8e 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -237,7 +237,228 @@ class ChatThreadService extends Disposable implements IChatThreadService { if (!threadsStr) { return null } - return this._convertThreadDataFromStorage(threadsStr); + const threads = this._convertThreadDataFromStorage(threadsStr); + + // threads['abc'] = { + // id: 'abc', + // createdAt: new Date().toISOString(), + // lastModified: new Date().toISOString(), + // messages: [ + // { + // role: 'tool', + // name: 'pathname_search', + // id: 'tool-1', + // paramsStr: '{"query": "hello", "pageNumber": 0}', + // content: '/users/andrew/void/Desktop/etc/abc.txt', + // result: { type: 'success', params: { queryStr: 'hello', pageNumber: 0 }, value: { uris: [URI.file('/Users/username/Downloads/helloooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.txt'), URI.file('/Users/username/Downloads/hello1.txt'), URI.file('/Users/username/Downloads/hello2.txt'), URI.file('/Users/username/Downloads/hello3.txt'), URI.file('/Users/username/hello.txt')], hasNextPage: true } }, + // } satisfies ToolMessage<'pathname_search'>, + // { + // role: 'tool', + // name: 'pathname_search', + // id: 'tool-1', + // paramsStr: '{"query": "hello", "pageNumber": 0}', + // content: '/users/andrew/void/Desktop/etc/abc.txt', + // result: { type: 'success', params: { queryStr: 'hello', pageNumber: 0 }, value: { uris: [], hasNextPage: false } }, + // } satisfies ToolMessage<'pathname_search'>, + + // // { + // // role: 'tool_request', + // // name: 'pathname_search', + // // params: { queryStr: 'hello', pageNumber: 0 }, + // // paramsStr: '{"query": "hello", "pageNumber": 0}', + // // voidToolId: 'request-1', + // // } satisfies ToolRequestApproval<'pathname_search'>, + + // { + // role: 'tool', + // name: 'list_dir', + // id: 'tool-2', + // paramsStr: '{"uri": "/Users/username/Documents"}', + // content: 'Directory listing of /Users/username/Documents', + // result: { + // type: 'success', + // params: { rootURI: URI.file('/Users/username/Documents'), pageNumber: 1, }, + // value: { + // children: [ + // { uri: URI.file('/Users/username/Documents/file1.txt'), name: 'file1.txt', isDirectory: false, isSymbolicLink: false }, + // { uri: URI.file('/Users/username/Documents/folder1'), name: 'folder1', isDirectory: true, isSymbolicLink: false } + // ], + // hasNextPage: true, + // hasPrevPage: true, + // itemsRemaining: 5, + // } + // }, + // } satisfies ToolMessage<'list_dir'>, + + // // { + // // role: 'tool_request', + // // name: 'list_dir', + // // params: { rootURI: URI.file('/Users/username/Documents'), pageNumber: 0 }, + // // paramsStr: '{"uri": "/Users/username/Documents"}', + // // voidToolId: 'request-2', + // // } satisfies ToolRequestApproval<'list_dir'>, + + // { + // role: 'tool', + // name: 'read_file', + // id: 'tool-3', + // paramsStr: '{"uri": "/Users/username/Documents/file1.txt"}', + // content: 'Content of file1.txt\nThis is a sample file.\nHello world!', + // result: { + // type: 'success', + // params: { uri: URI.file('/Users/username/Documents/file1.txt'), pageNumber: 0 }, + // value: { fileContents: 'Content of file1.txt\nThis is a sample file.\nHello world!', hasNextPage: false } + // }, + // } satisfies ToolMessage<'read_file'>, + + // // { + // // role: 'tool_request', + // // name: 'read_file', + // // params: { uri: URI.file('/Users/username/Documents/file1.txt'), pageNumber: 0 }, + // // paramsStr: '{"uri": "/Users/username/Documents/file1.txt"}', + // // voidToolId: 'request-3', + // // } satisfies ToolRequestApproval<'read_file'>, + + // { + // role: 'tool', + // name: 'search', + // id: 'tool-4', + // paramsStr: '{"query": "function main"}', + // content: 'Found matches in 3 files', + // result: { + // type: 'success', + // params: { queryStr: 'function main', pageNumber: 0 }, + // value: { + // uris: [ + // URI.file('/Users/username/Project/main.js'), + // URI.file('/Users/username/Project/src/app.js'), + // URI.file('/Users/username/Project/test/test.js') + // ], + // hasNextPage: false + // } + // }, + // } satisfies ToolMessage<'search'>, + + // // { + // // role: 'tool_request', + // // name: 'search', + // // params: { queryStr: 'function main', pageNumber: 0 }, + // // paramsStr: '{"query": "function main"}', + // // voidToolId: 'request-4', + // // } satisfies ToolRequestApproval<'search'>, + + // // --- + + // { + // role: 'tool', + // name: 'edit', + // id: 'tool-5', + // paramsStr: '{"uri": "/Users/username/Project/main.js", "changeDescription": "Add console.log statement"}', + // content: 'Successfully edited the file at /Users/username/Project/main.js', + // result: { + // type: 'success', + // params: { uri: URI.file('/Users/username/Project/main.js'), changeDescription: 'Add console.log statement' }, + // value: {} + // }, + // } satisfies ToolMessage<'edit'>, + // { + // role: 'tool_request', + // name: 'edit', + // params: { uri: URI.file('/Users/username/Project/main.js'), changeDescription: 'Add console.log statement' }, + // paramsStr: '{"uri": "/Users/username/Project/main.js", "changeDescription": "Add console.log statement"}', + // voidToolId: 'request-5', + // } satisfies ToolRequestApproval<'edit'>, + + // { + // role: 'tool', + // name: 'create_uri', + // id: 'tool-6', + // paramsStr: '{"uri": "/Users/username/Project/new-file.js"}', + // content: 'Successfully created file at /Users/username/Project/new-file.js', + // result: { + // type: 'success', + // params: { uri: URI.file('/Users/username/Project/new-file.js'), isFolder: false }, + // value: {} + // }, + // } satisfies ToolMessage<'create_uri'>, + // { + // role: 'tool_request', + // name: 'create_uri', + // params: { uri: URI.file('/Users/username/Project/new-file.js'), isFolder: false }, + // paramsStr: '{"uri": "/Users/username/Project/new-file.js"}', + // voidToolId: 'request-6', + // } satisfies ToolRequestApproval<'create_uri'>, + + // { + // role: 'tool', + // name: 'delete_uri', + // id: 'tool-7', + // paramsStr: '{"uri": "/Users/username/Project/old-file.js", "params": ""}', + // content: 'Successfully deleted file at /Users/username/Project/old-file.js', + // result: { + // type: 'success', + // params: { uri: URI.file('/Users/username/Project/old-file.js'), isRecursive: false, isFolder: false }, + // value: {} + // }, + // } satisfies ToolMessage<'delete_uri'>, + // { + // role: 'tool_request', + // name: 'delete_uri', + // params: { uri: URI.file('/Users/username/Project/old-file.js'), isRecursive: false, isFolder: false }, + // paramsStr: '{"uri": "/Users/username/Project/old-file.js", "params": ""}', + // voidToolId: 'request-7', + // } satisfies ToolRequestApproval<'delete_uri'>, + + // { + // role: 'tool', + // name: 'terminal_command', + // id: 'tool-8', + // paramsStr: '{"command": "npm install", "waitForCompletion": "true"}', + // content: 'Command executed: npm install\nAdded 123 packages in 3.5s', + // result: { + // type: 'success', + // params: { command: 'npm install', proposedTerminalId: '1', waitForCompletion: true }, + // value: { + // terminalId: '1', + // didCreateTerminal: false, + // result: 'Added 123 packages in 3.5s', + // resolveReason: { type: 'done', exitCode: 0 } + // } + // }, + // } satisfies ToolMessage<'terminal_command'>, + // { + // role: 'tool_request', + // name: 'terminal_command', + // params: { command: 'npm install', proposedTerminalId: '1', waitForCompletion: true }, + // paramsStr: '{"command": "npm install", "waitForCompletion": "true"}', + // voidToolId: 'request-8', + // } satisfies ToolRequestApproval<'terminal_command'>, + + + + // // Examples of error and rejected states + // { + // role: 'tool', + // name: 'pathname_search', + // id: 'tool-error', + // paramsStr: '{"query": "invalid**query"}', + // content: 'Error: Invalid search pattern', + // result: { type: 'error', params: { queryStr: 'invalid**query', pageNumber: 0 }, value: 'Error: Invalid search pattern' }, + // } satisfies ToolMessage<'pathname_search'>, + + // { + // role: 'tool', + // name: 'pathname_search', + // id: 'tool-rejected', + // paramsStr: '{"query": "sensitive-data"}', + // content: 'Tool call was rejected by the user.', + // result: { type: 'rejected', params: { queryStr: 'sensitive-data', pageNumber: 0 } }, + // } satisfies ToolMessage<'pathname_search'>, + // ], + // state: defaultThreadState, + // } + + return threads } private _storeAllThreads(threads: ChatThreads) { diff --git a/src/vs/workbench/contrib/void/browser/media/void.css b/src/vs/workbench/contrib/void/browser/media/void.css index e5e9793e..3a420737 100644 --- a/src/vs/workbench/contrib/void/browser/media/void.css +++ b/src/vs/workbench/contrib/void/browser/media/void.css @@ -83,7 +83,7 @@ .void-scrollable-element::-webkit-scrollbar, .void-scrollable-element *::-webkit-scrollbar { width: 14px !important; - height: 14px !important; + height: 4px !important; } .void-scrollable-element::-webkit-scrollbar-track, 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 eb00ff52..8d75d801 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 @@ -5,6 +5,8 @@ import { isFeatureNameDisabled } from '../../../../common/voidSettingsTypes.js' import { URI } from '../../../../../../../base/common/uri.js' import { LucideIcon, RotateCw } from 'lucide-react' import { Check, X, Square, Copy, Play, } from 'lucide-react' +import { ToolContentsWrapper } from '../sidebar-tsx/SidebarChat.js' +import { ChatMarkdownRender } from './ChatMarkdownRender.js' enum CopyButtonText { Idle = 'Copy', @@ -27,7 +29,8 @@ export const IconShell1 = ({ onClick, title, Icon, disabled, className }: IconBu disabled={disabled} onClick={onClick} className={` - size-[24px] + size-[22px] + p-[4px] flex items-center justify-center text-sm bg-void-bg-3 text-void-fg-1 hover:brightness-110 @@ -36,28 +39,28 @@ export const IconShell1 = ({ onClick, title, Icon, disabled, className }: IconBu ${className} `} > - + ) -export const IconShell2 = ({ onClick, title, Icon, disabled, className }: IconButtonProps) => ( - -) +// export const IconShell2 = ({ onClick, title, Icon, disabled, className }: IconButtonProps) => ( +// +// ) const COPY_FEEDBACK_TIMEOUT = 1000 // amount of time to say 'Copied!' @@ -230,17 +233,16 @@ export const useApplyButtonHTML = ({ codeStr, applyBoxId }: { codeStr: string, a } - const statusIndicatorHTML =
+ const statusIndicatorHTML =
-
+ />
return { @@ -248,5 +250,52 @@ export const useApplyButtonHTML = ({ codeStr, applyBoxId }: { codeStr: string, a buttonsHTML } +} + + + + + + +export const BlockCodeApplyWrapper = ({ + children, + initValue, + applyBoxId, + language, + canApply, + +}: { + initValue: string; + children: React.ReactNode; + applyBoxId: string; + canApply: boolean; + language: string; +}) => { + + + const { statusIndicatorHTML, buttonsHTML } = useApplyButtonHTML({ codeStr: initValue, applyBoxId }) + + return
+ + {/* header */} +
+
+ {statusIndicatorHTML} + + {language || 'text'} + +
+
+ {buttonsHTML} +
+
+ + {/* contents */} + + {children} + +
} 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 c1cd1de2..5a403ad5 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 @@ -4,54 +4,10 @@ *--------------------------------------------------------------------------------------*/ import { VoidCodeEditor, VoidCodeEditorProps } from '../util/inputs.js'; -import { useApplyButtonHTML } from './ApplyBlockHoverButtons.js'; - -export const BlockCodeWithApply = ({ initValue, language, applyBoxId }: { initValue: string, language?: string, applyBoxId: string }) => { - - const { statusIndicatorHTML, buttonsHTML } = useApplyButtonHTML({ codeStr: initValue, applyBoxId }) - - return ( -
-
-
-
{language || 'text'}
- {statusIndicatorHTML} -
-
- {buttonsHTML} -
-
- - - -
- ) -} +import { BlockCodeApplyWrapper, useApplyButtonHTML } from './ApplyBlockHoverButtons.js'; export const BlockCode = ({ ...codeEditorProps }: VoidCodeEditorProps) => { - const isSingleLine = !codeEditorProps.initValue.includes('\n') - - return ( - <> - - - {/*
- {buttonsOnHover === null ? null : ( -
-
- {buttonsOnHover} -
-
- )} - - -
*/} - - - ) + return } 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 521b552d..9823f62a 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 @@ -5,12 +5,10 @@ import React, { JSX, useState } from 'react' import { marked, MarkedToken, Token } from 'marked' -import { BlockCode, BlockCodeWithApply } from './BlockCode.js' +import { BlockCode } from './BlockCode.js' import { convertToVscodeLang, getFirstLine, getLanguage } from '../../../../common/helpers/getLanguage.js' -import { useApplyButtonHTML } from './ApplyBlockHoverButtons.js' -import { useAccessor, useChatThreadsState } from '../util/services.js' -import { Range } from '../../../../../../services/search/common/searchExtTypes.js' -import { IRange } from '../../../../../../../base/common/range.js' +import { BlockCodeApplyWrapper, useApplyButtonHTML } from './ApplyBlockHoverButtons.js' +import { useAccessor } from '../util/services.js' import { ScrollType } from '../../../../../../../editor/common/editorCommon.js' import { URI } from '../../../../../../../base/common/uri.js' @@ -102,7 +100,7 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string export type RenderTokenOptions = { isApplyEnabled?: boolean, isLinkDetectionEnabled?: boolean } -const RenderToken = ({ token, inPTag, chatMessageLocation, tokenIdx, ...options }: { token: Token | string, inPTag?: boolean, chatMessageLocation?: ChatMessageLocation, tokenIdx: string, } & RenderTokenOptions): JSX.Element => { +const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ...options }: { token: Token | string, inPTag?: boolean, codeURI?: URI, chatMessageLocation?: ChatMessageLocation, tokenIdx: string, } & RenderTokenOptions): JSX.Element => { const accessor = useAccessor() const languageService = accessor.get('ILanguageService') @@ -120,36 +118,45 @@ const RenderToken = ({ token, inPTag, chatMessageLocation, tokenIdx, ...options if (t.type === "code") { const [firstLine, remainingContents] = getFirstLine(t.text) const firstLineIsURI = URI.isUri(firstLine) - - let language: string | undefined = undefined - if (t.lang !== undefined) { - // convert markdown language to language that vscode recognizes (eg markdown doesn't know bash but it does know shell) - language = convertToVscodeLang(languageService, t.lang) - } - - else if (!language) { // if still no lang - if (firstLineIsURI) { // get lang from the uri - const uri = URI.file(firstLine) - language = getLanguage(languageService, { uri, fileContents: remainingContents ?? undefined }) - } - else { // get lang from the contents - language = getLanguage(languageService, { uri: null, fileContents: remainingContents ?? undefined }) - } - } const contents = firstLineIsURI ? (remainingContents || '') : t.text // exclude first-line URI from contents - // TODO!!! user should only be able to apply this when the code has been closed (t.raw ends with "```") + // figure out langauge + let language: string | undefined = undefined + let uri: URI | undefined = undefined + if (t.lang) { // a language was provided. empty string is common so check truthy, not just undefined + uri = codeURI + language = convertToVscodeLang(languageService, t.lang) // convert markdown language to language that vscode recognizes (eg markdown doesn't know bash but it does know shell) + } + else { // no language provided - fallback + if (firstLineIsURI) { // get lang from the uri in the markdown + uri = codeURI ?? URI.file(firstLine) + language = getLanguage(languageService, { uri, fileContents: remainingContents ?? undefined }) + } + else { // get lang from the given URI and contents + uri = codeURI + language = getLanguage(languageService, { uri: codeURI ?? null, fileContents: remainingContents ?? undefined }) + } + } + if (options.isApplyEnabled && chatMessageLocation) { + const isCodeblockClosed = t.raw.trimEnd().endsWith('```') // user should only be able to Apply when the code has been closed (t.raw ends with "```") + const applyBoxId = getApplyBoxId({ threadId: chatMessageLocation.threadId, messageIdx: chatMessageLocation.messageIdx, tokenIdx: tokenIdx, }) - return + > + + } return - + } @@ -361,7 +368,7 @@ const RenderToken = ({ token, inPTag, chatMessageLocation, tokenIdx, ...options } -export const ChatMarkdownRender = ({ string, inPTag = false, chatMessageLocation, ...options }: { string: string, inPTag?: boolean, chatMessageLocation: ChatMessageLocation | undefined } & RenderTokenOptions) => { +export const ChatMarkdownRender = ({ string, inPTag = false, chatMessageLocation, ...options }: { string: string, inPTag?: boolean, codeURI?: URI, chatMessageLocation: ChatMessageLocation | undefined } & RenderTokenOptions) => { const tokens = marked.lexer(string); // https://marked.js.org/using_pro#renderer return ( <> 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 2427634c..f8a2cad6 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 @@ -696,8 +696,7 @@ const ToolHeaderComponent = ({ const isClickable = !!(isDropdown || onClick) return (
-
- +
{/* header */}
)} -
-
- {title} - {desc1} +
+
+ {title} + + {/* Fixed description with proper ellipsis */} + {desc1}
-
+
{desc2 && {desc2} } @@ -731,11 +732,10 @@ const ToolHeaderComponent = ({
{/* children */} {
- {children || '(no results)'} + {children}
}
@@ -938,6 +938,29 @@ const UserMessageComponent = ({ chatMessage, messageIdx, isLoading }: ChatBubble } + +export const ToolContentsWrapper = ({ children, className }: { children: React.ReactNode, className?: string }) => { + return
+
+ {children} +
+
+} +const ListableToolItem = ({ name, onClick, isSmall, className }: { name: string, onClick?: () => void, isSmall?: boolean, className?: string }) => { + return
+
+
{name}
+
+} + + const AssistantMessageComponent = ({ chatMessage, isLoading, messageIdx, isLast }: ChatBubbleProps & { chatMessage: ChatMessage & { role: 'assistant' } }) => { const accessor = useAccessor() @@ -963,7 +986,6 @@ const AssistantMessageComponent = ({ chatMessage, isLoading, messageIdx, isLast className=' text-void-fg-2 - prose prose-sm break-words @@ -1014,15 +1036,15 @@ const AssistantMessageComponent = ({ chatMessage, isLoading, messageIdx, isLast // should either be past or "-ing" tense, not present tense. Eg. when the LLM searches for something, the user expects it to say "I searched for X" or "I am searching for X". Not "I search X". -const toolNameToTitle: Record = { - 'read_file': 'Read file', // past tense - 'list_dir': 'Inspected folder', // past tense - 'pathname_search': 'Searched by file name', // past tense - 'search': 'Searched', // past tense - 'create_uri': 'Created file', // past tense - 'delete_uri': 'Deleted file', // past tense - 'edit': 'Edited file', // past tense - 'terminal_command': 'Ran terminal command' // past tense +const toolNameToTitle: Record = { + 'read_file': { past: 'Read file', current: 'Reading file', proposed: 'Read file' }, + 'list_dir': { past: 'Inspected folder', current: 'Inspecting folder', proposed: 'Inspect folder' }, + 'pathname_search': { past: 'Searched by file name', current: 'Searching by file name', proposed: 'Search by file name' }, + 'search': { past: 'Searched', current: 'Searching', proposed: 'Search' }, + 'create_uri': { past: 'Created file', current: 'Creating file', proposed: 'Create file' }, + 'delete_uri': { past: 'Deleted file', current: 'Deleting file', proposed: 'Delete file' }, + 'edit': { past: 'Edited file', current: 'Editing file', proposed: 'Edit file' }, + 'terminal_command': { past: 'Ran terminal command', current: 'Running terminal command', proposed: 'Run terminal command' } } const toolNameToDesc = (toolName: ToolName, _toolParams: ToolCallParams[ToolName] | undefined): string => { @@ -1128,17 +1150,17 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ({ toolMessage }) => { const accessor = useAccessor() const commandService = accessor.get('ICommandService') - const title = toolNameToTitle[toolMessage.name] + const title = toolNameToTitle[toolMessage.name].past const { uri } = toolMessage.result.params ?? {} const desc1 = uri ? getBasename(uri.fsPath) : ''; const icon = null - if (toolMessage.result.type === 'rejected') return null + if (toolMessage.result.type === 'rejected') return null // will never happen, not rejectable const isError = toolMessage.result.type === 'error' const componentParams: ToolHeaderParams = { title, desc1, isError, icon } - if (toolMessage.result.type !== 'error') { + if (toolMessage.result.type === 'success') { const { value, params } = toolMessage.result componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) } if (toolMessage.result.value.hasNextPage) componentParams.desc2 = `(AI can scroll for more)` @@ -1158,38 +1180,34 @@ const toolNameToComponent: { [T in ToolName]: { const accessor = useAccessor() const commandService = accessor.get('ICommandService') const explorerService = accessor.get('IExplorerService') - const title = toolNameToTitle[toolMessage.name] + const title = toolNameToTitle[toolMessage.name].past const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params) const icon = null - if (toolMessage.result.type === 'rejected') return null + if (toolMessage.result.type === 'rejected') return null // will never happen, not rejectable const isError = toolMessage.result.type === 'error' const componentParams: ToolHeaderParams = { title, desc1, isError, icon } - if (toolMessage.result.type !== 'error') { + if (toolMessage.result.type === 'success') { const { value, params } = toolMessage.result componentParams.numResults = value.children?.length - componentParams.children = (value.children?.length ?? 0) === 0 ? null : <> - {value.children?.map((child, i) => ( -
+ {!value.children || (value.children.length ?? 0) === 0 ? <> + + : <> + {value.children.map((child, i) => ( { commandService.executeCommand('workbench.view.explorer'); explorerService.select(child.uri, true); }} - > -
- {`${child.name}${child.isDirectory ? '/' : ''}`} -
- ))} - {value.hasNextPage && ( -
- {value.itemsRemaining} more items... -
- )} - + />))} + {value.hasNextPage && + + } + } + } else { componentParams.children = <> @@ -1205,37 +1223,31 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ({ toolMessage }) => { const accessor = useAccessor() const commandService = accessor.get('ICommandService') - const title = toolNameToTitle[toolMessage.name] + const title = toolNameToTitle[toolMessage.name].past const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params) const icon = null - if (toolMessage.result.type === 'rejected') return null + if (toolMessage.result.type === 'rejected') return null // will never happen, not rejectable const isError = toolMessage.result.type === 'error' const componentParams: ToolHeaderParams = { title, desc1, isError, icon } - if (toolMessage.result.type !== 'error') { + if (toolMessage.result.type === 'success') { const { value, params } = toolMessage.result componentParams.numResults = value.uris.length - componentParams.children = value.uris.length === 0 ? null : <> - {value.uris.map((uri, i) => ( -
{ - commandService.executeCommand('vscode.open', uri, { preview: true }) - }} - > -
- {uri.fsPath.split('/').pop()} -
- ))} - {value.hasNextPage && ( -
- More results available... -
- )} - + componentParams.children = + {value.uris.length === 0 ? <> + + : <> + {value.uris.map((uri, i) => ( { commandService.executeCommand('vscode.open', uri, { preview: true }) }} + />))} + {value.hasNextPage && + + } + } + } else { componentParams.children = <> @@ -1251,30 +1263,31 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ({ toolMessage }) => { const accessor = useAccessor() const commandService = accessor.get('ICommandService') - const title = toolNameToTitle[toolMessage.name] + const title = toolNameToTitle[toolMessage.name].past const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params) const icon = null - if (toolMessage.result.type === 'rejected') return null + if (toolMessage.result.type === 'rejected') return null // will never happen, not rejectable const isError = toolMessage.result.type === 'error' const componentParams: ToolHeaderParams = { title, desc1, isError, icon } - if (toolMessage.result.type !== 'error') { + if (toolMessage.result.type === 'success') { const { value, params } = toolMessage.result componentParams.numResults = value.uris.length - componentParams.children = value.uris.length === 0 ? null : <> - {value.uris.map((uri, i) => ( -
+ {value.uris.length === 0 ? <> + + : <> + {value.uris.map((uri, i) => ( { commandService.executeCommand('vscode.open', uri, { preview: true }) }} - > -
- {uri.fsPath.split('/').pop()} -
- ))} - {value.hasNextPage && (
More results available...
)} - + />))} + {value.hasNextPage && + + } + } + } else { componentParams.children = <> @@ -1291,7 +1304,7 @@ const toolNameToComponent: { [T in ToolName]: { requestWrapper: ({ toolRequest }) => { const accessor = useAccessor() const commandService = accessor.get('ICommandService') - const title = toolNameToTitle[toolRequest.name] + const title = toolNameToTitle[toolRequest.name].proposed const desc1 = toolNameToDesc(toolRequest.name, toolRequest.params) const icon = null @@ -1306,7 +1319,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ({ toolMessage }) => { const accessor = useAccessor() const commandService = accessor.get('ICommandService') - const title = toolNameToTitle[toolMessage.name] + const title = toolNameToTitle[toolMessage.name].past const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params) const icon = null @@ -1335,7 +1348,7 @@ const toolNameToComponent: { [T in ToolName]: { requestWrapper: ({ toolRequest, }) => { const accessor = useAccessor() const commandService = accessor.get('ICommandService') - const title = toolNameToTitle[toolRequest.name] + const title = toolNameToTitle[toolRequest.name].proposed const desc1 = toolNameToDesc(toolRequest.name, toolRequest.params) const icon = null @@ -1350,7 +1363,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ({ toolMessage }) => { const accessor = useAccessor() const commandService = accessor.get('ICommandService') - const title = toolNameToTitle[toolMessage.name] + const title = toolNameToTitle[toolMessage.name].past const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params) const icon = null @@ -1378,7 +1391,7 @@ const toolNameToComponent: { [T in ToolName]: { requestWrapper: ({ toolRequest, }) => { const accessor = useAccessor() const commandService = accessor.get('ICommandService') - const title = toolNameToTitle[toolRequest.name] + const title = toolNameToTitle[toolRequest.name].proposed const desc1 = toolNameToDesc(toolRequest.name, toolRequest.params) const icon = null @@ -1386,21 +1399,22 @@ const toolNameToComponent: { [T in ToolName]: { const componentParams: ToolHeaderParams = { title, desc1, isError, icon, } const { params } = toolRequest - componentParams.children =
-
{ commandService.executeCommand('vscode.open', params.uri, { preview: true }) }}> - {getBasename(params.uri.fsPath)} + componentParams.children = + { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }} + /> +
+
- -
+ return }, resultWrapper: ({ toolMessage }) => { const accessor = useAccessor() const commandService = accessor.get('ICommandService') - const title = toolNameToTitle[toolMessage.name] + const title = toolNameToTitle[toolMessage.name].past const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params) const icon = null @@ -1409,12 +1423,12 @@ const toolNameToComponent: { [T in ToolName]: { if (toolMessage.result.type === 'success') { const { params } = toolMessage.result - componentParams.children = + componentParams.children = componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) } } else if (toolMessage.result.type === 'rejected') { const { params } = toolMessage.result - componentParams.children = + componentParams.children = componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) } } else if (toolMessage.result.type === 'error') { @@ -1431,7 +1445,7 @@ const toolNameToComponent: { [T in ToolName]: { const accessor = useAccessor() const commandService = accessor.get('ICommandService') const terminalToolsService = accessor.get('ITerminalToolService') - const title = toolNameToTitle[toolRequest.name] + const title = toolNameToTitle[toolRequest.name].proposed const desc1 = toolNameToDesc(toolRequest.name, toolRequest.params) const icon = null @@ -1451,7 +1465,7 @@ const toolNameToComponent: { [T in ToolName]: { const accessor = useAccessor() const commandService = accessor.get('ICommandService') const terminalToolsService = accessor.get('ITerminalToolService') - const title = toolNameToTitle[toolMessage.name] + const title = toolNameToTitle[toolMessage.name].past const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params) const icon = null @@ -1529,7 +1543,8 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx, isLast }: ChatBubblePr } else if (role === 'tool_request') { const ToolRequestWrapper = toolNameToComponent[chatMessage.name].requestWrapper as React.FC<{ toolRequest: any }> // ts isnt smart enough... - if (!isLast) return null + // if (!isLast) return null + if (!ToolRequestWrapper) return null return <> diff --git a/src/vs/workbench/contrib/void/browser/toolsService.ts b/src/vs/workbench/contrib/void/browser/toolsService.ts index d740a26d..c6c8a7f8 100644 --- a/src/vs/workbench/contrib/void/browser/toolsService.ts +++ b/src/vs/workbench/contrib/void/browser/toolsService.ts @@ -75,7 +75,7 @@ const directoryResultToString = (params: ToolCallParams['list_dir'], result: Too let output = ''; const entries = result.children; - if (!result.hasPrevPage) { + if (!result.hasPrevPage) { // is first page output += `${params.rootURI}\n`; } @@ -351,7 +351,7 @@ export class ToolsService implements IToolsService { }, list_dir: (params, result) => { const dirTreeStr = directoryResultToString(params, result) - return dirTreeStr + nextPageStr(result.hasNextPage) + return dirTreeStr // + nextPageStr(result.hasNextPage) // already handles num results remaining }, pathname_search: (params, result) => { return result.uris.map(uri => uri.fsPath).join('\n') + nextPageStr(result.hasNextPage) diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index 806e8468..067fc649 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -14,8 +14,9 @@ import { CodeSelection, FileSelection, StagingSelectionItem } from '../chatThrea export const tripleTick = ['```', '```'] export const editToolDesc_toolDescription = `\ -A high level description of the change you'd like to make in the file. This description will be handed to a dumber, faster model that will quickly apply the change. \ -Typically the best description you can give here is a single code block of the form:\n${tripleTick[0]}\n// ... existing code ...\n{{change 1}}\n// ... existing code ...\n{{change2}}\n// ... existing code ...\n{{change 3}}\n...\n${tripleTick[1]}. \ +A high level description of the change you'd like to make in the file. This description will be handed to a dumber, faster model that will quickly apply the change.\ +The model does not have ANY context except the file content and this description, so make sure to include all necessary information to make the change here.\ +Typically the best description you can give is a code block of the form:\n${tripleTick[0]}\n// ... existing code ...\n{{change 1}}\n// ... existing code ...\n{{change2}}\n// ... existing code ...\n{{change 3}}\n...\n${tripleTick[1]}. \ Do NOT output the whole file here if possible, and try to write as LITTLE code as needed to describe the change.` @@ -29,22 +30,23 @@ The user's system information is as follows: - ${os} - Open workspace(s): ${workspaces.join(', ') || 'NO WORKSPACE OPEN'} ${(mode === 'agent' || mode === 'gather') && runningTerminalIds.length !== 0 ? `\ -- Running terminal IDs: ${runningTerminalIds.join(', ')} +- Existing terminal IDs: ${runningTerminalIds.join(', ')} `: '\n'} ${mode === 'agent' || mode === 'gather' /* tool use */ ? `\ You will be given tools you can call. -- Only use tools if they help you accomplish the user's goal. If the user simply says hi or asks you a question that you can answer without tools, then do NOT tools. -- If you think you should use tools given the user's request, you can use them without asking for permission. Feel free to use tools to gather context, understand the codebase, ${mode === 'agent' ? 'edit files, ' : ''}etc. -- NEVER refer to a tool by name when speaking with the user. For example, do NOT say to the user "I'm going to use \`list_dir\`". Instead, say "I'm going to list all files in ___ directory", etc. Do not refer to "pages" of results, just say you're getting more results. -- Some tools only work if the user has a workspace open. ${mode === 'gather' ? '' : ` -- NEVER modify a file outside one of the the user's workspaces without confirmation from the user.`} +- Only use tools if they help you accomplish the user's goal. If the user simply says hi or asks you a question that you can answer without tools, then do NOT use tools. +- If you think you should use tools, you do not need to ask for permission. Feel free to call tools whenever you'd like. You can use them to understand the codebase, ${mode === 'agent' ? 'run terminal commands, edit files, ' : 'gather relevant files and information, '}etc. +- NEVER refer to a tool by name when speaking with the user (NEVER say something like "I'm going to use \`tool_name\`"). Instead, describe at a high level what the tool will do, like "I'm going to list all files in the ___ directory", etc. Also do not refer to "pages" of results, just say you're getting more results. +- Some tools only work if the user has a workspace open.${mode === 'agent' ? ` +- NEVER modify a file outside the user's workspace(s) without permission from the user.` : ''} \ `: `\ You're allowed to ask for more context. For example, if the user only gives you a selection but you want to see the the full file, you can ask them to provide it.\ `} ${mode === 'agent' /* code blocks */ ? `\ -If you have a change to make, you should almost always use a tool to edit the file. Even if you don't (e.g. if the user asks you not to), you should still NEVER re-write the entire file for the user. Instead, you should write comments like "// ... existing code" to indicate how to change the existing code. \ +- Prioritize editing files and running commands over simply making suggestions. +- Prioritize taking as many steps as you need to complete your request over stopping early.\ `: `\ If you think it's appropriate to suggest an edit to a file, then you must describe your suggestion in CODE BLOCK(S) (wrapped in triple backticks). - The first line of the code block must be the FULL PATH of the file you want to change. If the path does not already exist, it will be created. @@ -53,8 +55,8 @@ If you think it's appropriate to suggest an edit to a file, then you must descri - Do NOT re-write the entire file in the code block(s). Instead, write comments like "// ... existing code" to indicate how to change the existing code.`} Misc: -- Always wrap any code you produce in triple backticks. -\ +- Do not make things up. +- Always wrap any code you produce in triple backticks, and specify a language if possible. For example, ${tripleTick[0]}typescript\n...\n${tripleTick[1]}.\ `