From fcbad101a4306505f6688d14159a325ea16a2fb9 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Sun, 2 Mar 2025 20:31:02 -0800 Subject: [PATCH 01/41] tool use progress (partial) --- .../contrib/void/browser/editCodeService.ts | 17 +- .../contrib/void/browser/prompt/prompts.ts | 8 + .../src/markdown/ApplyBlockHoverButtons.tsx | 1 + .../react/src/sidebar-tsx/SidebarChat.tsx | 262 +++++++++++++----- .../void/browser/terminalToolService.ts | 71 +++++ .../contrib/void/common/chatThreadService.ts | 70 ++--- .../contrib/void/common/llmMessageTypes.ts | 7 +- .../contrib/void/common/toolsService.ts | 217 ++++++++++----- 8 files changed, 473 insertions(+), 180 deletions(-) create mode 100644 src/vs/workbench/contrib/void/browser/terminalToolService.ts diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index c4b127a1..9c4e79e4 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -132,6 +132,7 @@ export type StartApplyingOpts = { from: 'ClickApply'; type: 'searchReplace' | 'rewrite'; applyStr: string; + uri: 'current' | URI; } @@ -1226,6 +1227,7 @@ class EditCodeService extends Disposable implements IEditCodeService { + // throws if there's an error public startApplying(opts: StartApplyingOpts) { if (opts.type === 'rewrite') { const addedDiffArea = this._initializeWriteoverStream(opts) @@ -1448,11 +1450,18 @@ class EditCodeService extends Disposable implements IEditCodeService { private _initializeSearchAndReplaceStream(opts: StartApplyingOpts & { from: 'ClickApply' }) { - const { applyStr } = opts + const { applyStr, uri: givenURI } = opts + let uri: URI + + if (givenURI === 'current') { + const uri_ = this._getActiveEditorURI() + if (!uri_) return + uri = uri_ + } + else { + uri = givenURI + } - const uri_ = this._getActiveEditorURI() - if (!uri_) return - const uri = uri_ // generate search/replace block text const originalFileCode = this._voidFileService.readModel(uri) diff --git a/src/vs/workbench/contrib/void/browser/prompt/prompts.ts b/src/vs/workbench/contrib/void/browser/prompt/prompts.ts index a7259020..801abcb6 100644 --- a/src/vs/workbench/contrib/void/browser/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/browser/prompt/prompts.ts @@ -15,6 +15,14 @@ import { IVoidFileService } from '../../common/voidFileService.js'; // this is just for ease of readability 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 high level view of the final code you'd like to see. For example, code excerpt(s) with "// ... existing code ..." comments to help you write less. \ +However, you are allowed to describe the change using whatever text/language you like, especially if the change is better described without code. \ +Do NOT output the whole file if possible, and try to write as LITTLE as needed to describe the change.` + + + export const chat_systemMessage = (workspaces: string[]) => `\ You are a coding assistant. You are given a list of instructions to follow \`INSTRUCTIONS\`, and optionally a list of relevant files \`FILES\`, and selections inside of files \`SELECTIONS\`. 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 b31bfb7b..8ce22b2f 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 @@ -86,6 +86,7 @@ export const ApplyBlockHoverButtons = ({ codeStr, applyBoxId }: { codeStr: strin from: 'ClickApply', type: 'searchReplace', applyStr: codeStr, + uri: 'current', }) applyingURIOfApplyBoxIdRef.current[applyBoxId] = newApplyingUri ?? undefined rerender(c => c + 1) 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 a77aba9a..05cf7fc0 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 @@ -151,7 +151,7 @@ interface VoidChatAreaProps { onAbort: () => void; isStreaming: boolean; isDisabled?: boolean; - divRef?: React.RefObject; + divRef?: React.RefObject; // UI customization featureName: FeatureName; @@ -528,22 +528,36 @@ export const SelectedFiles = ( } -type ToolResultToComponent = { [T in ToolName]: (props: { message: ToolMessage }) => React.ReactNode } -interface ToolResultProps { - actionTitle: string; - actionParam: string; - actionNumResults?: number; - children?: React.ReactNode; - onClick?: () => void; +const actionTitleOfToolName: { [T in ToolName]: string } = { + 'read_file': 'Read file', + 'list_dir': 'Inspected folder', + 'pathname_search': 'Searched filename', + 'search': 'Searched', + + 'create_uri': 'Created URI', + 'delete_uri': 'Deleted URI', + 'edit': 'Edited file', + 'terminal_command': 'Ran terminal command', } + + + +type ToolResultToComponent = { [T in ToolName]: (props: { message: ToolMessage }) => React.ReactNode } + const ToolResult = ({ - actionTitle, + toolName, actionParam, actionNumResults, children, onClick, -}: ToolResultProps) => { +}: { + toolName: ToolName; + actionParam: string; + actionNumResults?: number; + children?: React.ReactNode; + onClick?: () => void; +}) => { const [isExpanded, setIsExpanded] = useState(false); const isDropdown = !!children @@ -565,7 +579,7 @@ const ToolResult = ({ /> )}
- {actionTitle} + {actionTitleOfToolName[toolName]} {actionParam} {actionNumResults !== undefined && ( @@ -587,18 +601,38 @@ const ToolResult = ({ +const ToolError = ({ toolName, errorMessage }: { toolName: T, errorMessage: string }) => { + return +} + + const toolResultToComponent: ToolResultToComponent = { 'read_file': ({ message }) => { const accessor = useAccessor() const commandService = accessor.get('ICommandService') + if (message.result.type === 'error') return + const { value } = message.result return ( { commandService.executeCommand('vscode.open', message.result.uri, { preview: true }) }} - /> + toolName='read_file' + actionParam={'View file'} + > +
+
{ commandService.executeCommand('vscode.open', value.uri, { preview: true }) }} + > + + {getBasename(value.uri.fsPath)} +
+ {value.hasNextPage && (
AI can scroll for more content...
)} +
+
) }, 'list_dir': ({ message }) => { @@ -607,16 +641,18 @@ const toolResultToComponent: ToolResultToComponent = { const explorerService = accessor.get('IExplorerService') // message.result.hasNextPage = true // message.result.itemsRemaining = 400 + if (message.result.type === 'error') return + + const { value } = message.result return (
- {message.result.children?.map((child, i) => ( -
( +
{ commandService.executeCommand('workbench.view.explorer'); @@ -627,11 +663,7 @@ const toolResultToComponent: ToolResultToComponent = { {`${child.name}${child.isDirectory ? '/' : ''}`}
))} - {message.result.hasNextPage && ( -
- {message.result.itemsRemaining} more items... -
- )} + {value.hasNextPage && (
{value.itemsRemaining} more items...
)}
) @@ -640,74 +672,162 @@ const toolResultToComponent: ToolResultToComponent = { const accessor = useAccessor() const commandService = accessor.get('ICommandService') + if (message.result.type === 'error') return + const { value } = message.result return (
- {Array.isArray(message.result.uris) ? - message.result.uris.map((uri, i) => ( -
{ - commandService.executeCommand('vscode.open', uri, { preview: true }) - }} - > - - {uri.fsPath.split('/').pop()} -
- )) : -
{message.result.uris}
- } - {message.result.hasNextPage && ( -
- More results available... + {value.uris.map((uri, i) => ( +
{ commandService.executeCommand('vscode.open', uri, { preview: true }) }} + > + + {uri.fsPath.split('/').pop()}
- )} + ))} + {value.hasNextPage && (
More results available...
)}
) }, 'search': ({ message }) => { + const accessor = useAccessor() + const commandService = accessor.get('ICommandService') + if (message.result.type === 'error') return + const { value } = message.result + return ( + +
+ {value.uris.map((uri, i) => ( +
{ commandService.executeCommand('vscode.open', uri, { preview: true }) }} + > + + {uri.fsPath.split('/').pop()} +
+ ))} + {value.hasNextPage && (
More results available...
)} +
+
+ ) + }, + + // --- + + 'create_uri': ({ message }) => { const accessor = useAccessor() const commandService = accessor.get('ICommandService') + if (message.result.type === 'error') return + + const { value } = message.result return ( { commandService.executeCommand('vscode.open', value.uri, { preview: true }) }} >
- {Array.isArray(message.result.uris) ? - message.result.uris.map((uri, i) => ( -
{ - commandService.executeCommand('vscode.open', uri, { preview: true }) - }} - > - - {uri.fsPath.split('/').pop()} -
- )) : -
{message.result.uris}
- } - {message.result.hasNextPage && ( -
- More results available... -
- )} +
{ commandService.executeCommand('vscode.open', value.uri, { preview: true }) }} + > + + {value.uri.fsPath.split('/').pop()} +
+
+
+ ) + }, + 'delete_uri': ({ message }) => { + const accessor = useAccessor() + const commandService = accessor.get('ICommandService') + + if (message.result.type === 'error') return + + const { value } = message.result + return ( + { commandService.executeCommand('vscode.open', value.uri, { preview: true }) }} + > +
+
{ commandService.executeCommand('vscode.open', value.uri, { preview: true }) }} + > + + {value.uri.fsPath.split('/').pop()} +
+
+
+ ) + }, + 'edit': ({ message }) => { + const accessor = useAccessor() + const commandService = accessor.get('ICommandService') + + if (message.result.type === 'error') return + + const { value } = message.result + return ( + { commandService.executeCommand('vscode.open', value.uri, { preview: true }) }} + > +
+
{ commandService.executeCommand('vscode.open', value.uri, { preview: true }) }} + > + + {value.uri.fsPath.split('/').pop()} +
+
+
+ ) + }, + 'terminal_command': ({ message }) => { + const accessor = useAccessor() + const commandService = accessor.get('ICommandService') + + if (message.result.type === 'error') return + + const { value } = message.result + return ( + +
+
+ + + +
) } + }; diff --git a/src/vs/workbench/contrib/void/browser/terminalToolService.ts b/src/vs/workbench/contrib/void/browser/terminalToolService.ts new file mode 100644 index 00000000..2fe64d11 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/terminalToolService.ts @@ -0,0 +1,71 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; +import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +import { ITerminalService, ITerminalInstance } from '../../../../workbench/contrib/terminal/browser/terminal.js'; +import { TerminalLocation } from '../../../../platform/terminal/common/terminal.js'; +import { generateUuid } from '../../../../base/common/uuid.js'; + +export interface ITerminalToolService { + readonly _serviceBrand: undefined; + + createNewTerminal(terminalId: string): Promise; + runCommand(command: string, terminalId?: string): Promise; + focus(terminalId: string): Promise; +} + +export const ITerminalToolService = createDecorator('TerminalToolService'); + +export class TerminalToolService extends Disposable implements ITerminalToolService { + readonly _serviceBrand: undefined; + + private terminalInstances: Record = {} + + constructor( + @ITerminalService private readonly terminalService: ITerminalService + ) { + super(); + } + + async createNewTerminal() { + const terminalId = generateUuid(); + + this.terminalService.createTerminal({}); + const terminal = await this.terminalService.createTerminal({ + location: TerminalLocation.Editor, + config: { name: `Void Agent (${terminalId})`, } + }); + + this.terminalInstances[terminalId] = terminal + return terminalId; + } + + async runCommand(command: string, terminalId?: string) { + + if (!terminalId) { + terminalId = await this.createNewTerminal(); + } + + const terminal = this.terminalInstances[terminalId]; + if (!terminal) throw new Error(`Terminal with ID ${terminalId} does not exist`); + + terminal.sendText(command, true); + return; + } + + async focus(terminalId: string) { + const terminal = this.terminalInstances[terminalId]; + if (!terminal) throw new Error(`That terminal was closed.`); + + + terminal.focus(true); + return; + } + +} + +registerSingleton(ITerminalToolService, TerminalToolService, InstantiationType.Eager); diff --git a/src/vs/workbench/contrib/void/common/chatThreadService.ts b/src/vs/workbench/contrib/void/common/chatThreadService.ts index d95e2666..dc16b52d 100644 --- a/src/vs/workbench/contrib/void/common/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/common/chatThreadService.ts @@ -13,11 +13,12 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { IRange } from '../../../../editor/common/core/range.js'; import { ILLMMessageService } from './llmMessageService.js'; import { chat_userMessageContent, chat_systemMessage, chat_userMessageContentWithAllFilesToo, chat_selectionsString } from '../browser/prompt/prompts.js'; -import { InternalToolInfo, IToolsService, ToolCallReturnType, ToolFns, ToolName, voidTools } from './toolsService.js'; -import { toLLMChatMessage } from './llmMessageTypes.js'; +import { InternalToolInfo, IToolsService, ToolCallReturnType, ToolName, voidTools } from './toolsService.js'; +import { toLLMChatMessage, ToolCallType } from './llmMessageTypes.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; import { IVoidFileService } from './voidFileService.js'; import { generateUuid } from '../../../../base/common/uuid.js'; +import { getErrorMessage } from '../../../../base/common/errors.js'; const findLastIndex = (arr: T[], condition: (t: T) => boolean): number => { @@ -59,11 +60,9 @@ export type ToolMessage = { name: T; // internal use params: string; // internal use id: string; // apis require this tool use id - content: string; // result - result: ToolCallReturnType[T]; // text message of result + content: string; // give this result to LLM + result: { type: 'success'; value: ToolCallReturnType[T], } | { type: 'error'; value: string }; // give this result to user } - - // WARNING: changing this format is a big deal!!!!!! need to migrate old format to new format on users' computers so people don't get errors. export type ChatMessage = { @@ -390,37 +389,44 @@ class ChatThreadService extends Disposable implements IChatThreadService { else { this._addMessageToThread(threadId, { role: 'assistant', content: fullText, reasoning: fullReasoning || null }) this._setStreamState(threadId, { messageSoFar: undefined, reasoningSoFar: undefined }) // clear streaming message - for (const tool of toolCalls ?? []) { - const toolName = tool.name as ToolName - // 1. - let toolResult: Awaited> - let toolResultVal: ToolCallReturnType[ToolName] - try { - toolResult = await this._toolsService.toolFns[toolName](tool.params) - toolResultVal = toolResult - } catch (error) { - this._setStreamState(threadId, { error }) - shouldSendAnotherMessage = false - break - } + // deal with the tool + const tool: ToolCallType | undefined = toolCalls?.[0] + if (!tool) { + res_() + return + } + const toolName = tool.name - // 2. - let toolResultStr: string - try { - toolResultStr = this._toolsService.toolResultToString[toolName](toolResult as any) // typescript is so bad it doesn't even couple the type of ToolResult with the type of the function being called here - } catch (error) { - this._setStreamState(threadId, { error }) - shouldSendAnotherMessage = false - break - } - - this._addMessageToThread(threadId, { role: 'tool', name: toolName, params: tool.params, id: tool.id, content: toolResultStr, result: toolResultVal, }) + // 1. + let toolResultVal: ToolCallReturnType[typeof toolName] + try { + const val = await this._toolsService.toolFns[toolName](tool.params) + toolResultVal = val + } catch (error) { + const errorMessage = getErrorMessage(error) + this._addMessageToThread(threadId, { role: 'tool', name: toolName, params: tool.params, id: tool.id, content: errorMessage, result: { type: 'error', value: errorMessage }, }) shouldSendAnotherMessage = true + res_() + return } + // 2. + let toolResultStr: string + try { + toolResultStr = this._toolsService.toolResultToString[toolName](toolResultVal as any) // typescript is so bad it doesn't even couple the type of ToolResult with the type of the function being called here + } catch (error) { + // treat as irrecoverable error + this._setStreamState(threadId, { error }) + res_() + return + } + + this._addMessageToThread(threadId, { role: 'tool', name: toolName, params: tool.params, id: tool.id, content: toolResultStr, result: { type: 'success', value: toolResultVal }, }) + shouldSendAnotherMessage = true + res_() } - res_() + }, onError: (error) => { const messageSoFar = this.streamState[threadId]?.messageSoFar ?? '' @@ -436,7 +442,7 @@ class ChatThreadService extends Disposable implements IChatThreadService { } } - agentLoop() // DO NOT AWAIT THIS, this fn should resolve when ready to clear inputs + agentLoop() // DO NOT AWAIT THIS, add fn should resolve when we've added message (this lets us interrupt the agent loop correctly instead of waiting for it to resolve) } diff --git a/src/vs/workbench/contrib/void/common/llmMessageTypes.ts b/src/vs/workbench/contrib/void/common/llmMessageTypes.ts index 4f767928..4257e616 100644 --- a/src/vs/workbench/contrib/void/common/llmMessageTypes.ts +++ b/src/vs/workbench/contrib/void/common/llmMessageTypes.ts @@ -22,6 +22,11 @@ export const errorDetails = (fullError: Error | null): string | null => { return null } +export const getErrorMessage: (error: unknown) => string = (error) => { + if (error instanceof Error) return `${error.name}: ${error.message}` + return error + '' +} + export type LLMChatMessage = { role: 'system' | 'user'; @@ -60,7 +65,7 @@ export const toLLMChatMessage = (c: ChatMessage): LLMChatMessage => { else if (c.role === 'tool') return { role: c.role, id: c.id, name: c.name, params: c.params, content: c.content || '(empty output)' } else { - throw 1 + throw new Error(`Role ${(c as any).role} not recognized.`) } } diff --git a/src/vs/workbench/contrib/void/common/toolsService.ts b/src/vs/workbench/contrib/void/common/toolsService.ts index 95dcb045..cf6052dd 100644 --- a/src/vs/workbench/contrib/void/common/toolsService.ts +++ b/src/vs/workbench/contrib/void/common/toolsService.ts @@ -6,7 +6,10 @@ import { createDecorator, IInstantiationService } from '../../../../platform/ins import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js' import { QueryBuilder } from '../../../../workbench/services/search/common/queryBuilder.js' import { ISearchService } from '../../../../workbench/services/search/common/search.js' +import { IEditCodeService } from '../browser/editCodeService.js' +import { editToolDesc_toolDescription } from '../browser/prompt/prompts.js' import { IVoidFileService } from './voidFileService.js' +import { ITerminalToolService } from '../browser/terminalToolService.js' // tool use for AI @@ -29,6 +32,8 @@ const paginationHelper = { } as const export const voidTools = { + // --- context-gathering (read/search/list) --- + read_file: { name: 'read_file', description: `Returns file contents of a given URI. ${paginationHelper.desc}`, @@ -68,44 +73,50 @@ export const voidTools = { required: ['query'], }, + // --- editing (create/delete) --- - // create_file: { - // name: 'create_file', - // description: `Creates a file at the given path. Fails gracefully if the file already exists by doing nothing.`, - // params: { - // uri: { type: 'string', description: undefined }, - // }, - // required: ['uri'], - // }, + create_uri: { + name: 'create_uri', + description: `Creates a file or folder at the given path. To create a folder, ensure the path ends with a trailing slash. Fails gracefully if the file already exists. Missing ancestors in the path will be recursively created automatically.`, + params: { + uri: { type: 'string', description: undefined }, + }, + required: ['uri'], + }, - // create_folder: { - // name: 'create_folder', - // description: `Creates a folder at the given path. Fails gracefully if the folder already exists by doing nothing.`, - // params: { - // uri: { type: 'string', description: undefined }, - // }, - // required: ['uri'], - // }, + delete_uri: { + name: 'delete_uri', + description: `Deletes the file or folder at the given path. Fails gracefully if the file or folder does not exist.`, + params: { + uri: { type: 'string', description: undefined }, + params: { type: 'string', description: 'Return -r here to delete this URI and all descendants (if applicable). Default is the empty string.' } + }, + required: ['uri', 'params'], + }, + + edit: { // APPLY TOOL + name: 'edit', + description: `Edits the contents of a file at the given URI. Fails gracefully if the file does not exist.`, + params: { + uri: { type: 'string', description: undefined }, + changeDescription: { type: 'string', description: editToolDesc_toolDescription } + }, + required: ['uri', 'changeDescription'], + }, + + terminal_command: { + name: 'terminal_command', + description: `Executes a terminal command.`, + params: { + command: { type: 'string', description: 'The terminal command to execute.' } + }, + required: ['command'], + }, - // go_to_definition: { + // go_to_definition + // go_to_usages - // }, - - // go_to_usages: - - // create_file: { - // name: 'create_file', - // description: `Creates a file at the given path. Fails gracefully if the file already exists by doing nothing.` - // params: { - // uri: { type: 'string', description: undefined }, - // } - // } - - // semantic_search: { - // description: 'Searches files semantically for the given string query.', - // // RAG - // }, } satisfies { [name: string]: InternalToolInfo } export type ToolName = keyof typeof voidTools @@ -124,9 +135,13 @@ export type ToolParamsObj = { [paramName in ToolParamNames { return o } catch (e) { - throw new Error(`Tool parameter was not a valid JSON: "${s}".`) + throw new Error(`Tool parameter was not a string of a valid JSON: "${s}".`) } } -const validateQueryStr = (queryStr: unknown) => { - if (typeof queryStr !== 'string') throw new Error('Error calling tool: provided query must be a string.') - return queryStr +const validateStr = (argName: string, value: unknown) => { + if (typeof value !== 'string') throw new Error(`Error: ${argName} must be a string.`) + return value } // TODO!!!! check to make sure in workspace const validateURI = (uriStr: unknown) => { - if (typeof uriStr !== 'string') throw new Error('Error calling tool: provided uri must be a string.') + if (typeof uriStr !== 'string') throw new Error('Error: provided uri must be a string.') const uri = URI.file(uriStr) return uri } const validatePageNum = (pageNumberUnknown: unknown) => { - const proposedPageNum = Number.parseInt(pageNumberUnknown + '') - const num = Number.isInteger(proposedPageNum) ? proposedPageNum : 1 - const pageNumber = num < 1 ? 1 : num - return pageNumber + if (!pageNumberUnknown) return 1 + const parsedInt = Number.parseInt(pageNumberUnknown + '') + if (!Number.isInteger(parsedInt)) throw new Error(`Page number was not an integer: "${pageNumberUnknown}".`) + if (parsedInt < 1) throw new Error(`Specified page number must be 1 or greater: "${pageNumberUnknown}".`) + return parsedInt } + +const validateRecursiveParamStr = (paramsUnknown: unknown) => { + if (typeof paramsUnknown !== 'string') throw new Error('Error calling tool: provided params must be a string.') + const params = paramsUnknown + const isRecursive = params.includes('r') + return isRecursive +} + export interface IToolsService { readonly _serviceBrand: undefined; toolFns: ToolFns; @@ -256,8 +280,8 @@ export class ToolsService implements IToolsService { readonly _serviceBrand: undefined; - public toolFns: ToolFns - public toolResultToString: ToolResultToString + public toolFns: ToolFns; + public toolResultToString: ToolResultToString; constructor( @@ -266,15 +290,17 @@ export class ToolsService implements IToolsService { @ISearchService searchService: ISearchService, @IInstantiationService instantiationService: IInstantiationService, @IVoidFileService voidFileService: IVoidFileService, + @IEditCodeService editCodeService: IEditCodeService, + @ITerminalToolService private readonly terminalToolService: ITerminalToolService, ) { const queryBuilder = instantiationService.createInstance(QueryBuilder); this.toolFns = { - read_file: async (s: string) => { + read_file: async (params: string) => { console.log('read_file') - const o = validateJSON(s) + const o = validateJSON(params) const { uri: uriStr, pageNumber: pageNumberUnknown } = o const uri = validateURI(uriStr) @@ -293,25 +319,21 @@ export class ToolsService implements IToolsService { return { uri, fileContents, hasNextPage } }, - list_dir: async (s: string) => { - console.log('list_dir') - const o = validateJSON(s) + list_dir: async (params: string) => { + const o = validateJSON(params) const { uri: uriStr, pageNumber: pageNumberUnknown } = o const uri = validateURI(uriStr) const pageNumber = validatePageNum(pageNumberUnknown) const dirResult = await computeDirectoryResult(fileService, uri, pageNumber) - console.log('list_dir result:', dirResult) - return dirResult }, - pathname_search: async (s: string) => { - console.log('pathname_search') - const o = validateJSON(s) + pathname_search: async (params: string) => { + const o = validateJSON(params) const { query: queryUnknown, pageNumber: pageNumberUnknown } = o - const queryStr = validateQueryStr(queryUnknown) + const queryStr = validateStr('query', queryUnknown) const pageNumber = validatePageNum(pageNumberUnknown) const query = queryBuilder.file(workspaceContextService.getWorkspace().folders.map(f => f.uri), { filePattern: queryStr, }) @@ -324,19 +346,13 @@ export class ToolsService implements IToolsService { .map(({ resource, results }) => resource) const hasNextPage = (data.results.length - 1) - toIdx >= 1 - console.log('pathname_search result:', uris) - return { queryStr, uris, hasNextPage } }, - search: async (s: string) => { - - - console.log('search') - - const o = validateJSON(s) + search: async (params: string) => { + const o = validateJSON(params) const { query: queryUnknown, pageNumber: pageNumberUnknown } = o - const queryStr = validateQueryStr(queryUnknown) + const queryStr = validateStr('query', queryUnknown) const pageNumber = validatePageNum(pageNumberUnknown) const query = queryBuilder.text({ pattern: queryStr, }, workspaceContextService.getWorkspace().folders.map(f => f.uri)) @@ -349,17 +365,62 @@ export class ToolsService implements IToolsService { .map(({ resource, results }) => resource) const hasNextPage = (data.results.length - 1) - toIdx >= 1 - - console.log('search result:', uris) - return { queryStr, uris, hasNextPage } }, + // --- + + create_uri: async (params: string) => { + const o = validateJSON(params) + const { uri: uriStr } = o + const uri = validateURI(uriStr) + await fileService.createFile(uri) + return { uri } + }, + + delete_uri: async (params: string) => { + const o = validateJSON(params) + const { uri: uriStr, params: paramsStr } = o + const uri = validateURI(uriStr) + const isRecursive = validateRecursiveParamStr(paramsStr) + await fileService.del(uri, { recursive: isRecursive }) + return { uri } + }, + + edit: async (params: string) => { + const o = validateJSON(params) + const { uri: uriStr, changeDescription: changeDescriptionUnknown } = o + const uri = validateURI(uriStr) + const changeDescription = validateStr('changeDescription', changeDescriptionUnknown) + + const applyId = editCodeService.startApplying({ uri, applyStr: changeDescription, from: 'ClickApply', type: 'rewrite' }) + + // // TODO!!! + + // await // await apply done before moving on + + return { uri, changeDescription } + }, + + terminal_command: async (s: string) => { + const o = validateJSON(s) + const { command: commandUnknown } = o + const command = validateStr('command', commandUnknown) + + // TODO!!!! + // await // Await user confirmation and then command execution before resolving + + + return { command } + }, + + } const nextPageStr = (hasNextPage: boolean) => hasNextPage ? '\n\n(more on next page...)' : '' + // given to the LLM after the call this.toolResultToString = { read_file: (result) => { return nextPageStr(result.hasNextPage) @@ -369,13 +430,25 @@ export class ToolsService implements IToolsService { return dirTreeStr + nextPageStr(result.hasNextPage) }, pathname_search: (result) => { - if (typeof result.uris === 'string') return result.uris return result.uris.map(uri => uri.fsPath).join('\n') + nextPageStr(result.hasNextPage) }, search: (result) => { - if (typeof result.uris === 'string') return result.uris return result.uris.map(uri => uri.fsPath).join('\n') + nextPageStr(result.hasNextPage) }, + // --- + create_uri: (result) => { + return `URI ${result.uri.fsPath} successfully created.` + }, + delete_uri: (result) => { + return `URI ${result.uri.fsPath} successfully deleted.` + }, + edit: (result) => { + return `Change successfully made ${result.uri.fsPath} successfully deleted.` + }, + terminal_command: (result) => { + return `Terminal command "${result.command}" successfully executed.` + }, + } From 1257e54ebe2ebd03cdf3ff04558953080cc8b903 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 3 Mar 2025 20:13:26 -0800 Subject: [PATCH 02/41] tools are getting close --- .../contrib/void/browser/editCodeService.ts | 20 +- .../react/src/sidebar-tsx/SidebarChat.tsx | 68 ++--- .../contrib/void/common/chatThreadService.ts | 82 +++++- .../contrib/void/common/llmMessageTypes.ts | 8 +- .../contrib/void/common/toolsService.ts | 256 +++++++++++------- .../void/electron-main/llmMessage/MODELS.ts | 19 +- 6 files changed, 289 insertions(+), 164 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index 9c4e79e4..f8b7dd07 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -39,7 +39,7 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { VOID_OPEN_SETTINGS_ACTION_ID } from './voidSettingsPane.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { ILLMMessageService } from '../common/llmMessageService.js'; -import { LLMChatMessage, OnError, errorDetails } from '../common/llmMessageTypes.js'; +import { LLMChatMessage, OnError, OnFinalMessage, OnText, errorDetails } from '../common/llmMessageTypes.js'; import { IMetricsService } from '../common/metricsService.js'; import { IVoidFileService } from '../common/voidFileService.js'; @@ -133,6 +133,10 @@ export type StartApplyingOpts = { type: 'searchReplace' | 'rewrite'; applyStr: string; uri: 'current' | URI; + + onText?: OnText; + onFinalMessage?: OnFinalMessage; + onError?: OnError; } @@ -1450,7 +1454,7 @@ class EditCodeService extends Disposable implements IEditCodeService { private _initializeSearchAndReplaceStream(opts: StartApplyingOpts & { from: 'ClickApply' }) { - const { applyStr, uri: givenURI } = opts + const { applyStr, uri: givenURI, onText: onText_, onFinalMessage: onFinalMessage_, onError: onError_, } = opts let uri: URI if (givenURI === 'current') { @@ -1583,7 +1587,8 @@ class EditCodeService extends Disposable implements IEditCodeService { useProviderFor: 'Apply', logging: { loggingName: `generateSearchAndReplace` }, messages, - onText: ({ fullText }) => { + onText: (params) => { + const { fullText } = params // blocks are [done done done ... {writingFinal|writingOriginal}] // ^ // currStreamingBlockNum @@ -1678,8 +1683,11 @@ class EditCodeService extends Disposable implements IEditCodeService { } // end for this._refreshStylesAndDiffsInURI(uri) + + onText_?.(params) }, - onFinalMessage: async ({ fullText }) => { + onFinalMessage: async (params) => { + const { fullText } = params console.log('final message!!', fullText) // 1. wait 500ms and fix lint errors - call lint error workflow @@ -1715,11 +1723,15 @@ class EditCodeService extends Disposable implements IEditCodeService { } onDone() + + onFinalMessage_?.(params) }, onError: (e) => { this._notifyError(e) onDone() this._undoHistory(uri) + + onError_?.(e) }, }) 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 05cf7fc0..e89ddbb0 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 @@ -25,7 +25,7 @@ import { ChevronRight, Pencil, X } from 'lucide-react'; import { FeatureName, isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js'; import { WarningBox } from '../void-settings-tsx/WarningBox.js'; -import { ToolCallReturnType, ToolName } from '../../../../common/toolsService.js'; +import { ToolResultType, ToolName } from '../../../../common/toolsService.js'; @@ -604,8 +604,14 @@ const ToolResult = ({ const ToolError = ({ toolName, errorMessage }: { toolName: T, errorMessage: string }) => { return + actionParam={'Error'} + > + + } @@ -616,7 +622,7 @@ const toolResultToComponent: ToolResultToComponent = { const commandService = accessor.get('ICommandService') if (message.result.type === 'error') return - const { value } = message.result + const { value, params } = message.result return (
{ commandService.executeCommand('vscode.open', value.uri, { preview: true }) }} + onClick={() => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }} > - {getBasename(value.uri.fsPath)} + {getBasename(params.uri.fsPath)}
{value.hasNextPage && (
AI can scroll for more content...
)}
@@ -643,11 +649,11 @@ const toolResultToComponent: ToolResultToComponent = { // message.result.itemsRemaining = 400 if (message.result.type === 'error') return - const { value } = message.result + const { value, params } = message.result return (
@@ -674,11 +680,11 @@ const toolResultToComponent: ToolResultToComponent = { const commandService = accessor.get('ICommandService') if (message.result.type === 'error') return - const { value } = message.result + const { value, params } = message.result return (
@@ -701,11 +707,11 @@ const toolResultToComponent: ToolResultToComponent = { const commandService = accessor.get('ICommandService') if (message.result.type === 'error') return - const { value } = message.result + const { value, params } = message.result return (
@@ -732,20 +738,20 @@ const toolResultToComponent: ToolResultToComponent = { if (message.result.type === 'error') return - const { value } = message.result + const { params } = message.result return ( { commandService.executeCommand('vscode.open', value.uri, { preview: true }) }} + actionParam={getBasename(params.uri.fsPath)} + onClick={() => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }} >
{ commandService.executeCommand('vscode.open', value.uri, { preview: true }) }} + onClick={() => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }} > - {value.uri.fsPath.split('/').pop()} + {params.uri.fsPath.split('/').pop()}
@@ -757,20 +763,20 @@ const toolResultToComponent: ToolResultToComponent = { if (message.result.type === 'error') return - const { value } = message.result + const { params } = message.result return ( { commandService.executeCommand('vscode.open', value.uri, { preview: true }) }} + actionParam={getBasename(params.uri.fsPath) + ' (deleted)'} + onClick={() => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }} >
{ commandService.executeCommand('vscode.open', value.uri, { preview: true }) }} + onClick={() => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }} > - {value.uri.fsPath.split('/').pop()} + {params.uri.fsPath.split('/').pop()}
@@ -782,20 +788,20 @@ const toolResultToComponent: ToolResultToComponent = { if (message.result.type === 'error') return - const { value } = message.result + const { params } = message.result return ( { commandService.executeCommand('vscode.open', value.uri, { preview: true }) }} + actionParam={getBasename(params.uri.fsPath)} + onClick={() => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }} >
{ commandService.executeCommand('vscode.open', value.uri, { preview: true }) }} + onClick={() => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }} > - {value.uri.fsPath.split('/').pop()} + {params.uri.fsPath.split('/').pop()}
@@ -807,16 +813,16 @@ const toolResultToComponent: ToolResultToComponent = { if (message.result.type === 'error') return - const { value } = message.result + const { params } = message.result return (
@@ -1026,7 +1032,7 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM chatbubbleContents = - console.log('tool result:', chatMessage.name, chatMessage.params, chatMessage.result) + console.log('tool result:', chatMessage.name, chatMessage.paramsStr, chatMessage.result) } diff --git a/src/vs/workbench/contrib/void/common/chatThreadService.ts b/src/vs/workbench/contrib/void/common/chatThreadService.ts index dc16b52d..32afe5e8 100644 --- a/src/vs/workbench/contrib/void/common/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/common/chatThreadService.ts @@ -13,7 +13,7 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { IRange } from '../../../../editor/common/core/range.js'; import { ILLMMessageService } from './llmMessageService.js'; import { chat_userMessageContent, chat_systemMessage, chat_userMessageContentWithAllFilesToo, chat_selectionsString } from '../browser/prompt/prompts.js'; -import { InternalToolInfo, IToolsService, ToolCallReturnType, ToolName, voidTools } from './toolsService.js'; +import { InternalToolInfo, IToolsService, ToolCallParams, ToolResultType, ToolName, toolNamesThatRequireApproval, voidTools } from './toolsService.js'; import { toLLMChatMessage, ToolCallType } from './llmMessageTypes.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; import { IVoidFileService } from './voidFileService.js'; @@ -58,11 +58,18 @@ export type StagingSelectionItem = CodeSelection | FileSelection export type ToolMessage = { role: 'tool'; name: T; // internal use - params: string; // internal use + paramsStr: string; // internal use id: string; // apis require this tool use id content: string; // give this result to LLM - result: { type: 'success'; value: ToolCallReturnType[T], } | { type: 'error'; value: string }; // give this result to user + result: { type: 'success'; params: ToolCallParams[T]; value: ToolResultType[T], } | { type: 'error'; value: string }; // give this result to user } +export type ToolRequestApproval = { + role: 'tool_request'; + name: T; // internal use + params: ToolCallParams[T]; // internal use + voidToolId: string; // internal id Void uses +} + // WARNING: changing this format is a big deal!!!!!! need to migrate old format to new format on users' computers so people don't get errors. export type ChatMessage = { @@ -80,6 +87,7 @@ export type ChatMessage = reasoning: string | null; // reasoning from the LLM, used for step-by-step thinking } | ToolMessage + | ToolRequestApproval type UserMessageType = ChatMessage & { role: 'user' } type UserMessageState = UserMessageType['state'] @@ -316,6 +324,18 @@ class ChatThreadService extends Disposable implements IChatThreadService { } + private resRejOfToolAwaitingApproval: { [toolId: string]: { res: () => void, rej: () => void } } = {} + approveTool(toolId: string) { + const resRej = this.resRejOfToolAwaitingApproval[toolId] + resRej?.res() + delete this.resRejOfToolAwaitingApproval[toolId] + } + rejectTool(toolId: string) { + const resRej = this.resRejOfToolAwaitingApproval[toolId] + resRej?.rej() + delete this.resRejOfToolAwaitingApproval[toolId] + } + async addUserMessageAndStreamResponse({ userMessage, chatMode, chatSelections }: { userMessage: string, chatMode: ChatMode, chatSelections?: { prevSelns?: StagingSelectionItem[], currSelns?: StagingSelectionItem[] } }) { @@ -357,7 +377,7 @@ class ChatThreadService extends Disposable implements IChatThreadService { const awaitable = new Promise((res, rej) => { res_ = res }) // replace last userMessage with userMessageFullContent (which contains all the files too) - const messages_ = this.getCurrentThread().messages.map(m => (toLLMChatMessage(m))) + const messages_ = this.getCurrentThread().messages.map(m => (toLLMChatMessage(m))).filter(m => !!m) const lastUserMsgIdx = findLastIndex(messages_, m => m.role === 'user') let messages = messages_ if (lastUserMsgIdx !== -1) { // should never be -1 @@ -398,31 +418,63 @@ class ChatThreadService extends Disposable implements IChatThreadService { } const toolName = tool.name - // 1. - let toolResultVal: ToolCallReturnType[typeof toolName] + // 1. validate tool params + let toolParams: ToolCallParams[typeof toolName] try { - const val = await this._toolsService.toolFns[toolName](tool.params) - toolResultVal = val + const params = await this._toolsService.validateParams[toolName](tool.paramsStr) + toolParams = params } catch (error) { const errorMessage = getErrorMessage(error) - this._addMessageToThread(threadId, { role: 'tool', name: toolName, params: tool.params, id: tool.id, content: errorMessage, result: { type: 'error', value: errorMessage }, }) + this._addMessageToThread(threadId, { role: 'tool', name: toolName, paramsStr: tool.paramsStr, id: tool.id, content: errorMessage, result: { type: 'error', value: errorMessage }, }) shouldSendAnotherMessage = true res_() return } - // 2. - let toolResultStr: string + // 2. if tool requires approval, await the approval + if (toolNamesThatRequireApproval.has(toolName)) { + const voidToolId = generateUuid() + const toolApprovalPromise = new Promise((res, rej) => { this.resRejOfToolAwaitingApproval[voidToolId] = { res, rej } }) + this._addMessageToThread(threadId, { role: 'tool_request', name: toolName, params: toolParams, voidToolId: voidToolId }) + try { + await toolApprovalPromise + // accepted tool + } + catch (e) { + const errorMessage = 'Tool call was rejected by the user.' + this._addMessageToThread(threadId, { role: 'tool', name: toolName, paramsStr: tool.paramsStr, id: tool.id, content: errorMessage, result: { type: 'error', value: errorMessage }, }) + shouldSendAnotherMessage = false + res_() + return + } + } + + // 3. call the tool + let toolResult: ToolResultType[typeof toolName] try { - toolResultStr = this._toolsService.toolResultToString[toolName](toolResultVal as any) // typescript is so bad it doesn't even couple the type of ToolResult with the type of the function being called here + toolResult = this._toolsService.callTool[toolName](toolParams as any) // typescript is so bad it doesn't even couple the type of ToolResult with the type of the function being called here } catch (error) { - // treat as irrecoverable error - this._setStreamState(threadId, { error }) + const errorMessage = getErrorMessage(error) + this._addMessageToThread(threadId, { role: 'tool', name: toolName, paramsStr: tool.paramsStr, id: tool.id, content: errorMessage, result: { type: 'error', value: errorMessage }, }) + shouldSendAnotherMessage = true res_() return } - this._addMessageToThread(threadId, { role: 'tool', name: toolName, params: tool.params, id: tool.id, content: toolResultStr, result: { type: 'success', value: toolResultVal }, }) + // 4. stringify the result to give the LLM + let toolResultStr: string + try { + toolResultStr = this._toolsService.stringOfResult[toolName](toolParams as any, toolResult as any) + } catch (error) { + const errorMessage = `Tool call succeeded, but there was an error stringifying the output.\n${getErrorMessage(error)}` + this._addMessageToThread(threadId, { role: 'tool', name: toolName, paramsStr: tool.paramsStr, id: tool.id, content: errorMessage, result: { type: 'error', value: errorMessage }, }) + shouldSendAnotherMessage = true + res_() + return + } + + // 5. add to history + this._addMessageToThread(threadId, { role: 'tool', name: toolName, paramsStr: tool.paramsStr, id: tool.id, content: toolResultStr, result: { type: 'success', params: toolParams, value: toolResult }, }) shouldSendAnotherMessage = true res_() } diff --git a/src/vs/workbench/contrib/void/common/llmMessageTypes.ts b/src/vs/workbench/contrib/void/common/llmMessageTypes.ts index 4257e616..b38e7030 100644 --- a/src/vs/workbench/contrib/void/common/llmMessageTypes.ts +++ b/src/vs/workbench/contrib/void/common/llmMessageTypes.ts @@ -45,7 +45,7 @@ export type LLMChatMessage = { export type ToolCallType = { name: ToolName; - params: string; + paramsStr: string; id: string; } @@ -56,14 +56,16 @@ export type OnError = (p: { message: string, fullError: Error | null }) => void export type AbortRef = { current: (() => void) | null } -export const toLLMChatMessage = (c: ChatMessage): LLMChatMessage => { +export const toLLMChatMessage = (c: ChatMessage): LLMChatMessage | null => { if (c.role === 'user') { return { role: c.role, content: c.content || '(empty message)' } } else if (c.role === 'assistant') return { role: c.role, content: c.content || '(empty message)' } else if (c.role === 'tool') - return { role: c.role, id: c.id, name: c.name, params: c.params, content: c.content || '(empty output)' } + return { role: c.role, id: c.id, name: c.name, params: c.paramsStr, content: c.content || '(empty output)' } + else if (c.role === 'tool_request') + return null else { throw new Error(`Role ${(c as any).role} not recognized.`) } diff --git a/src/vs/workbench/contrib/void/common/toolsService.ts b/src/vs/workbench/contrib/void/common/toolsService.ts index cf6052dd..4e0d080a 100644 --- a/src/vs/workbench/contrib/void/common/toolsService.ts +++ b/src/vs/workbench/contrib/void/common/toolsService.ts @@ -9,7 +9,6 @@ import { ISearchService } from '../../../../workbench/services/search/common/sea import { IEditCodeService } from '../browser/editCodeService.js' import { editToolDesc_toolDescription } from '../browser/prompt/prompts.js' import { IVoidFileService } from './voidFileService.js' -import { ITerminalToolService } from '../browser/terminalToolService.js' // tool use for AI @@ -129,20 +128,7 @@ export const isAToolName = (toolName: string): toolName is ToolName => { } -export type ToolParamNames = keyof typeof voidTools[T]['params'] -export type ToolParamsObj = { [paramName in ToolParamNames]: unknown } - -export type ToolCallReturnType = { - 'read_file': { uri: URI, fileContents: string, hasNextPage: boolean }, - 'list_dir': { rootURI: URI, children: DirectoryItem[] | null, hasNextPage: boolean, hasPrevPage: boolean, itemsRemaining: number }, - 'pathname_search': { queryStr: string, uris: URI[], hasNextPage: boolean }, - 'search': { queryStr: string, uris: URI[], hasNextPage: boolean }, - // --- - 'edit': { uri: URI, changeDescription: string }, - 'create_uri': { uri: URI }, - 'delete_uri': { uri: URI }, - 'terminal_command': { command: string }, -} +export const toolNamesThatRequireApproval = new Set(['create_uri', 'delete_uri', 'edit', 'terminal_command'] satisfies ToolName[]) type DirectoryItem = { uri: URI; @@ -151,8 +137,39 @@ type DirectoryItem = { isSymbolicLink: boolean; } -export type ToolFns = { [T in ToolName]: (p: string) => Promise } -export type ToolResultToString = { [T in ToolName]: (result: ToolCallReturnType[T]) => string } + +export type ToolCallParams = { + 'read_file': { uri: URI, pageNumber: number }, + 'list_dir': { rootURI: URI, pageNumber: number }, + 'pathname_search': { queryStr: string, pageNumber: number }, + 'search': { queryStr: string, pageNumber: number }, + // --- + 'edit': { uri: URI, changeDescription: string }, + 'create_uri': { uri: URI }, + 'delete_uri': { uri: URI, isRecursive: boolean }, + 'terminal_command': { command: string }, +} + + +export type ToolResultType = { + 'read_file': { fileContents: string, hasNextPage: boolean }, + 'list_dir': { children: DirectoryItem[] | null, hasNextPage: boolean, hasPrevPage: boolean, itemsRemaining: number }, + 'pathname_search': { uris: URI[], hasNextPage: boolean }, + 'search': { uris: URI[], hasNextPage: boolean }, + // --- + 'edit': {}, + 'create_uri': {}, + 'delete_uri': {}, + 'terminal_command': {}, +} + + + +export type ValidateParams = { [T in ToolName]: (p: string) => Promise } +export type CallTool = { [T in ToolName]: (p: ToolCallParams[T]) => Promise } +export type ToolResultToString = { [T in ToolName]: (p: ToolCallParams[T], result: ToolResultType[T]) => string } + + // pagination info @@ -165,10 +182,10 @@ const computeDirectoryResult = async ( fileService: IFileService, rootURI: URI, pageNumber: number = 1 -): Promise => { +): Promise => { const stat = await fileService.resolve(rootURI, { resolveMetadata: false }); if (!stat.isDirectory) { - return { rootURI, children: null, hasNextPage: false, hasPrevPage: false, itemsRemaining: 0 }; + return { children: null, hasNextPage: false, hasPrevPage: false, itemsRemaining: 0 }; } const originalChildrenLength = stat.children?.length ?? 0; @@ -188,7 +205,6 @@ const computeDirectoryResult = async ( const itemsRemaining = Math.max(0, originalChildrenLength - (toChildIdx + 1)); return { - rootURI, children, hasNextPage, hasPrevPage, @@ -196,16 +212,16 @@ const computeDirectoryResult = async ( }; }; -const directoryResultToString = (result: ToolCallReturnType['list_dir']): string => { +const directoryResultToString = (params: ToolCallParams['list_dir'], result: ToolResultType['list_dir']): string => { if (!result.children) { - return `Error: ${result.rootURI} is not a directory`; + return `Error: ${params.rootURI} is not a directory`; } let output = ''; const entries = result.children; if (!result.hasPrevPage) { - output += `${result.rootURI}\n`; + output += `${params.rootURI}\n`; } for (let i = 0; i < entries.length; i++) { @@ -270,8 +286,9 @@ const validateRecursiveParamStr = (paramsUnknown: unknown) => { export interface IToolsService { readonly _serviceBrand: undefined; - toolFns: ToolFns; - toolResultToString: ToolResultToString; + validateParams: ValidateParams; + callTool: CallTool; + stringOfResult: ToolResultToString; } export const IToolsService = createDecorator('ToolsService'); @@ -280,8 +297,9 @@ export class ToolsService implements IToolsService { readonly _serviceBrand: undefined; - public toolFns: ToolFns; - public toolResultToString: ToolResultToString; + public validateParams: ValidateParams; + public callTool: CallTool; + public stringOfResult: ToolResultToString; constructor( @@ -291,12 +309,12 @@ export class ToolsService implements IToolsService { @IInstantiationService instantiationService: IInstantiationService, @IVoidFileService voidFileService: IVoidFileService, @IEditCodeService editCodeService: IEditCodeService, - @ITerminalToolService private readonly terminalToolService: ITerminalToolService, + // @ITerminalToolService private readonly terminalToolService: ITerminalToolService, ) { const queryBuilder = instantiationService.createInstance(QueryBuilder); - this.toolFns = { + this.validateParams = { read_file: async (params: string) => { console.log('read_file') @@ -306,18 +324,7 @@ export class ToolsService implements IToolsService { const uri = validateURI(uriStr) const pageNumber = validatePageNum(pageNumberUnknown) - const readFileContents = await voidFileService.readFile(uri) - - const fromIdx = MAX_FILE_CHARS_PAGE * (pageNumber - 1) - const toIdx = MAX_FILE_CHARS_PAGE * pageNumber - 1 - const fileContents = readFileContents.slice(fromIdx, toIdx + 1) || '(empty)' // paginate - const hasNextPage = (readFileContents.length - 1) - toIdx >= 1 - - - console.log('read_file result:', fileContents) - - - return { uri, fileContents, hasNextPage } + return { uri, pageNumber } }, list_dir: async (params: string) => { const o = validateJSON(params) @@ -325,9 +332,7 @@ export class ToolsService implements IToolsService { const uri = validateURI(uriStr) const pageNumber = validatePageNum(pageNumberUnknown) - - const dirResult = await computeDirectoryResult(fileService, uri, pageNumber) - return dirResult + return { rootURI: uri, pageNumber } }, pathname_search: async (params: string) => { const o = validateJSON(params) @@ -336,6 +341,74 @@ export class ToolsService implements IToolsService { const queryStr = validateStr('query', queryUnknown) const pageNumber = validatePageNum(pageNumberUnknown) + return { queryStr, pageNumber } + + }, + search: async (params: string) => { + const o = validateJSON(params) + const { query: queryUnknown, pageNumber: pageNumberUnknown } = o + + const queryStr = validateStr('query', queryUnknown) + const pageNumber = validatePageNum(pageNumberUnknown) + + return { queryStr, pageNumber } + }, + + // --- + + create_uri: async (params: string) => { + const o = validateJSON(params) + const { uri: uriStr } = o + const uri = validateURI(uriStr) + return { uri } + }, + + delete_uri: async (params: string) => { + const o = validateJSON(params) + const { uri: uriStr, params: paramsStr } = o + const uri = validateURI(uriStr) + const isRecursive = validateRecursiveParamStr(paramsStr) + return { uri, isRecursive } + }, + + edit: async (params: string) => { + const o = validateJSON(params) + const { uri: uriStr, changeDescription: changeDescriptionUnknown } = o + const uri = validateURI(uriStr) + const changeDescription = validateStr('changeDescription', changeDescriptionUnknown) + + + return { uri, changeDescription } + }, + + terminal_command: async (s: string) => { + const o = validateJSON(s) + const { command: commandUnknown } = o + const command = validateStr('command', commandUnknown) + return { command } + }, + + } + + + this.callTool = { + read_file: async ({ uri, pageNumber }) => { + const readFileContents = await voidFileService.readFile(uri) + + const fromIdx = MAX_FILE_CHARS_PAGE * (pageNumber - 1) + const toIdx = MAX_FILE_CHARS_PAGE * pageNumber - 1 + const fileContents = readFileContents.slice(fromIdx, toIdx + 1) || '(empty)' // paginate + const hasNextPage = (readFileContents.length - 1) - toIdx >= 1 + console.log('read_file result:', fileContents) + return { fileContents, hasNextPage } + }, + + list_dir: async ({ rootURI, pageNumber }) => { + const dirResult = await computeDirectoryResult(fileService, rootURI, pageNumber) + return dirResult + }, + + pathname_search: async ({ queryStr, pageNumber }) => { const query = queryBuilder.file(workspaceContextService.getWorkspace().folders.map(f => f.uri), { filePattern: queryStr, }) const data = await searchService.fileSearch(query, CancellationToken.None) @@ -346,15 +419,10 @@ export class ToolsService implements IToolsService { .map(({ resource, results }) => resource) const hasNextPage = (data.results.length - 1) - toIdx >= 1 - return { queryStr, uris, hasNextPage } + return { uris, hasNextPage } }, - search: async (params: string) => { - const o = validateJSON(params) - const { query: queryUnknown, pageNumber: pageNumberUnknown } = o - - const queryStr = validateStr('query', queryUnknown) - const pageNumber = validatePageNum(pageNumberUnknown) + search: async ({ queryStr, pageNumber }) => { const query = queryBuilder.text({ pattern: queryStr, }, workspaceContextService.getWorkspace().folders.map(f => f.uri)) const data = await searchService.textSearch(query, CancellationToken.None) @@ -370,83 +438,67 @@ export class ToolsService implements IToolsService { // --- - create_uri: async (params: string) => { - const o = validateJSON(params) - const { uri: uriStr } = o - const uri = validateURI(uriStr) + create_uri: async ({ uri }) => { await fileService.createFile(uri) - return { uri } + return {} }, - delete_uri: async (params: string) => { - const o = validateJSON(params) - const { uri: uriStr, params: paramsStr } = o - const uri = validateURI(uriStr) - const isRecursive = validateRecursiveParamStr(paramsStr) + delete_uri: async ({ uri, isRecursive }) => { await fileService.del(uri, { recursive: isRecursive }) - return { uri } + return {} }, - edit: async (params: string) => { - const o = validateJSON(params) - const { uri: uriStr, changeDescription: changeDescriptionUnknown } = o - const uri = validateURI(uriStr) - const changeDescription = validateStr('changeDescription', changeDescriptionUnknown) - - const applyId = editCodeService.startApplying({ uri, applyStr: changeDescription, from: 'ClickApply', type: 'rewrite' }) - - // // TODO!!! - - // await // await apply done before moving on - - return { uri, changeDescription } + edit: async ({ uri, changeDescription }) => { + const p = new Promise((res, rej) => { + editCodeService.startApplying({ + uri, + applyStr: changeDescription, + from: 'ClickApply', + type: 'rewrite', + onFinalMessage: (p) => { res(p) }, + onError: (e) => { throw new Error(e.message) }, + }) + }) + await p + return {} }, - - terminal_command: async (s: string) => { - const o = validateJSON(s) - const { command: commandUnknown } = o - const command = validateStr('command', commandUnknown) - + terminal_command: async ({ command }) => { // TODO!!!! // await // Await user confirmation and then command execution before resolving - - - return { command } + return {} }, - - - } + const nextPageStr = (hasNextPage: boolean) => hasNextPage ? '\n\n(more on next page...)' : '' // given to the LLM after the call - this.toolResultToString = { - read_file: (result) => { - return nextPageStr(result.hasNextPage) + this.stringOfResult = { + read_file: (params, result) => { + return result.fileContents + nextPageStr(result.hasNextPage) }, - list_dir: (result) => { - const dirTreeStr = directoryResultToString(result) + list_dir: (params, result) => { + const dirTreeStr = directoryResultToString(params, result) return dirTreeStr + nextPageStr(result.hasNextPage) }, - pathname_search: (result) => { + pathname_search: (params, result) => { return result.uris.map(uri => uri.fsPath).join('\n') + nextPageStr(result.hasNextPage) }, - search: (result) => { + search: (params, result) => { return result.uris.map(uri => uri.fsPath).join('\n') + nextPageStr(result.hasNextPage) }, // --- - create_uri: (result) => { - return `URI ${result.uri.fsPath} successfully created.` + create_uri: (params, result) => { + return `URI ${params.uri.fsPath} successfully created.` }, - delete_uri: (result) => { - return `URI ${result.uri.fsPath} successfully deleted.` + delete_uri: (params, result) => { + return `URI ${params.uri.fsPath} successfully deleted.` }, - edit: (result) => { - return `Change successfully made ${result.uri.fsPath} successfully deleted.` + edit: (params, result) => { + return `Change successfully made ${params.uri.fsPath} successfully deleted.` }, - terminal_command: (result) => { - return `Terminal command "${result.command}" successfully executed.` + terminal_command: (params, result) => { + return `Terminal command "${params.command}" successfully executed.` }, } diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/MODELS.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/MODELS.ts index c618a0da..ccad5462 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/MODELS.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/MODELS.ts @@ -10,7 +10,7 @@ import OpenAI, { ClientOptions } from 'openai'; import { Model as OpenAIModel } from 'openai/resources/models.js'; import { extractReasoningOnFinalMessage, extractReasoningOnTextWrapper } from '../../browser/helpers/extractCodeFromResult.js'; import { LLMChatMessage, LLMFIMMessage, ModelListParams, OllamaModelResponse, OnError, OnFinalMessage, OnText } from '../../common/llmMessageTypes.js'; -import { InternalToolInfo, isAToolName } from '../../common/toolsService.js'; +import { InternalToolInfo, isAToolName, ToolName } from '../../common/toolsService.js'; import { defaultProviderSettings, displayInfoOfProviderName, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js'; import { prepareFIMMessage, prepareMessages } from './preprocessLLMMessages.js'; @@ -582,12 +582,13 @@ const toOpenAICompatibleTool = (toolInfo: InternalToolInfo) => { } satisfies OpenAI.Chat.Completions.ChatCompletionTool } -type ToolCallOfIndex = { [index: string]: { name: string, params: string, id: string } } +type ToolCallOfIndex = { [index: string]: { name: string, paramsStr: string, id: string } } // type used to stream tool calls as they come in +type ToolCallsFrom_ReturnType = { name: ToolName, id: string, paramsStr: string }[] // return type of toolCallsFrom_ -const toolCallsFrom_OpenAICompat = (toolCallOfIndex: ToolCallOfIndex) => { +const toolCallsFrom_OpenAICompat = (toolCallOfIndex: ToolCallOfIndex): ToolCallsFrom_ReturnType => { return Object.keys(toolCallOfIndex).map(index => { const tool = toolCallOfIndex[index] - return isAToolName(tool.name) ? { name: tool.name, id: tool.id, params: tool.params } : null + return isAToolName(tool.name) ? { name: tool.name, id: tool.id, paramsStr: tool.paramsStr } : null }).filter(t => !!t) } @@ -719,9 +720,9 @@ const _sendOpenAICompatibleChat = ({ messages: messages_, onText, onFinalMessage // tool call for (const tool of chunk.choices[0]?.delta?.tool_calls ?? []) { const index = tool.index - if (!toolCallOfIndex[index]) toolCallOfIndex[index] = { name: '', params: '', id: '' } + if (!toolCallOfIndex[index]) toolCallOfIndex[index] = { name: '', paramsStr: '', id: '' } toolCallOfIndex[index].name += tool.function?.name ?? '' - toolCallOfIndex[index].params += tool.function?.arguments ?? ''; + toolCallOfIndex[index].paramsStr += tool.function?.arguments ?? ''; toolCallOfIndex[index].id = tool.id ?? '' } // message @@ -804,11 +805,11 @@ const toAnthropicTool = (toolInfo: InternalToolInfo) => { } satisfies Anthropic.Messages.Tool } -const toolCallsFromAnthropicContent = (content: Anthropic.Messages.ContentBlock[]) => { +const toolCallsFrom_AnthropicContent = (content: Anthropic.Messages.ContentBlock[]): ToolCallsFrom_ReturnType => { return content.map(c => { if (c.type !== 'tool_use') return null if (!isAToolName(c.name)) return null - return c.type === 'tool_use' ? { name: c.name, params: JSON.stringify(c.input), id: c.id } : null + return c.type === 'tool_use' ? { name: c.name, paramsStr: JSON.stringify(c.input), id: c.id } : null }).filter(t => !!t) } @@ -842,7 +843,7 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM // when we get the final message on this stream (or when error/fail) stream.on('finalMessage', (response) => { const content = response.content.map(c => c.type === 'text' ? c.text : '').join('\n\n') - const toolCalls = toolCallsFromAnthropicContent(response.content) + const toolCalls = toolCallsFrom_AnthropicContent(response.content) onFinalMessage({ fullText: content, toolCalls }) }) // on error From 7311f3eaf70a99fb9522eea30c994101cd72e3ee Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 3 Mar 2025 20:55:04 -0800 Subject: [PATCH 03/41] + --- src/vs/workbench/contrib/void/common/toolsService.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/void/common/toolsService.ts b/src/vs/workbench/contrib/void/common/toolsService.ts index cf6052dd..d876fff3 100644 --- a/src/vs/workbench/contrib/void/common/toolsService.ts +++ b/src/vs/workbench/contrib/void/common/toolsService.ts @@ -9,7 +9,7 @@ import { ISearchService } from '../../../../workbench/services/search/common/sea import { IEditCodeService } from '../browser/editCodeService.js' import { editToolDesc_toolDescription } from '../browser/prompt/prompts.js' import { IVoidFileService } from './voidFileService.js' -import { ITerminalToolService } from '../browser/terminalToolService.js' +// import { ITerminalToolService } from '../browser/terminalToolService.js' // tool use for AI @@ -291,7 +291,7 @@ export class ToolsService implements IToolsService { @IInstantiationService instantiationService: IInstantiationService, @IVoidFileService voidFileService: IVoidFileService, @IEditCodeService editCodeService: IEditCodeService, - @ITerminalToolService private readonly terminalToolService: ITerminalToolService, + // @ITerminalToolService private readonly terminalToolService: ITerminalToolService, ) { const queryBuilder = instantiationService.createInstance(QueryBuilder); @@ -393,7 +393,7 @@ export class ToolsService implements IToolsService { const uri = validateURI(uriStr) const changeDescription = validateStr('changeDescription', changeDescriptionUnknown) - const applyId = editCodeService.startApplying({ uri, applyStr: changeDescription, from: 'ClickApply', type: 'rewrite' }) + // const applyId = editCodeService.startApplying({ uri, applyStr: changeDescription, from: 'ClickApply', type: 'rewrite' }) // // TODO!!! From e23fee51918772707c632cf4f8ef457a858a9af2 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 3 Mar 2025 21:53:14 -0800 Subject: [PATCH 04/41] fix untraceable import error that breaks everything --- .../{common => browser}/chatThreadService.ts | 8 +-- .../contrib/void/browser/editCodeService.ts | 57 ++--------------- .../void/browser/editCodeServiceInterface.ts | 62 +++++++++++++++++++ .../browser/helpers/extractCodeFromResult.ts | 2 +- .../contrib/void/browser/quickEditActions.ts | 2 +- .../src/markdown/ApplyBlockHoverButtons.tsx | 1 - .../react/src/markdown/ChatMarkdownRender.tsx | 2 +- .../react/src/sidebar-tsx/SidebarChat.tsx | 6 +- .../void/browser/react/src/util/services.tsx | 7 ++- .../react/src/void-settings-tsx/Settings.tsx | 2 +- .../contrib/void/browser/sidebarActions.ts | 2 +- .../void/{common => browser}/toolsService.ts | 10 +-- .../contrib/void/browser/void.contribution.ts | 4 +- .../helpers/detectLanguage.ts | 0 .../{browser => common}/helpers/systemInfo.ts | 0 .../contrib/void/common/llmMessageTypes.ts | 4 +- .../{browser => common}/prompt/prompts.ts | 4 +- .../void/electron-main/llmMessage/MODELS.ts | 2 +- 18 files changed, 96 insertions(+), 79 deletions(-) rename src/vs/workbench/contrib/void/{common => browser}/chatThreadService.ts (98%) create mode 100644 src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts rename src/vs/workbench/contrib/void/{common => browser}/toolsService.ts (97%) rename src/vs/workbench/contrib/void/{browser => common}/helpers/detectLanguage.ts (100%) rename src/vs/workbench/contrib/void/{browser => common}/helpers/systemInfo.ts (100%) rename src/vs/workbench/contrib/void/{browser => common}/prompt/prompts.ts (99%) diff --git a/src/vs/workbench/contrib/void/common/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts similarity index 98% rename from src/vs/workbench/contrib/void/common/chatThreadService.ts rename to src/vs/workbench/contrib/void/browser/chatThreadService.ts index 32afe5e8..e9bd708a 100644 --- a/src/vs/workbench/contrib/void/common/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -11,12 +11,12 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo import { URI } from '../../../../base/common/uri.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { IRange } from '../../../../editor/common/core/range.js'; -import { ILLMMessageService } from './llmMessageService.js'; -import { chat_userMessageContent, chat_systemMessage, chat_userMessageContentWithAllFilesToo, chat_selectionsString } from '../browser/prompt/prompts.js'; +import { ILLMMessageService } from '../common/llmMessageService.js'; +import { chat_userMessageContent, chat_systemMessage, chat_userMessageContentWithAllFilesToo, chat_selectionsString } from '../common/prompt/prompts.js'; import { InternalToolInfo, IToolsService, ToolCallParams, ToolResultType, ToolName, toolNamesThatRequireApproval, voidTools } from './toolsService.js'; -import { toLLMChatMessage, ToolCallType } from './llmMessageTypes.js'; +import { toLLMChatMessage, ToolCallType } from '../common/llmMessageTypes.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; -import { IVoidFileService } from './voidFileService.js'; +import { IVoidFileService } from '../common/voidFileService.js'; import { generateUuid } from '../../../../base/common/uuid.js'; import { getErrorMessage } from '../../../../base/common/errors.js'; diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index f8b7dd07..5e1625fa 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -5,7 +5,7 @@ import { Disposable } from '../../../../base/common/lifecycle.js'; import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; -import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ICodeEditor, IOverlayWidget, IViewZone, OverlayWidgetPositionPreference } from '../../../../editor/browser/editorBrowser.js'; // import { IUndoRedoService } from '../../../../platform/undoRedo/common/undoRedo.js'; @@ -25,23 +25,24 @@ import * as dom from '../../../../base/browser/dom.js'; import { Widget } from '../../../../base/browser/ui/widget.js'; import { URI } from '../../../../base/common/uri.js'; import { IConsistentEditorItemService, IConsistentItemService } from './helperServices/consistentItemService.js'; -import { voidPrefixAndSuffix, ctrlKStream_userMessage, ctrlKStream_systemMessage, defaultQuickEditFimTags, rewriteCode_systemMessage, rewriteCode_userMessage, searchReplace_systemMessage, searchReplace_userMessage, } from './prompt/prompts.js'; +import { voidPrefixAndSuffix, ctrlKStream_userMessage, ctrlKStream_systemMessage, defaultQuickEditFimTags, rewriteCode_systemMessage, rewriteCode_userMessage, searchReplace_systemMessage, searchReplace_userMessage, } from '../common/prompt/prompts.js'; import { mountCtrlK } from './react/out/quick-edit-tsx/index.js' import { QuickEditPropsType } from './quickEditActions.js'; import { IModelContentChangedEvent } from '../../../../editor/common/textModelEvents.js'; import { extractCodeFromFIM, extractCodeFromRegular, ExtractedSearchReplaceBlock, extractSearchReplaceBlocks } from './helpers/extractCodeFromResult.js'; -import { filenameToVscodeLanguage } from './helpers/detectLanguage.js'; +import { filenameToVscodeLanguage } from '../common/helpers/detectLanguage.js'; import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js'; import { isMacintosh } from '../../../../base/common/platform.js'; import { EditorOption } from '../../../../editor/common/config/editorOptions.js'; -import { Emitter, Event } from '../../../../base/common/event.js'; +import { Emitter } from '../../../../base/common/event.js'; import { VOID_OPEN_SETTINGS_ACTION_ID } from './voidSettingsPane.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { ILLMMessageService } from '../common/llmMessageService.js'; -import { LLMChatMessage, OnError, OnFinalMessage, OnText, errorDetails } from '../common/llmMessageTypes.js'; +import { LLMChatMessage, OnError, errorDetails } from '../common/llmMessageTypes.js'; import { IMetricsService } from '../common/metricsService.js'; import { IVoidFileService } from '../common/voidFileService.js'; +import { IEditCodeService, URIStreamState, AddCtrlKOpts, StartApplyingOpts } from './editCodeServiceInterface.js'; const configOfBG = (color: Color) => { return { dark: color, light: color, hcDark: color, hcLight: color, } @@ -121,32 +122,8 @@ const findTextInCode = (text: string, fileContents: string, startingAtLine?: num } -export type URIStreamState = 'idle' | 'acceptRejectAll' | 'streaming' -export type StartApplyingOpts = { - from: 'QuickEdit'; - type: 'rewrite'; - diffareaid: number; // id of the CtrlK area (contains text selection) -} | { - from: 'ClickApply'; - type: 'searchReplace' | 'rewrite'; - applyStr: string; - uri: 'current' | URI; - - onText?: OnText; - onFinalMessage?: OnFinalMessage; - onError?: OnError; -} - - - -export type AddCtrlKOpts = { - startLine: number, - endLine: number, - editor: ICodeEditor, -} - // // TODO diffArea should be removed if we just discovered it has no more diffs in it // for (const diffareaid of this.diffAreasOfURI[uri.fsPath] || []) { // const diffArea = this.diffAreaOfId[diffareaid] @@ -252,28 +229,6 @@ type HistorySnapshot = { type StreamLocationMutable = { line: number, col: number, addedSplitYet: boolean, originalCodeStartLine: number } -export interface IEditCodeService { - readonly _serviceBrand: undefined; - startApplying(opts: StartApplyingOpts): URI | null; - - addCtrlKZone(opts: AddCtrlKOpts): number | undefined; - removeCtrlKZone(opts: { diffareaid: number }): void; - removeDiffAreas(opts: { uri: URI, removeCtrlKs: boolean, behavior: 'reject' | 'accept' }): void; - - // CtrlKZone streaming state - isCtrlKZoneStreaming(opts: { diffareaid: number }): boolean; - interruptCtrlKStreaming(opts: { diffareaid: number }): void; - onDidChangeCtrlKZoneStreaming: Event<{ uri: URI; diffareaid: number }>; - - // // DiffZone codeBoxId streaming state - getURIStreamState(opts: { uri: URI | null }): URIStreamState; - interruptURIStreaming(opts: { uri: URI }): void; - onDidChangeURIStreamState: Event<{ uri: URI; state: URIStreamState }>; - - // testDiffs(): void; -} - -export const IEditCodeService = createDecorator('editCodeService'); class EditCodeService extends Disposable implements IEditCodeService { _serviceBrand: undefined; diff --git a/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts b/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts new file mode 100644 index 00000000..1d900ce7 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts @@ -0,0 +1,62 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +import { Event } from '../../../../base/common/event.js'; +import { URI } from '../../../../base/common/uri.js'; +import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; +import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +import { OnError, OnFinalMessage, OnText } from '../common/llmMessageTypes.js'; + + + + +export type StartApplyingOpts = { + from: 'QuickEdit'; + type: 'rewrite'; + diffareaid: number; // id of the CtrlK area (contains text selection) +} | { + from: 'ClickApply'; + type: 'searchReplace' | 'rewrite'; + applyStr: string; + uri: 'current' | URI; + + onText?: OnText; + onFinalMessage?: OnFinalMessage; + onError?: OnError; +} + + + +export type AddCtrlKOpts = { + startLine: number, + endLine: number, + editor: ICodeEditor, +} + +export type URIStreamState = 'idle' | 'acceptRejectAll' | 'streaming' + + +export const IEditCodeService = createDecorator('editCodeService'); + +export interface IEditCodeService { + readonly _serviceBrand: undefined; + startApplying(opts: StartApplyingOpts): URI | null; + + addCtrlKZone(opts: AddCtrlKOpts): number | undefined; + removeCtrlKZone(opts: { diffareaid: number }): void; + removeDiffAreas(opts: { uri: URI, removeCtrlKs: boolean, behavior: 'reject' | 'accept' }): void; + + // CtrlKZone streaming state + isCtrlKZoneStreaming(opts: { diffareaid: number }): boolean; + interruptCtrlKStreaming(opts: { diffareaid: number }): void; + onDidChangeCtrlKZoneStreaming: Event<{ uri: URI; diffareaid: number }>; + + // // DiffZone codeBoxId streaming state + getURIStreamState(opts: { uri: URI | null }): URIStreamState; + interruptURIStreaming(opts: { uri: URI }): void; + onDidChangeURIStreamState: Event<{ uri: URI; state: URIStreamState }>; + + // testDiffs(): void; +} diff --git a/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts b/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts index e24bc232..7b3eca15 100644 --- a/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts +++ b/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------*/ import { OnText } from '../../common/llmMessageTypes.js' -import { DIVIDER, FINAL, ORIGINAL } from '../prompt/prompts.js' +import { DIVIDER, FINAL, ORIGINAL } from '../../common/prompt/prompts.js' class SurroundingsRemover { readonly originalS: string diff --git a/src/vs/workbench/contrib/void/browser/quickEditActions.ts b/src/vs/workbench/contrib/void/browser/quickEditActions.ts index da8f5c55..a420d8bb 100644 --- a/src/vs/workbench/contrib/void/browser/quickEditActions.ts +++ b/src/vs/workbench/contrib/void/browser/quickEditActions.ts @@ -8,7 +8,7 @@ import { Action2, registerAction2 } from '../../../../platform/actions/common/ac import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; -import { IEditCodeService } from './editCodeService.js'; +import { IEditCodeService } from './editCodeServiceInterface.js'; import { roundRangeToLines } from './sidebarActions.js'; import { VOID_CTRL_K_ACTION_ID } from './actionIDs.js'; import { localize2 } from '../../../../nls.js'; 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 8ce22b2f..ec6107ba 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 @@ -3,7 +3,6 @@ import { useAccessor, useURIStreamState, useSettingsState } from '../util/servic import { useRefState } from '../util/helpers.js' import { isFeatureNameDisabled } from '../../../../common/voidSettingsTypes.js' import { URI } from '../../../../../../../base/common/uri.js' -import { IEditCodeService, URIStreamState } from '../../../editCodeService.js' enum CopyButtonText { Idle = 'Copy', 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 c8571403..0d57ca81 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 @@ -6,7 +6,7 @@ import React, { JSX } from 'react' import { marked, MarkedToken, Token } from 'marked' import { BlockCode } from './BlockCode.js' -import { nameToVscodeLanguage } from '../../../helpers/detectLanguage.js' +import { nameToVscodeLanguage } from '../../../../common/helpers/detectLanguage.js' import { ApplyBlockHoverButtons } from './ApplyBlockHoverButtons.js' export type ChatMessageLocation = { 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 e89ddbb0..8e890fc2 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 @@ -7,7 +7,7 @@ import React, { ButtonHTMLAttributes, FormEvent, FormHTMLAttributes, Fragment, K import { useAccessor, useSidebarState, useChatThreadsState, useChatThreadsStreamState, useUriState, useSettingsState } from '../util/services.js'; -import { ChatMessage, StagingSelectionItem, ToolMessage } from '../../../../common/chatThreadService.js'; +import { ChatMessage, StagingSelectionItem, ToolMessage } from '../../../chatThreadService.js'; import { BlockCode } from '../markdown/BlockCode.js'; import { ChatMarkdownRender, ChatMessageLocation } from '../markdown/ChatMarkdownRender.js'; @@ -19,13 +19,13 @@ import { ModelDropdown, } from '../void-settings-tsx/ModelDropdown.js'; import { SidebarThreadSelector } from './SidebarThreadSelector.js'; import { useScrollbarStyles } from '../util/useScrollbarStyles.js'; import { VOID_CTRL_L_ACTION_ID } from '../../../actionIDs.js'; -import { filenameToVscodeLanguage } from '../../../helpers/detectLanguage.js'; +import { filenameToVscodeLanguage } from '../../../../common/helpers/detectLanguage.js'; import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js'; import { ChevronRight, Pencil, X } from 'lucide-react'; import { FeatureName, isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js'; import { WarningBox } from '../void-settings-tsx/WarningBox.js'; -import { ToolResultType, ToolName } from '../../../../common/toolsService.js'; +import { ToolResultType, ToolName } from '../../../toolsService.js'; 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 9898ef52..33d0ae8b 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 @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------*/ import React, { useState, useEffect, useCallback } from 'react' -import { ThreadStreamState,IChatThreadService, ThreadsState } from '../../../../common/chatThreadService.js' +import { ThreadStreamState,IChatThreadService, ThreadsState } from '../../../chatThreadService.js' import { RefreshableProviderName, SettingsOfProvider } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js' import { IDisposable } from '../../../../../../../base/common/lifecycle.js' import { VoidSidebarState } from '../../../sidebarStateService.js' @@ -25,7 +25,8 @@ import { IThemeService } from '../../../../../../../platform/theme/common/themeS import { ILLMMessageService } from '../../../../../../../workbench/contrib/void/common/llmMessageService.js'; import { IRefreshModelService } from '../../../../../../../workbench/contrib/void/common/refreshModelService.js'; import { IVoidSettingsService } from '../../../../../../../workbench/contrib/void/common/voidSettingsService.js'; -import { IEditCodeService, URIStreamState } from '../../../editCodeService.js'; +import { IEditCodeService, URIStreamState } from '../../../editCodeServiceInterface.js' + import { IVoidUriStateService } from '../../../voidUriStateService.js'; import { IQuickEditStateService } from '../../../quickEditStateService.js'; import { ISidebarStateService } from '../../../sidebarStateService.js'; @@ -171,7 +172,7 @@ export const _registerServices = (accessor: ServicesAccessor) => { colorThemeState = themeService.getColorTheme().type disposables.push( themeService.onDidColorThemeChange(theme => { - colorThemeState = theme.type + colorThemeState = theme.theme.type colorThemeStateListeners.forEach(l => l(colorThemeState)) }) ) diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx index 7f28467f..f886fdff 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx @@ -17,7 +17,7 @@ import { env } from '../../../../../../../base/common/process.js' import { ModelDropdown } from './ModelDropdown.js' import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js' import { WarningBox } from './WarningBox.js' -import { os } from '../../../helpers/systemInfo.js' +import { os } from '../../../../common/helpers/systemInfo.js' const SubtleButton = ({ onClick, text, icon, disabled }: { onClick: () => void, text: string, icon: React.ReactNode, disabled: boolean }) => { diff --git a/src/vs/workbench/contrib/void/browser/sidebarActions.ts b/src/vs/workbench/contrib/void/browser/sidebarActions.ts index fff6dda4..23974f7e 100644 --- a/src/vs/workbench/contrib/void/browser/sidebarActions.ts +++ b/src/vs/workbench/contrib/void/browser/sidebarActions.ts @@ -11,7 +11,7 @@ import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; -import { StagingSelectionItem, IChatThreadService } from '../common/chatThreadService.js'; +import { StagingSelectionItem, IChatThreadService } from './chatThreadService.js'; import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; import { IRange } from '../../../../editor/common/core/range.js'; diff --git a/src/vs/workbench/contrib/void/common/toolsService.ts b/src/vs/workbench/contrib/void/browser/toolsService.ts similarity index 97% rename from src/vs/workbench/contrib/void/common/toolsService.ts rename to src/vs/workbench/contrib/void/browser/toolsService.ts index 4e0d080a..64c9bf1d 100644 --- a/src/vs/workbench/contrib/void/common/toolsService.ts +++ b/src/vs/workbench/contrib/void/browser/toolsService.ts @@ -4,11 +4,11 @@ import { IFileService } from '../../../../platform/files/common/files.js' import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js' import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js' import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js' -import { QueryBuilder } from '../../../../workbench/services/search/common/queryBuilder.js' -import { ISearchService } from '../../../../workbench/services/search/common/search.js' -import { IEditCodeService } from '../browser/editCodeService.js' -import { editToolDesc_toolDescription } from '../browser/prompt/prompts.js' -import { IVoidFileService } from './voidFileService.js' +import { QueryBuilder } from '../../../services/search/common/queryBuilder.js' +import { ISearchService } from '../../../services/search/common/search.js' +import { IEditCodeService } from './editCodeServiceInterface.js' +import { editToolDesc_toolDescription } from '../common/prompt/prompts.js' +import { IVoidFileService } from '../common/voidFileService.js' // tool use for AI diff --git a/src/vs/workbench/contrib/void/browser/void.contribution.ts b/src/vs/workbench/contrib/void/browser/void.contribution.ts index f9f6a29b..d5e78754 100644 --- a/src/vs/workbench/contrib/void/browser/void.contribution.ts +++ b/src/vs/workbench/contrib/void/browser/void.contribution.ts @@ -53,8 +53,8 @@ import '../common/metricsService.js' import '../common/voidUpdateService.js' // tools -import '../common/toolsService.js' +import './toolsService.js' // register Thread History -import '../common/chatThreadService.js' +import './chatThreadService.js' diff --git a/src/vs/workbench/contrib/void/browser/helpers/detectLanguage.ts b/src/vs/workbench/contrib/void/common/helpers/detectLanguage.ts similarity index 100% rename from src/vs/workbench/contrib/void/browser/helpers/detectLanguage.ts rename to src/vs/workbench/contrib/void/common/helpers/detectLanguage.ts diff --git a/src/vs/workbench/contrib/void/browser/helpers/systemInfo.ts b/src/vs/workbench/contrib/void/common/helpers/systemInfo.ts similarity index 100% rename from src/vs/workbench/contrib/void/browser/helpers/systemInfo.ts rename to src/vs/workbench/contrib/void/common/helpers/systemInfo.ts diff --git a/src/vs/workbench/contrib/void/common/llmMessageTypes.ts b/src/vs/workbench/contrib/void/common/llmMessageTypes.ts index b38e7030..74c2d067 100644 --- a/src/vs/workbench/contrib/void/common/llmMessageTypes.ts +++ b/src/vs/workbench/contrib/void/common/llmMessageTypes.ts @@ -3,8 +3,8 @@ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------*/ -import { ChatMessage } from './chatThreadService.js' -import { InternalToolInfo, ToolName } from './toolsService.js' +import { ChatMessage } from '../browser/chatThreadService.js' +import { InternalToolInfo, ToolName } from '../browser/toolsService.js' import { FeatureName, ProviderName, SettingsOfProvider } from './voidSettingsTypes.js' diff --git a/src/vs/workbench/contrib/void/browser/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts similarity index 99% rename from src/vs/workbench/contrib/void/browser/prompt/prompts.ts rename to src/vs/workbench/contrib/void/common/prompt/prompts.ts index 801abcb6..6be879c2 100644 --- a/src/vs/workbench/contrib/void/browser/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -6,10 +6,10 @@ import { URI } from '../../../../../base/common/uri.js'; import { filenameToVscodeLanguage } from '../helpers/detectLanguage.js'; -import { CodeSelection, StagingSelectionItem, FileSelection } from '../../common/chatThreadService.js'; +import { CodeSelection, StagingSelectionItem, FileSelection } from '../../browser/chatThreadService.js'; import { IModelService } from '../../../../../editor/common/services/model.js'; import { os } from '../helpers/systemInfo.js'; -import { IVoidFileService } from '../../common/voidFileService.js'; +import { IVoidFileService } from '../voidFileService.js'; // this is just for ease of readability diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/MODELS.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/MODELS.ts index ccad5462..e7945b71 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/MODELS.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/MODELS.ts @@ -10,7 +10,7 @@ import OpenAI, { ClientOptions } from 'openai'; import { Model as OpenAIModel } from 'openai/resources/models.js'; import { extractReasoningOnFinalMessage, extractReasoningOnTextWrapper } from '../../browser/helpers/extractCodeFromResult.js'; import { LLMChatMessage, LLMFIMMessage, ModelListParams, OllamaModelResponse, OnError, OnFinalMessage, OnText } from '../../common/llmMessageTypes.js'; -import { InternalToolInfo, isAToolName, ToolName } from '../../common/toolsService.js'; +import { InternalToolInfo, isAToolName, ToolName } from '../../browser/toolsService.js'; import { defaultProviderSettings, displayInfoOfProviderName, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js'; import { prepareFIMMessage, prepareMessages } from './preprocessLLMMessages.js'; From 176d302258a0ef5b071a2bd28949819b788d9f54 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 3 Mar 2025 21:53:57 -0800 Subject: [PATCH 05/41] lint --- .../workbench/contrib/void/browser/chatThreadService.ts | 2 +- src/vs/workbench/contrib/void/browser/editCodeService.ts | 2 +- .../contrib/void/browser/helpers/extractCodeFromResult.ts | 2 +- .../contrib/void/{common => browser}/prompt/prompts.ts | 8 ++++---- src/vs/workbench/contrib/void/browser/toolsService.ts | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) rename src/vs/workbench/contrib/void/{common => browser}/prompt/prompts.ts (98%) diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index e9bd708a..783cb4d4 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -12,7 +12,7 @@ import { URI } from '../../../../base/common/uri.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { IRange } from '../../../../editor/common/core/range.js'; import { ILLMMessageService } from '../common/llmMessageService.js'; -import { chat_userMessageContent, chat_systemMessage, chat_userMessageContentWithAllFilesToo, chat_selectionsString } from '../common/prompt/prompts.js'; +import { chat_userMessageContent, chat_systemMessage, chat_userMessageContentWithAllFilesToo, chat_selectionsString } from './prompt/prompts.js'; import { InternalToolInfo, IToolsService, ToolCallParams, ToolResultType, ToolName, toolNamesThatRequireApproval, voidTools } from './toolsService.js'; import { toLLMChatMessage, ToolCallType } from '../common/llmMessageTypes.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index 5e1625fa..019b7887 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -25,7 +25,7 @@ import * as dom from '../../../../base/browser/dom.js'; import { Widget } from '../../../../base/browser/ui/widget.js'; import { URI } from '../../../../base/common/uri.js'; import { IConsistentEditorItemService, IConsistentItemService } from './helperServices/consistentItemService.js'; -import { voidPrefixAndSuffix, ctrlKStream_userMessage, ctrlKStream_systemMessage, defaultQuickEditFimTags, rewriteCode_systemMessage, rewriteCode_userMessage, searchReplace_systemMessage, searchReplace_userMessage, } from '../common/prompt/prompts.js'; +import { voidPrefixAndSuffix, ctrlKStream_userMessage, ctrlKStream_systemMessage, defaultQuickEditFimTags, rewriteCode_systemMessage, rewriteCode_userMessage, searchReplace_systemMessage, searchReplace_userMessage, } from './prompt/prompts.js'; import { mountCtrlK } from './react/out/quick-edit-tsx/index.js' import { QuickEditPropsType } from './quickEditActions.js'; diff --git a/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts b/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts index 7b3eca15..e24bc232 100644 --- a/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts +++ b/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------*/ import { OnText } from '../../common/llmMessageTypes.js' -import { DIVIDER, FINAL, ORIGINAL } from '../../common/prompt/prompts.js' +import { DIVIDER, FINAL, ORIGINAL } from '../prompt/prompts.js' class SurroundingsRemover { readonly originalS: string diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/browser/prompt/prompts.ts similarity index 98% rename from src/vs/workbench/contrib/void/common/prompt/prompts.ts rename to src/vs/workbench/contrib/void/browser/prompt/prompts.ts index 6be879c2..db27866f 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/browser/prompt/prompts.ts @@ -5,11 +5,11 @@ import { URI } from '../../../../../base/common/uri.js'; -import { filenameToVscodeLanguage } from '../helpers/detectLanguage.js'; -import { CodeSelection, StagingSelectionItem, FileSelection } from '../../browser/chatThreadService.js'; +import { filenameToVscodeLanguage } from '../../common/helpers/detectLanguage.js'; +import { CodeSelection, StagingSelectionItem, FileSelection } from '../chatThreadService.js'; import { IModelService } from '../../../../../editor/common/services/model.js'; -import { os } from '../helpers/systemInfo.js'; -import { IVoidFileService } from '../voidFileService.js'; +import { os } from '../../common/helpers/systemInfo.js'; +import { IVoidFileService } from '../../common/voidFileService.js'; // this is just for ease of readability diff --git a/src/vs/workbench/contrib/void/browser/toolsService.ts b/src/vs/workbench/contrib/void/browser/toolsService.ts index 64c9bf1d..b78d0ed5 100644 --- a/src/vs/workbench/contrib/void/browser/toolsService.ts +++ b/src/vs/workbench/contrib/void/browser/toolsService.ts @@ -7,7 +7,7 @@ import { IWorkspaceContextService } from '../../../../platform/workspace/common/ import { QueryBuilder } from '../../../services/search/common/queryBuilder.js' import { ISearchService } from '../../../services/search/common/search.js' import { IEditCodeService } from './editCodeServiceInterface.js' -import { editToolDesc_toolDescription } from '../common/prompt/prompts.js' +import { editToolDesc_toolDescription } from './prompt/prompts.js' import { IVoidFileService } from '../common/voidFileService.js' From 9aae56682e99319f17e06d4e544a78e3c42cb785 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 3 Mar 2025 21:59:47 -0800 Subject: [PATCH 06/41] sweep to fix lint --- .../src/sidebar-tsx/SidebarThreadSelector.tsx | 8 +- .../void/browser/react/tailwind.config.js | 196 +++++++++--------- .../contrib/void/common/llmMessageTypes.ts | 4 +- .../contrib/void/common/metricsService.ts | 3 +- .../llmMessage/preprocessLLMMessages.ts | 2 +- 5 files changed, 104 insertions(+), 109 deletions(-) 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 6da3d5b9..bdf2ae0d 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 @@ -68,9 +68,7 @@ export const SidebarThreadSelector = () => { let firstMsg = null; // let secondMsg = null; - const firstUserMsgIdx = pastThread.messages.findIndex( - (msg) => msg.role !== 'system' && msg.role !== 'tool' && !!msg.displayContent - ); + const firstUserMsgIdx = pastThread.messages.findIndex((msg) => msg.role !== 'tool' && msg.role !== 'tool_request'); if (firstUserMsgIdx !== -1) { // firstMsg = truncate(pastThread.messages[firstMsgIdx].displayContent ?? ''); @@ -88,9 +86,7 @@ export const SidebarThreadSelector = () => { // secondMsg = truncate(pastThread.messages[secondMsgIdx].displayContent ?? ''); // } - const numMessages = pastThread.messages.filter( - (msg) => msg.role !== 'system' - ).length; + const numMessages = pastThread.messages.filter((msg) => msg.role !== 'tool_request').length; return (
  • 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 d7c0f7e0..38a94f83 100644 --- a/src/vs/workbench/contrib/void/browser/react/tailwind.config.js +++ b/src/vs/workbench/contrib/void/browser/react/tailwind.config.js @@ -27,141 +27,141 @@ module.exports = { // common colors to use, ordered light to dark colors: { - "void-bg-1": "var(--vscode-input-background)", - "void-bg-1-alt": "var(--vscode-badge-background)", - "void-bg-2": "var(--vscode-sideBar-background)", - "void-bg-2-alt": "color-mix(in srgb, var(--vscode-sideBar-background) 30%, var(--vscode-editor-background) 70%)", - "void-bg-3": "var(--vscode-editor-background)", + 'void-bg-1': 'var(--vscode-input-background)', + 'void-bg-1-alt': 'var(--vscode-badge-background)', + 'void-bg-2': 'var(--vscode-sideBar-background)', + 'void-bg-2-alt': 'color-mix(in srgb, var(--vscode-sideBar-background) 30%, var(--vscode-editor-background) 70%)', + 'void-bg-3': 'var(--vscode-editor-background)', - "void-fg-1": "var(--vscode-editor-foreground)", - "void-fg-2": "var(--vscode-input-foreground)", - "void-fg-3": "var(--vscode-input-placeholderForeground)", - // "void-fg-4": "var(--vscode-tab-inactiveForeground)", - "void-fg-4": "var(--vscode-list-deemphasizedForeground)", + 'void-fg-1': 'var(--vscode-editor-foreground)', + 'void-fg-2': 'var(--vscode-input-foreground)', + 'void-fg-3': 'var(--vscode-input-placeholderForeground)', + // 'void-fg-4': 'var(--vscode-tab-inactiveForeground)', + 'void-fg-4': 'var(--vscode-list-deemphasizedForeground)', - "void-warning": "var(--vscode-charts-yellow)", + 'void-warning': 'var(--vscode-charts-yellow)', - "void-border-1": "var(--vscode-commandCenter-activeBorder)", - "void-border-2": "var(--vscode-commandCenter-border)", - "void-border-3": "var(--vscode-commandCenter-inactiveBorder)", - "void-border-4": "var(--vscode-editorGroup-border)", + 'void-border-1': 'var(--vscode-commandCenter-activeBorder)', + 'void-border-2': 'var(--vscode-commandCenter-border)', + 'void-border-3': 'var(--vscode-commandCenter-inactiveBorder)', + 'void-border-4': 'var(--vscode-editorGroup-border)', vscode: { // see: https://code.visualstudio.com/api/extension-guides/webview#theming-webview-content // base colors - "fg": "var(--vscode-foreground)", - "focus-border": "var(--vscode-focusBorder)", - "disabled-fg": "var(--vscode-disabledForeground)", - "widget-border": "var(--vscode-widget-border)", - "widget-shadow": "var(--vscode-widget-shadow)", - "selection-bg": "var(--vscode-selection-background)", - "description-fg": "var(--vscode-descriptionForeground)", - "error-fg": "var(--vscode-errorForeground)", - "icon-fg": "var(--vscode-icon-foreground)", - "sash-hover-border": "var(--vscode-sash-hoverBorder)", + 'fg': 'var(--vscode-foreground)', + 'focus-border': 'var(--vscode-focusBorder)', + 'disabled-fg': 'var(--vscode-disabledForeground)', + 'widget-border': 'var(--vscode-widget-border)', + 'widget-shadow': 'var(--vscode-widget-shadow)', + 'selection-bg': 'var(--vscode-selection-background)', + 'description-fg': 'var(--vscode-descriptionForeground)', + 'error-fg': 'var(--vscode-errorForeground)', + 'icon-fg': 'var(--vscode-icon-foreground)', + 'sash-hover-border': 'var(--vscode-sash-hoverBorder)', // text colors - "text-blockquote-bg": "var(--vscode-textBlockQuote-background)", - "text-blockquote-border": "var(--vscode-textBlockQuote-border)", - "text-codeblock-bg": "var(--vscode-textCodeBlock-background)", - "text-link-active-fg": "var(--vscode-textLink-activeForeground)", - "text-link-fg": "var(--vscode-textLink-foreground)", - "text-preformat-fg": "var(--vscode-textPreformat-foreground)", - "text-preformat-bg": "var(--vscode-textPreformat-background)", - "text-separator-fg": "var(--vscode-textSeparator-foreground)", + 'text-blockquote-bg': 'var(--vscode-textBlockQuote-background)', + 'text-blockquote-border': 'var(--vscode-textBlockQuote-border)', + 'text-codeblock-bg': 'var(--vscode-textCodeBlock-background)', + 'text-link-active-fg': 'var(--vscode-textLink-activeForeground)', + 'text-link-fg': 'var(--vscode-textLink-foreground)', + 'text-preformat-fg': 'var(--vscode-textPreformat-foreground)', + 'text-preformat-bg': 'var(--vscode-textPreformat-background)', + 'text-separator-fg': 'var(--vscode-textSeparator-foreground)', // input colors - "input-bg": "var(--vscode-input-background)", - "input-border": "var(--vscode-input-border)", - "input-fg": "var(--vscode-input-foreground)", - "input-placeholder-fg": "var(--vscode-input-placeholderForeground)", - "input-active-bg": "var(--vscode-input-activeBackground)", - "input-option-active-border": "var(--vscode-inputOption-activeBorder)", - "input-option-active-fg": "var(--vscode-inputOption-activeForeground)", - "input-option-hover-bg": "var(--vscode-inputOption-hoverBackground)", - "input-validation-error-bg": "var(--vscode-inputValidation-errorBackground)", - "input-validation-error-fg": "var(--vscode-inputValidation-errorForeground)", - "input-validation-error-border": "var(--vscode-inputValidation-errorBorder)", - "input-validation-info-bg": "var(--vscode-inputValidation-infoBackground)", - "input-validation-info-fg": "var(--vscode-inputValidation-infoForeground)", - "input-validation-info-border": "var(--vscode-inputValidation-infoBorder)", - "input-validation-warning-bg": "var(--vscode-inputValidation-warningBackground)", - "input-validation-warning-fg": "var(--vscode-inputValidation-warningForeground)", - "input-validation-warning-border": "var(--vscode-inputValidation-warningBorder)", + 'input-bg': 'var(--vscode-input-background)', + 'input-border': 'var(--vscode-input-border)', + 'input-fg': 'var(--vscode-input-foreground)', + 'input-placeholder-fg': 'var(--vscode-input-placeholderForeground)', + 'input-active-bg': 'var(--vscode-input-activeBackground)', + 'input-option-active-border': 'var(--vscode-inputOption-activeBorder)', + 'input-option-active-fg': 'var(--vscode-inputOption-activeForeground)', + 'input-option-hover-bg': 'var(--vscode-inputOption-hoverBackground)', + 'input-validation-error-bg': 'var(--vscode-inputValidation-errorBackground)', + 'input-validation-error-fg': 'var(--vscode-inputValidation-errorForeground)', + 'input-validation-error-border': 'var(--vscode-inputValidation-errorBorder)', + 'input-validation-info-bg': 'var(--vscode-inputValidation-infoBackground)', + 'input-validation-info-fg': 'var(--vscode-inputValidation-infoForeground)', + 'input-validation-info-border': 'var(--vscode-inputValidation-infoBorder)', + 'input-validation-warning-bg': 'var(--vscode-inputValidation-warningBackground)', + 'input-validation-warning-fg': 'var(--vscode-inputValidation-warningForeground)', + 'input-validation-warning-border': 'var(--vscode-inputValidation-warningBorder)', // command center colors (the top bar) - "commandcenter-fg": "var(--vscode-commandCenter-foreground)", - "commandcenter-active-fg": "var(--vscode-commandCenter-activeForeground)", - "commandcenter-bg": "var(--vscode-commandCenter-background)", - "commandcenter-active-bg": "var(--vscode-commandCenter-activeBackground)", - "commandcenter-border": "var(--vscode-commandCenter-border)", - "commandcenter-inactive-fg": "var(--vscode-commandCenter-inactiveForeground)", - "commandcenter-inactive-border": "var(--vscode-commandCenter-inactiveBorder)", - "commandcenter-active-border": "var(--vscode-commandCenter-activeBorder)", - "commandcenter-debugging-bg": "var(--vscode-commandCenter-debuggingBackground)", + 'commandcenter-fg': 'var(--vscode-commandCenter-foreground)', + 'commandcenter-active-fg': 'var(--vscode-commandCenter-activeForeground)', + 'commandcenter-bg': 'var(--vscode-commandCenter-background)', + 'commandcenter-active-bg': 'var(--vscode-commandCenter-activeBackground)', + 'commandcenter-border': 'var(--vscode-commandCenter-border)', + 'commandcenter-inactive-fg': 'var(--vscode-commandCenter-inactiveForeground)', + 'commandcenter-inactive-border': 'var(--vscode-commandCenter-inactiveBorder)', + 'commandcenter-active-border': 'var(--vscode-commandCenter-activeBorder)', + 'commandcenter-debugging-bg': 'var(--vscode-commandCenter-debuggingBackground)', // badge colors - "badge-fg": "var(--vscode-badge-foreground)", - "badge-bg": "var(--vscode-badge-background)", + 'badge-fg': 'var(--vscode-badge-foreground)', + 'badge-bg': 'var(--vscode-badge-background)', // button colors - "button-bg": "var(--vscode-button-background)", - "button-fg": "var(--vscode-button-foreground)", - "button-border": "var(--vscode-button-border)", - "button-separator": "var(--vscode-button-separator)", - "button-hover-bg": "var(--vscode-button-hoverBackground)", - "button-secondary-fg": "var(--vscode-button-secondaryForeground)", - "button-secondary-bg": "var(--vscode-button-secondaryBackground)", - "button-secondary-hover-bg": "var(--vscode-button-secondaryHoverBackground)", + 'button-bg': 'var(--vscode-button-background)', + 'button-fg': 'var(--vscode-button-foreground)', + 'button-border': 'var(--vscode-button-border)', + 'button-separator': 'var(--vscode-button-separator)', + 'button-hover-bg': 'var(--vscode-button-hoverBackground)', + 'button-secondary-fg': 'var(--vscode-button-secondaryForeground)', + 'button-secondary-bg': 'var(--vscode-button-secondaryBackground)', + 'button-secondary-hover-bg': 'var(--vscode-button-secondaryHoverBackground)', // checkbox colors - "checkbox-bg": "var(--vscode-checkbox-background)", - "checkbox-fg": "var(--vscode-checkbox-foreground)", - "checkbox-border": "var(--vscode-checkbox-border)", - "checkbox-select-bg": "var(--vscode-checkbox-selectBackground)", + 'checkbox-bg': 'var(--vscode-checkbox-background)', + 'checkbox-fg': 'var(--vscode-checkbox-foreground)', + 'checkbox-border': 'var(--vscode-checkbox-border)', + 'checkbox-select-bg': 'var(--vscode-checkbox-selectBackground)', // sidebar colors - "sidebar-bg": "var(--vscode-sideBar-background)", - "sidebar-fg": "var(--vscode-sideBar-foreground)", - "sidebar-border": "var(--vscode-sideBar-border)", - "sidebar-drop-bg": "var(--vscode-sideBar-dropBackground)", - "sidebar-title-fg": "var(--vscode-sideBarTitle-foreground)", - "sidebar-header-bg": "var(--vscode-sideBarSectionHeader-background)", - "sidebar-header-fg": "var(--vscode-sideBarSectionHeader-foreground)", - "sidebar-header-border": "var(--vscode-sideBarSectionHeader-border)", - "sidebar-activitybartop-border": "var(--vscode-sideBarActivityBarTop-border)", - "sidebar-title-bg": "var(--vscode-sideBarTitle-background)", - "sidebar-title-border": "var(--vscode-sideBarTitle-border)", - "sidebar-stickyscroll-bg": "var(--vscode-sideBarStickyScroll-background)", - "sidebar-stickyscroll-border": "var(--vscode-sideBarStickyScroll-border)", - "sidebar-stickyscroll-shadow": "var(--vscode-sideBarStickyScroll-shadow)", + 'sidebar-bg': 'var(--vscode-sideBar-background)', + 'sidebar-fg': 'var(--vscode-sideBar-foreground)', + 'sidebar-border': 'var(--vscode-sideBar-border)', + 'sidebar-drop-bg': 'var(--vscode-sideBar-dropBackground)', + 'sidebar-title-fg': 'var(--vscode-sideBarTitle-foreground)', + 'sidebar-header-bg': 'var(--vscode-sideBarSectionHeader-background)', + 'sidebar-header-fg': 'var(--vscode-sideBarSectionHeader-foreground)', + 'sidebar-header-border': 'var(--vscode-sideBarSectionHeader-border)', + 'sidebar-activitybartop-border': 'var(--vscode-sideBarActivityBarTop-border)', + 'sidebar-title-bg': 'var(--vscode-sideBarTitle-background)', + 'sidebar-title-border': 'var(--vscode-sideBarTitle-border)', + 'sidebar-stickyscroll-bg': 'var(--vscode-sideBarStickyScroll-background)', + 'sidebar-stickyscroll-border': 'var(--vscode-sideBarStickyScroll-border)', + 'sidebar-stickyscroll-shadow': 'var(--vscode-sideBarStickyScroll-shadow)', // other colors (these are partially complete) // text formatting - "text-preformat-bg": "var(--vscode-textPreformat-background)", - "text-preformat-fg": "var(--vscode-textPreformat-foreground)", + 'text-preformat-bg': 'var(--vscode-textPreformat-background)', + 'text-preformat-fg': 'var(--vscode-textPreformat-foreground)', // editor colors - "editor-bg": "var(--vscode-editor-background)", - "editor-fg": "var(--vscode-editor-foreground)", + 'editor-bg': 'var(--vscode-editor-background)', + 'editor-fg': 'var(--vscode-editor-foreground)', // other - "editorwidget-bg": "var(--vscode-editorWidget-background)", - "toolbar-hover-bg": "var(--vscode-toolbar-hoverBackground)", - "toolbar-foreground": "var(--vscode-editorActionList-foreground)", + 'editorwidget-bg': 'var(--vscode-editorWidget-background)', + 'toolbar-hover-bg': 'var(--vscode-toolbar-hoverBackground)', + 'toolbar-foreground': 'var(--vscode-editorActionList-foreground)', - "editorwidget-fg": "var(--vscode-editorWidget-foreground)", - "editorwidget-border": "var(--vscode-editorWidget-border)", + 'editorwidget-fg': 'var(--vscode-editorWidget-foreground)', + 'editorwidget-border': 'var(--vscode-editorWidget-border)', - "charts-orange": "var(--vscode-charts-orange)", - "charts-yellow": "var(--vscode-charts-yellow)", + 'charts-orange': 'var(--vscode-charts-orange)', + 'charts-yellow': 'var(--vscode-charts-yellow)', }, }, }, diff --git a/src/vs/workbench/contrib/void/common/llmMessageTypes.ts b/src/vs/workbench/contrib/void/common/llmMessageTypes.ts index 74c2d067..0e55b080 100644 --- a/src/vs/workbench/contrib/void/common/llmMessageTypes.ts +++ b/src/vs/workbench/contrib/void/common/llmMessageTypes.ts @@ -3,8 +3,8 @@ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------*/ -import { ChatMessage } from '../browser/chatThreadService.js' -import { InternalToolInfo, ToolName } from '../browser/toolsService.js' +import type { ChatMessage } from '../browser/chatThreadService.js' +import type { InternalToolInfo, ToolName } from '../browser/toolsService.js' import { FeatureName, ProviderName, SettingsOfProvider } from './voidSettingsTypes.js' diff --git a/src/vs/workbench/contrib/void/common/metricsService.ts b/src/vs/workbench/contrib/void/common/metricsService.ts index d6892dd8..f51b64eb 100644 --- a/src/vs/workbench/contrib/void/common/metricsService.ts +++ b/src/vs/workbench/contrib/void/common/metricsService.ts @@ -3,12 +3,11 @@ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------*/ -import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +import { createDecorator, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { ProxyChannel } from '../../../../base/parts/ipc/common/ipc.js'; import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; import { IMainProcessService } from '../../../../platform/ipc/common/mainProcessService.js'; import { localize2 } from '../../../../nls.js'; -import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; import { registerAction2, Action2 } from '../../../../platform/actions/common/actions.js'; import { INotificationService } from '../../../../platform/notification/common/notification.js'; diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/preprocessLLMMessages.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/preprocessLLMMessages.ts index 755729ce..741e911f 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/preprocessLLMMessages.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/preprocessLLMMessages.ts @@ -271,7 +271,7 @@ const prepareMessages_tools = ({ messages, supportsTools }: { messages: LLMChatM return prepareMessages_tools_openai({ messages }) } else { - throw 1 + throw new Error(`supportsTools type not recognized`) } } From dc65016ecbb648f1d9d9a0c1539d50882812c2be Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 3 Mar 2025 22:11:21 -0800 Subject: [PATCH 07/41] clarity --- .../contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8e890fc2..128ea7f6 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 @@ -1261,7 +1261,7 @@ export const SidebarChat = () => { > { chatThreadsService.setFocusedMessageIdx(undefined) }} From c9cc6cc6a1011f1e50c933d13315ef62eb7819c2 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 3 Mar 2025 22:46:40 -0800 Subject: [PATCH 08/41] properly deal with VSCode chat --- .../browser/chatParticipant.contribution.ts | 162 +++++++++--------- src/vs/workbench/workbench.common.main.ts | 5 +- 2 files changed, 83 insertions(+), 84 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts index 3256369a..0d98223a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts @@ -4,23 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import { coalesce, isNonEmptyArray } from '../../../../base/common/arrays.js'; -import { Codicon } from '../../../../base/common/codicons.js'; import { toErrorMessage } from '../../../../base/common/errorMessage.js'; import { Event } from '../../../../base/common/event.js'; import { MarkdownString } from '../../../../base/common/htmlContent.js'; -import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { Disposable, DisposableMap, DisposableStore } from '../../../../base/common/lifecycle.js'; import * as strings from '../../../../base/common/strings.js'; -import { localize, localize2 } from '../../../../nls.js'; -import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { localize } from '../../../../nls.js'; +import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { ExtensionIdentifier, IExtensionManifest } from '../../../../platform/extensions/common/extensions.js'; import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; -import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContainer.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; -import { IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, ViewContainerLocation, Extensions as ViewExtensions } from '../../../common/views.js'; +import { IViewsRegistry, Extensions as ViewExtensions } from '../../../common/views.js'; import { IExtensionFeatureTableRenderer, IRenderedData, ITableData, IRowData, IExtensionFeaturesRegistry, Extensions } from '../../../services/extensionManagement/common/extensionFeatures.js'; import { isProposedApiEnabled } from '../../../services/extensions/common/extensions.js'; import * as extensionsRegistry from '../../../services/extensions/common/extensionsRegistry.js'; @@ -30,90 +27,91 @@ import { ChatAgentLocation, IChatAgentData, IChatAgentService } from '../common/ import { ChatContextKeys } from '../common/chatContextKeys.js'; import { IRawChatParticipantContribution } from '../common/chatParticipantContribTypes.js'; import { ChatViewId } from './chat.js'; -import { CHAT_EDITING_SIDEBAR_PANEL_ID, CHAT_SIDEBAR_PANEL_ID, ChatViewPane } from './chatViewPane.js'; // --- Chat Container & View Registration -const chatViewContainer: ViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ - id: CHAT_SIDEBAR_PANEL_ID, - title: localize2('chat.viewContainer.label', "Chat"), - icon: Codicon.commentDiscussion, - ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [CHAT_SIDEBAR_PANEL_ID, { mergeViewWithContainerWhenSingleView: true }]), - storageId: CHAT_SIDEBAR_PANEL_ID, - hideIfEmpty: true, - order: 100, -}, ViewContainerLocation.AuxiliaryBar, { isDefault: true, doNotRegisterOpenCommand: true }); +// Void commented this out +// const chatViewContainer: ViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ +// id: CHAT_SIDEBAR_PANEL_ID, +// title: localize2('chat.viewContainer.label', "Chat"), +// icon: Codicon.commentDiscussion, +// ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [CHAT_SIDEBAR_PANEL_ID, { mergeViewWithContainerWhenSingleView: true }]), +// storageId: CHAT_SIDEBAR_PANEL_ID, +// hideIfEmpty: true, +// order: 100, +// }, ViewContainerLocation.AuxiliaryBar, { isDefault: true, doNotRegisterOpenCommand: true }); -const chatViewDescriptor: IViewDescriptor[] = [{ - id: ChatViewId, - containerIcon: chatViewContainer.icon, - containerTitle: chatViewContainer.title.value, - singleViewPaneContainerTitle: chatViewContainer.title.value, - name: localize2('chat.viewContainer.label', "Chat"), - canToggleVisibility: false, - canMoveView: true, - openCommandActionDescriptor: { - id: CHAT_SIDEBAR_PANEL_ID, - title: chatViewContainer.title, - mnemonicTitle: localize({ key: 'miToggleChat', comment: ['&& denotes a mnemonic'] }, "&&Chat"), - keybindings: { - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyI, - mac: { - primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KeyI - } - }, - order: 1 - }, - ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ location: ChatAgentLocation.Panel }]), - when: ContextKeyExpr.or( - ChatContextKeys.Setup.hidden.negate(), - ChatContextKeys.Setup.installed, - ChatContextKeys.panelParticipantRegistered, - ChatContextKeys.extensionInvalid - ) -}]; -Registry.as(ViewExtensions.ViewsRegistry).registerViews(chatViewDescriptor, chatViewContainer); +// const chatViewDescriptor: IViewDescriptor[] = [{ +// id: ChatViewId, +// containerIcon: chatViewContainer.icon, +// containerTitle: chatViewContainer.title.value, +// singleViewPaneContainerTitle: chatViewContainer.title.value, +// name: localize2('chat.viewContainer.label', "Chat"), +// canToggleVisibility: false, +// canMoveView: true, +// openCommandActionDescriptor: { +// id: CHAT_SIDEBAR_PANEL_ID, +// title: chatViewContainer.title, +// mnemonicTitle: localize({ key: 'miToggleChat', comment: ['&& denotes a mnemonic'] }, "&&Chat"), +// keybindings: { +// primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyI, +// mac: { +// primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KeyI +// } +// }, +// order: 1 +// }, +// ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ location: ChatAgentLocation.Panel }]), +// when: ContextKeyExpr.or( +// ChatContextKeys.Setup.hidden.negate(), +// ChatContextKeys.Setup.installed, +// ChatContextKeys.panelParticipantRegistered, +// ChatContextKeys.extensionInvalid +// ) +// }]; +// Registry.as(ViewExtensions.ViewsRegistry).registerViews(chatViewDescriptor, chatViewContainer); // --- Edits Container & View Registration -const editsViewContainer: ViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ - id: CHAT_EDITING_SIDEBAR_PANEL_ID, - title: localize2('chatEditing.viewContainer.label', "Copilot Edits"), - icon: Codicon.editSession, - ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [CHAT_EDITING_SIDEBAR_PANEL_ID, { mergeViewWithContainerWhenSingleView: true }]), - storageId: CHAT_EDITING_SIDEBAR_PANEL_ID, - hideIfEmpty: true, - order: 101, -}, ViewContainerLocation.AuxiliaryBar, { doNotRegisterOpenCommand: true }); +// Void commented this out +// const editsViewContainer: ViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ +// id: CHAT_EDITING_SIDEBAR_PANEL_ID, +// title: localize2('chatEditing.viewContainer.label', "Copilot Edits"), +// icon: Codicon.editSession, +// ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [CHAT_EDITING_SIDEBAR_PANEL_ID, { mergeViewWithContainerWhenSingleView: true }]), +// storageId: CHAT_EDITING_SIDEBAR_PANEL_ID, +// hideIfEmpty: true, +// order: 101, +// }, ViewContainerLocation.AuxiliaryBar, { doNotRegisterOpenCommand: true }); -const editsViewDescriptor: IViewDescriptor[] = [{ - id: 'workbench.panel.chat.view.edits', - containerIcon: editsViewContainer.icon, - containerTitle: editsViewContainer.title.value, - singleViewPaneContainerTitle: editsViewContainer.title.value, - name: editsViewContainer.title, - canToggleVisibility: false, - canMoveView: true, - openCommandActionDescriptor: { - id: CHAT_EDITING_SIDEBAR_PANEL_ID, - title: editsViewContainer.title, - mnemonicTitle: localize({ key: 'miToggleEdits', comment: ['&& denotes a mnemonic'] }, "Copilot Ed&&its"), - keybindings: { - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyI, - linux: { - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.KeyI - } - }, - order: 2 - }, - ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ location: ChatAgentLocation.EditingSession }]), - when: ContextKeyExpr.or( - ChatContextKeys.Setup.hidden.negate(), - ChatContextKeys.Setup.installed, - ChatContextKeys.editingParticipantRegistered - ) -}]; -Registry.as(ViewExtensions.ViewsRegistry).registerViews(editsViewDescriptor, editsViewContainer); +// const editsViewDescriptor: IViewDescriptor[] = [{ +// id: 'workbench.panel.chat.view.edits', +// containerIcon: editsViewContainer.icon, +// containerTitle: editsViewContainer.title.value, +// singleViewPaneContainerTitle: editsViewContainer.title.value, +// name: editsViewContainer.title, +// canToggleVisibility: false, +// canMoveView: true, +// openCommandActionDescriptor: { +// id: CHAT_EDITING_SIDEBAR_PANEL_ID, +// title: editsViewContainer.title, +// mnemonicTitle: localize({ key: 'miToggleEdits', comment: ['&& denotes a mnemonic'] }, "Copilot Ed&&its"), +// keybindings: { +// primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyI, +// linux: { +// primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.KeyI +// } +// }, +// order: 2 +// }, +// ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ location: ChatAgentLocation.EditingSession }]), +// when: ContextKeyExpr.or( +// ChatContextKeys.Setup.hidden.negate(), +// ChatContextKeys.Setup.installed, +// ChatContextKeys.editingParticipantRegistered +// ) +// }]; +// Registry.as(ViewExtensions.ViewsRegistry).registerViews(editsViewDescriptor, editsViewContainer); const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'chatParticipants', diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index be9232ef..5cf7947e 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -192,8 +192,9 @@ import './contrib/notebook/browser/notebook.contribution.js'; import './contrib/speech/browser/speech.contribution.js'; // Chat -// import './contrib/chat/browser/chat.contribution.js'; // Void - remove vscode built-in chat -// import './contrib/inlineChat/browser/inlineChat.contribution.js'; +// Void - this is still registered to avoid console errors, we just commented it out in chatParticipant.contribution.ts +import './contrib/chat/browser/chat.contribution.js'; +import './contrib/inlineChat/browser/inlineChat.contribution.js'; // Interactive import './contrib/interactive/browser/interactive.contribution.js'; From 1259e71589eb66b39a176f200b8fb76279fb9214 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 3 Mar 2025 23:01:24 -0800 Subject: [PATCH 09/41] update prompt and add accept/reject ui --- .../contrib/void/browser/chatThreadService.ts | 7 +++++-- .../contrib/void/browser/prompt/prompts.ts | 5 +++-- .../browser/react/src/sidebar-tsx/SidebarChat.tsx | 14 ++++++++++---- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index 783cb4d4..8003a02e 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -186,6 +186,9 @@ export interface IChatThreadService { cancelStreaming(threadId: string): void; dismissStreamError(threadId: string): void; + approveTool(toolId: string): void; + rejectTool(toolId: string): void; + } export const IChatThreadService = createDecorator('voidChatThreadService'); @@ -452,7 +455,7 @@ class ChatThreadService extends Disposable implements IChatThreadService { // 3. call the tool let toolResult: ToolResultType[typeof toolName] try { - toolResult = this._toolsService.callTool[toolName](toolParams as any) // typescript is so bad it doesn't even couple the type of ToolResult with the type of the function being called here + toolResult = await this._toolsService.callTool[toolName](toolParams as any) // typescript is so bad it doesn't even couple the type of ToolResult with the type of the function being called here } catch (error) { const errorMessage = getErrorMessage(error) this._addMessageToThread(threadId, { role: 'tool', name: toolName, paramsStr: tool.paramsStr, id: tool.id, content: errorMessage, result: { type: 'error', value: errorMessage }, }) @@ -464,7 +467,7 @@ class ChatThreadService extends Disposable implements IChatThreadService { // 4. stringify the result to give the LLM let toolResultStr: string try { - toolResultStr = this._toolsService.stringOfResult[toolName](toolParams as any, toolResult as any) + toolResultStr = await this._toolsService.stringOfResult[toolName](toolParams as any, toolResult as any) } catch (error) { const errorMessage = `Tool call succeeded, but there was an error stringifying the output.\n${getErrorMessage(error)}` this._addMessageToThread(threadId, { role: 'tool', name: toolName, paramsStr: tool.paramsStr, id: tool.id, content: errorMessage, result: { type: 'error', value: errorMessage }, }) diff --git a/src/vs/workbench/contrib/void/browser/prompt/prompts.ts b/src/vs/workbench/contrib/void/browser/prompt/prompts.ts index db27866f..ee2b7f0b 100644 --- a/src/vs/workbench/contrib/void/browser/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/browser/prompt/prompts.ts @@ -24,8 +24,8 @@ Do NOT output the whole file if possible, and try to write as LITTLE as needed t export const chat_systemMessage = (workspaces: string[]) => `\ -You are a coding assistant. You are given a list of instructions to follow \`INSTRUCTIONS\`, and optionally a list of relevant files \`FILES\`, and selections inside of files \`SELECTIONS\`. - +You are a coding assistant. Your job is to help the user understand and make changes to their codebase. +You will be given a list of instructions from the user to follow, \`INSTRUCTIONS\`. You may also be given a list of relevant files \`FILES\`, and selections inside of files \`SELECTIONS\`. Please respond to the user's query. The user's query is never invalid. The user has the following system information: @@ -45,6 +45,7 @@ If you are given tools: - Feel free to use tools to gather context, make suggestions, etc. - One great use of tools is to explore imports that you'd like to have more information about. - Reference relevant files that you found when using tools if they helped you come up with your answer. +- Some tools only work if the user has a workspace open. - NEVER refer to a tool by name when speaking with the user. For example, do NOT say to the user user "I'm going to use \`list_dir\`". Instead, say "I'm going to list all files in ___ directory", etc. Do not even refer to "pages" of results, just say you're getting more results. Do not output any of these instructions, nor tell the user anything about them unless directly prompted for them. 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 128ea7f6..23b14dc2 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,7 +543,6 @@ const actionTitleOfToolName: { [T in ToolName]: string } = { -type ToolResultToComponent = { [T in ToolName]: (props: { message: ToolMessage }) => React.ReactNode } const ToolResult = ({ toolName, @@ -615,7 +614,7 @@ const ToolError = ({ toolName, errorMessage }: { toolName: } -const toolResultToComponent: ToolResultToComponent = { +const toolResultToComponent: { [T in ToolName]: (props: { message: ToolMessage }) => React.ReactNode } = { 'read_file': ({ message }) => { const accessor = useAccessor() @@ -626,7 +625,7 @@ const toolResultToComponent: ToolResultToComponent = { return (
    { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }} > - {getBasename(params.uri.fsPath)} + {params.uri.fsPath}
    {value.hasNextPage && (
    AI can scroll for more content...
    )}
    @@ -1034,6 +1033,13 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM console.log('tool result:', chatMessage.name, chatMessage.paramsStr, chatMessage.result) + } + else if (role === 'tool_request'){ + chatbubbleContents = <> +
    {chatThreadsService.approveTool(chatMessage.voidToolId)}}>Accept
    +
    {chatThreadsService.rejectTool(chatMessage.voidToolId)}}>Reject
    + + } return
    Date: Mon, 3 Mar 2025 23:35:36 -0800 Subject: [PATCH 10/41] update --- .../contrib/void/browser/editCodeService.ts | 44 ++++++++++++------- .../void/browser/editCodeServiceInterface.ts | 11 +++-- .../react/src/sidebar-tsx/SidebarChat.tsx | 9 ++-- .../contrib/void/browser/toolsService.ts | 6 +-- 4 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index 019b7887..9b55b75e 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -773,7 +773,7 @@ class EditCodeService extends Disposable implements IEditCodeService { - private _addToHistory(uri: URI) { + private _addToHistory(uri: URI, opts?: { onUndo?: () => void }) { const getCurrentSnapshot = (): HistorySnapshot => { const snapshottedDiffAreaOfId: Record = {} @@ -855,7 +855,7 @@ class EditCodeService extends Disposable implements IEditCodeService { resource: uri, label: 'Void Changes', code: 'undoredo.editCode', - undo: () => { restoreDiffAreas(beforeSnapshot) }, + undo: () => { restoreDiffAreas(beforeSnapshot); opts?.onUndo?.() }, redo: () => { if (afterSnapshot) restoreDiffAreas(afterSnapshot) } } this._undoRedoService.pushElement(elt) @@ -1223,7 +1223,7 @@ class EditCodeService extends Disposable implements IEditCodeService { private _initializeWriteoverStream(opts: StartApplyingOpts): DiffZone | undefined { - const { from } = opts + const { from, onFinalMessage: onFinalMessage_, onError: onError_, } = opts let startLine: number let endLine: number @@ -1268,7 +1268,9 @@ class EditCodeService extends Disposable implements IEditCodeService { // add to history - const { onFinishEdit } = this._addToHistory(uri) + const { onFinishEdit } = this._addToHistory(uri, { + onUndo: () => { onError_?.({ message: 'Edit was interrupted by pressing undo.', fullError: null }) } + }) // __TODO__ let users customize modelFimTags const quickEditFIMTags = defaultQuickEditFimTags @@ -1369,7 +1371,8 @@ class EditCodeService extends Disposable implements IEditCodeService { useProviderFor: opts.from === 'ClickApply' ? 'Apply' : 'Ctrl+K', logging: { loggingName: `startApplying - ${from}` }, messages, - onText: ({ fullText: fullText_ }) => { + onText: (params) => { + const { fullText: fullText_ } = params const newText_ = fullText_.substring(fullTextSoFar.length, Infinity) const newText = prevIgnoredSuffix + newText_ // add the previously ignored suffix because it's no longer the suffix! @@ -1383,7 +1386,8 @@ class EditCodeService extends Disposable implements IEditCodeService { prevIgnoredSuffix = croppedSuffix }, - onFinalMessage: ({ fullText }) => { + onFinalMessage: (params) => { + const { fullText } = params // console.log('DONE! FULL TEXT\n', extractText(fullText), diffZone.startLine, diffZone.endLine) // at the end, re-write whole thing to make sure no sync errors const [croppedText, _1, _2] = extractText(fullText, 0) @@ -1392,11 +1396,13 @@ class EditCodeService extends Disposable implements IEditCodeService { { shouldRealignDiffAreas: true } ) onDone() + onFinalMessage_?.() }, onError: (e) => { this._notifyError(e) onDone() this._undoHistory(uri) + onError_?.(e) }, }) @@ -1409,7 +1415,7 @@ class EditCodeService extends Disposable implements IEditCodeService { private _initializeSearchAndReplaceStream(opts: StartApplyingOpts & { from: 'ClickApply' }) { - const { applyStr, uri: givenURI, onText: onText_, onFinalMessage: onFinalMessage_, onError: onError_, } = opts + const { applyStr, uri: givenURI, onFinalMessage: onFinalMessage_, onError: onError_, } = opts let uri: URI if (givenURI === 'current') { @@ -1444,7 +1450,19 @@ class EditCodeService extends Disposable implements IEditCodeService { // can use this as a proxy to set the diffArea's stream state requestId let streamRequestIdRef: { current: string | null } = { current: null } - let { onFinishEdit } = this._addToHistory(uri) + const getOnFinishEdit = () => { + const { onFinishEdit } = this._addToHistory(uri, { + onUndo: () => { onError_?.({ message: 'Edit was interrupted by pressing undo.', fullError: null }) } + }) + return onFinishEdit + } + const revertAndContinueHistory = () => { + this._undoHistory(uri) + onFinishEdit = getOnFinishEdit() + } + + + let onFinishEdit = getOnFinishEdit() // TODO replace these with whatever block we're on initially if already started @@ -1474,12 +1492,6 @@ class EditCodeService extends Disposable implements IEditCodeService { this._onDidAddOrDeleteDiffZones.fire({ uri }) - const revertAndContinueHistory = () => { - this._undoHistory(uri) - const { onFinishEdit: onFinishEdit_ } = this._addToHistory(uri) - onFinishEdit = onFinishEdit_ - } - const convertOriginalRangeToFinalRange = (originalRange: readonly [number, number]): [number, number] => { // adjust based on the changes by computing line offset @@ -1638,8 +1650,6 @@ class EditCodeService extends Disposable implements IEditCodeService { } // end for this._refreshStylesAndDiffsInURI(uri) - - onText_?.(params) }, onFinalMessage: async (params) => { const { fullText } = params @@ -1679,7 +1689,7 @@ class EditCodeService extends Disposable implements IEditCodeService { onDone() - onFinalMessage_?.(params) + onFinalMessage_?.() }, onError: (e) => { this._notifyError(e) diff --git a/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts b/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts index 1d900ce7..2ea419be 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts @@ -7,12 +7,12 @@ import { Event } from '../../../../base/common/event.js'; import { URI } from '../../../../base/common/uri.js'; import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; -import { OnError, OnFinalMessage, OnText } from '../common/llmMessageTypes.js'; +import { OnError } from '../common/llmMessageTypes.js'; -export type StartApplyingOpts = { +export type StartApplyingOpts = ({ from: 'QuickEdit'; type: 'rewrite'; diffareaid: number; // id of the CtrlK area (contains text selection) @@ -21,11 +21,10 @@ export type StartApplyingOpts = { type: 'searchReplace' | 'rewrite'; applyStr: string; uri: 'current' | URI; - - onText?: OnText; - onFinalMessage?: OnFinalMessage; +}) & ({ + onFinalMessage?: () => void; onError?: OnError; -} +}) 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 23b14dc2..1feeee03 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 @@ -767,7 +767,6 @@ const toolResultToComponent: { [T in ToolName]: (props: { message: ToolMessage { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }} >
    -
    {chatThreadsService.approveTool(chatMessage.voidToolId)}}>Accept
    -
    {chatThreadsService.rejectTool(chatMessage.voidToolId)}}>Reject
    + {JSON.stringify(chatMessage.name, null, 2)}
    + {JSON.stringify(chatMessage.params, null, 2)} +
    { chatThreadsService.approveTool(chatMessage.voidToolId) }}>Accept
    +
    { chatThreadsService.rejectTool(chatMessage.voidToolId) }}>Reject
    } diff --git a/src/vs/workbench/contrib/void/browser/toolsService.ts b/src/vs/workbench/contrib/void/browser/toolsService.ts index b78d0ed5..4e7deb30 100644 --- a/src/vs/workbench/contrib/void/browser/toolsService.ts +++ b/src/vs/workbench/contrib/void/browser/toolsService.ts @@ -449,13 +449,13 @@ export class ToolsService implements IToolsService { }, edit: async ({ uri, changeDescription }) => { - const p = new Promise((res, rej) => { + const p = new Promise((res, rej) => { editCodeService.startApplying({ uri, applyStr: changeDescription, from: 'ClickApply', - type: 'rewrite', - onFinalMessage: (p) => { res(p) }, + type: 'searchReplace', + onFinalMessage: () => { res() }, onError: (e) => { throw new Error(e.message) }, }) }) From 5c5a44e4aca67934151b4ec027ae5a8e205c1e0e Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 3 Mar 2025 23:54:57 -0800 Subject: [PATCH 11/41] stop omitting system message --- .../llmMessage/preprocessLLMMessages.ts | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/preprocessLLMMessages.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/preprocessLLMMessages.ts index 741e911f..f3ba5e64 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/preprocessLLMMessages.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/preprocessLLMMessages.ts @@ -17,6 +17,9 @@ export const parseObject = (args: unknown) => { const prepareMessages_normalize = ({ messages: messages_ }: { messages: LLMChatMessage[] }) => { const messages = deepClone(messages_) const newMessages: LLMChatMessage[] = [] + if (messages.length >= 0) newMessages.push(messages[0]) + + // remove duplicate roles for (let i = 1; i < messages.length; i += 1) { const curr = messages[i] const prev = messages[i - 1] @@ -65,6 +68,7 @@ const prepareMessages_systemMessage = ({ // } + // if it has a system message (if doesn't, we obviously don't care about whether it supports system message or not...) if (systemMessageStr) { // if supports system message if (supportsSystemMessage) { @@ -77,25 +81,18 @@ const prepareMessages_systemMessage = ({ } // if does not support system message else { - if (supportsSystemMessage) { - if (newMessages.length === 0) - newMessages.push({ role: 'user', content: systemMessageStr }) - // add system mesasges to first message (should be a user message) - else { - const newFirstMessage = { - role: 'user', - content: ('' - + '\n' - + systemMessageStr - + '\n' - + '\n' - + newMessages[0].content - ) - } as const - newMessages.splice(0, 1) // delete first message - newMessages.unshift(newFirstMessage) // add new first message - } - } + const newFirstMessage = { + role: 'user', + content: ('' + + '\n' + + systemMessageStr + + '\n' + + '\n' + + newMessages[0].content + ) + } as const + newMessages.splice(0, 1) // delete first message + newMessages.unshift(newFirstMessage) // add new first message } } From 7f278aafcb26ae0454941a95a1f37873e2607437 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Tue, 4 Mar 2025 01:18:10 -0800 Subject: [PATCH 12/41] fix SEARCH/REPLACE detection --- .../void/browser/autocompleteService.ts | 7 +++++- .../contrib/void/browser/editCodeService.ts | 3 +++ .../browser/helpers/extractCodeFromResult.ts | 22 +++++++------------ .../contrib/void/common/voidFileService.ts | 21 +++--------------- 4 files changed, 20 insertions(+), 33 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/autocompleteService.ts b/src/vs/workbench/contrib/void/browser/autocompleteService.ts index f9cbec7b..40746fe9 100644 --- a/src/vs/workbench/contrib/void/browser/autocompleteService.ts +++ b/src/vs/workbench/contrib/void/browser/autocompleteService.ts @@ -18,9 +18,14 @@ import { IModelService } from '../../../../editor/common/services/model.js'; import { extractCodeFromRegular } from './helpers/extractCodeFromResult.js'; import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; import { ILLMMessageService } from '../common/llmMessageService.js'; -import { _ln, allLinebreakSymbols } from '../common/voidFileService.js'; +import { isWindows } from '../../../../base/common/platform.js'; // import { IContextGatheringService } from './contextGatheringService.js'; + + +const allLinebreakSymbols = ['\r\n', '\n'] +const _ln = isWindows ? allLinebreakSymbols[0] : allLinebreakSymbols[1] + // The extension this was called from is here - https://github.com/voideditor/void/blob/autocomplete/extensions/void/src/extension/extension.ts diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index 9b55b75e..e8637609 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -1588,6 +1588,7 @@ class EditCodeService extends Disposable implements IEditCodeService { // if error if (typeof originalBounds === 'string') { + console.error('Error in originalBounds, retrying.', originalBounds) messages.push( { role: 'assistant', content: fullText }, // latest output { role: 'user', content: errMsgOfInvalidStr(originalBounds) } // user explanation of what's wrong @@ -1663,6 +1664,8 @@ class EditCodeService extends Disposable implements IEditCodeService { this._notificationService.info(`Void: We ran Apply, but the LLM didn't output any changes.`) } + await new Promise(resolve => setTimeout(resolve, 500)) + // writeover the whole file let newCode = originalFileCode for (let blockNum = addedTrackingZoneOfBlockNum.length - 1; blockNum >= 0; blockNum -= 1) { diff --git a/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts b/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts index e24bc232..5a492cd2 100644 --- a/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts +++ b/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts @@ -182,10 +182,9 @@ const endsWithAnyPrefixOf = (str: string, anyPrefix: string) => { // guarantees if you keep adding text, array length will strictly grow and state will progress without going back export const extractSearchReplaceBlocks = (str: string) => { - const ORIGINAL_ = ORIGINAL + `\n` - const DIVIDER_ = '\n' + DIVIDER + `\n` - // logic for FINAL_ is slightly more complicated - should be '\n' + FINAL, but that ignores if the final output is empty + const DIVIDER_ = DIVIDER + `\n` + // we only check FINAL, we don't care about FINAL + '\n' because the string is over const blocks: ExtractedSearchReplaceBlock[] = [] @@ -196,7 +195,7 @@ export const extractSearchReplaceBlocks = (str: string) => { if (origStart === -1) { return blocks } origStart += ORIGINAL_.length i = origStart - // wrote <<<< ORIGINAL + // wrote <<<< ORIGINAL\n let dividerStart = str.indexOf(DIVIDER_, i) if (dividerStart === -1) { // if didnt find DIVIDER_, either writing originalStr or DIVIDER_ right now @@ -211,17 +210,13 @@ export const extractSearchReplaceBlocks = (str: string) => { const origStrDone = str.substring(origStart, dividerStart) dividerStart += DIVIDER_.length i = dividerStart - // wrote ===== + // wrote \n=====\n - const finalStartA = str.indexOf(FINAL, i) - const finalStartB = str.indexOf('\n' + FINAL, i) // go with B if possible, else fallback to A, it's more permissive - const FINAL_ = finalStartB !== -1 ? '\n' + FINAL : FINAL - let finalStart = finalStartB !== -1 ? finalStartB : finalStartA - - if (finalStart === -1) { // if didnt find FINAL_, either writing finalStr or FINAL_ right now - const isWritingFINAL = endsWithAnyPrefixOf(str, FINAL_) + const finalStart = str.indexOf(FINAL, i) + if (finalStart === -1) { // if didnt find FINAL, either writing finalStr or FINAL right now + const isWritingFINAL = endsWithAnyPrefixOf(str, FINAL) blocks.push({ orig: origStrDone, final: str.substring(dividerStart, str.length - (isWritingFINAL?.length ?? 0)), @@ -230,8 +225,7 @@ export const extractSearchReplaceBlocks = (str: string) => { return blocks } const finalStrDone = str.substring(dividerStart, finalStart) - finalStart += FINAL_.length - i = finalStart + i = finalStart + FINAL.length // wrote >>>>> FINAL blocks.push({ diff --git a/src/vs/workbench/contrib/void/common/voidFileService.ts b/src/vs/workbench/contrib/void/common/voidFileService.ts index a7c25631..cebad454 100644 --- a/src/vs/workbench/contrib/void/common/voidFileService.ts +++ b/src/vs/workbench/contrib/void/common/voidFileService.ts @@ -3,7 +3,6 @@ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------*/ -import { isWindows } from '../../../../base/common/platform.js'; import { URI } from '../../../../base/common/uri.js'; import { EndOfLinePreference } from '../../../../editor/common/model.js'; import { IModelService } from '../../../../editor/common/services/model.js'; @@ -11,11 +10,6 @@ import { IFileService } from '../../../../platform/files/common/files.js'; import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; - -// linebreak symbols -export const allLinebreakSymbols = ['\r\n', '\n'] -export const _ln = isWindows ? allLinebreakSymbols[0] : allLinebreakSymbols[1] - export interface IVoidFileService { readonly _serviceBrand: undefined; @@ -52,19 +46,10 @@ export class VoidFileService implements IVoidFileService { _readFileRaw = async (uri: URI, range?: { startLineNumber: number, endLineNumber: number }): Promise => { try { // this throws an error if no file exists (eg it was deleted) - const res = await this.fileService.readFile(uri); - - if (range) { - return res.value.toString() - .split(_ln) - .slice(range.startLineNumber - 1, range.endLineNumber) - .join(_ln) - } - - return res.value.toString(); - - + const str = res.value.toString().replace(/\r\n/g, '\n'); // even if not on Windows, might read a file with \r\n + if (range) return str.split('\n').slice(range.startLineNumber - 1, range.endLineNumber).join('\n') + return str; } catch (e) { return null; } From 866547df79e17e3755a24faa113cc6aee9207d87 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Tue, 4 Mar 2025 02:24:25 -0800 Subject: [PATCH 13/41] fix extractCodeFromResult Search/Replace --- .../browser/helpers/extractCodeFromResult.ts | 239 +++++++++++++++++- 1 file changed, 229 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts b/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts index 5a492cd2..045c09da 100644 --- a/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts +++ b/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts @@ -182,10 +182,10 @@ const endsWithAnyPrefixOf = (str: string, anyPrefix: string) => { // guarantees if you keep adding text, array length will strictly grow and state will progress without going back export const extractSearchReplaceBlocks = (str: string) => { - const ORIGINAL_ = ORIGINAL + `\n` - const DIVIDER_ = DIVIDER + `\n` - // we only check FINAL, we don't care about FINAL + '\n' because the string is over + const ORIGINAL_ = ORIGINAL + `\n` + const DIVIDER_ = '\n' + DIVIDER + `\n` + // logic for FINAL_ is slightly more complicated - should be '\n' + FINAL, but that ignores if the final output is empty const blocks: ExtractedSearchReplaceBlock[] = [] @@ -212,11 +212,15 @@ export const extractSearchReplaceBlocks = (str: string) => { i = dividerStart // wrote \n=====\n + const finalStartA = str.indexOf(FINAL, i) + const finalStartB = str.indexOf('\n' + FINAL, i) // go with B if possible, else fallback to A, it's more permissive + const isFINAL_ = finalStartB !== -1 && finalStartA === finalStartB // this logic is really important, otherwise we might look for FINAL_ at a much later part of the string + const usingFINAL = isFINAL_ ? '\n' + FINAL : FINAL + let finalStart = isFINAL_ ? finalStartB : finalStartA - const finalStart = str.indexOf(FINAL, i) - if (finalStart === -1) { // if didnt find FINAL, either writing finalStr or FINAL right now - const isWritingFINAL = endsWithAnyPrefixOf(str, FINAL) + if (finalStart === -1) { // if didnt find FINAL_, either writing finalStr or FINAL_ right now + const isWritingFINAL = endsWithAnyPrefixOf(str, usingFINAL) blocks.push({ orig: origStrDone, final: str.substring(dividerStart, str.length - (isWritingFINAL?.length ?? 0)), @@ -225,7 +229,8 @@ export const extractSearchReplaceBlocks = (str: string) => { return blocks } const finalStrDone = str.substring(dividerStart, finalStart) - i = finalStart + FINAL.length + finalStart += usingFINAL.length + i = finalStart // wrote >>>>> FINAL blocks.push({ @@ -241,9 +246,6 @@ export const extractSearchReplaceBlocks = (str: string) => { - - - // could simplify this - this assumes we can never add a tag without committing it to the user's screen, but that's not true export const extractReasoningOnTextWrapper = (onText: OnText, thinkTags: [string, string]): OnText => { let latestAddIdx = 0 // exclusive index in fullText_ @@ -351,3 +353,220 @@ export const extractReasoningOnFinalMessage = (fullText_: string, thinkTags: [st const fullText = fullText_.substring(0, tag1Idx) + fullText_.substring(tag2Idx + thinkTags[1].length, Infinity) return { fullText, fullReasoning } } + + + + + + + + + + + + + + + + + + +// const tests: [string, { shape: Partial[] }][] = [[ +// `\ +// \`\`\` +// <<<<<<< ORIGINA`, { shape: [] } +// ], [ +// `\ +// \`\`\` +// <<<<<<< ORIGINAL`, { shape: [], } +// ], [ +// `\ +// \`\`\` +// <<<<<<< ORIGINAL +// A`, { shape: [{ state: 'writingOriginal', orig: 'A' }], } +// ], [ +// `\ +// \`\`\` +// <<<<<<< ORIGINAL +// A +// B`, { shape: [{ state: 'writingOriginal', orig: 'A\nB' }], } +// ], [ +// `\ +// \`\`\` +// <<<<<<< ORIGINAL +// A +// B +// `, { shape: [{ state: 'writingOriginal', orig: 'A\nB' }], } +// ], [ +// `\ +// \`\`\` +// <<<<<<< ORIGINAL +// A +// B +// ===`, { shape: [{ state: 'writingOriginal', orig: 'A\nB' }], } +// ], [ +// `\ +// \`\`\` +// <<<<<<< ORIGINAL +// A +// B +// ======`, { shape: [{ state: 'writingOriginal', orig: 'A\nB' }], } +// ], [ +// `\ +// \`\`\` +// <<<<<<< ORIGINAL +// A +// B +// =======`, { shape: [{ state: 'writingOriginal', orig: 'A\nB' }], } +// ], [ +// `\ +// \`\`\` +// <<<<<<< ORIGINAL +// A +// B +// ======= +// `, { shape: [{ state: 'writingFinal', orig: 'A\nB', final: '' }], } +// ], [ +// `\ +// \`\`\` +// <<<<<<< ORIGINAL +// A +// B +// ======= +// >>>>>>> UPDAT`, { shape: [{ state: 'writingFinal', orig: 'A\nB', final: '' }], } +// ], [ +// `\ +// \`\`\` +// <<<<<<< ORIGINAL +// A +// B +// ======= +// >>>>>>> UPDATED`, { shape: [{ state: 'done', orig: 'A\nB', final: '' }], } +// ], [ +// `\ +// \`\`\` +// <<<<<<< ORIGINAL +// A +// B +// ======= +// >>>>>>> UPDATED +// \`\`\``, { shape: [{ state: 'done', orig: 'A\nB', final: '' }], } +// ], + + +// // alternatively +// [ +// `\ +// \`\`\` +// <<<<<<< ORIGINAL +// A +// B +// ======= +// X`, { shape: [{ state: 'writingFinal', orig: 'A\nB', final: 'X' }], } +// ], +// [ +// `\ +// \`\`\` +// <<<<<<< ORIGINAL +// A +// B +// ======= +// X +// Y`, { shape: [{ state: 'writingFinal', orig: 'A\nB', final: 'X\nY' }], } +// ], +// [ +// `\ +// \`\`\` +// <<<<<<< ORIGINAL +// A +// B +// ======= +// X +// Y +// `, { shape: [{ state: 'writingFinal', orig: 'A\nB', final: 'X\nY' }], } +// ], +// [ +// `\ +// \`\`\` +// <<<<<<< ORIGINAL +// A +// B +// ======= +// X +// Y +// >>>>>>> FINA`, { shape: [{ state: 'writingFinal', orig: 'A\nB', final: 'X\nY' }], } +// ], [ +// `\ +// \`\`\` +// <<<<<<< ORIGINAL +// A +// B +// ======= +// X +// Y +// >>>>>>> UPDATED`, { shape: [{ state: 'done', orig: 'A\nB', final: 'X\nY' }], } +// ], [ +// `\ +// \`\`\` +// <<<<<<< ORIGINAL +// A +// B +// ======= +// X +// Y +// >>>>>>> UPDATED +// \`\`\``, { shape: [{ state: 'done', orig: 'A\nB', final: 'X\nY' }], } +// ]] + + + + +// function runTests() { + + +// let passedTests = 0; +// let failedTests = 0; + +// for (let i = 0; i < tests.length; i++) { +// const [input, expected] = tests[i]; +// const result = extractSearchReplaceBlocks(input); + +// // Compare result with expected shape +// let passed = true; +// if (result.length !== expected.shape.length) { +// passed = false; +// } else { +// for (let j = 0; j < result.length; j++) { // block +// const expectedItem = expected.shape[j]; +// const resultItem = result[j]; + +// if ((expectedItem.state !== undefined) && (expectedItem.state !== resultItem.state) || +// (expectedItem.orig !== undefined) && (expectedItem.orig !== resultItem.orig) || +// (expectedItem.final !== undefined) && (expectedItem.final !== resultItem.final)) { +// passed = false; +// break; +// } +// } +// } + +// if (passed) { +// passedTests++; +// console.log(`Test ${i + 1} passed`); +// } else { +// failedTests++; +// console.log(`Test ${i + 1} failed`); +// console.log('Input:', input) +// console.log(`Expected:`, expected.shape); +// console.log(`Got:`, result); +// } +// } + +// console.log(`Total: ${tests.length}, Passed: ${passedTests}, Failed: ${failedTests}`); +// return failedTests === 0; +// } + + + +// runTests() + + From 4ba735237478655452f91e48621f9f2350a174f9 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Tue, 4 Mar 2025 03:33:44 -0800 Subject: [PATCH 14/41] fix substr logic + nuanced FINAL_ logic --- .../browser/helpers/extractCodeFromResult.ts | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts b/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts index 045c09da..ab722209 100644 --- a/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts +++ b/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts @@ -171,6 +171,9 @@ export type ExtractedSearchReplaceBlock = { } +// JS substring swaps indices, so "ab".substr(1,0) will NOT be '', it will be 'a'! +const voidSubstr = (str: string, start: number, end: number) => end < start ? '' : str.substring(start, end) + const endsWithAnyPrefixOf = (str: string, anyPrefix: string) => { // for each prefix for (let i = anyPrefix.length; i >= 1; i--) { // i >= 1 because must not be empty string @@ -199,36 +202,37 @@ export const extractSearchReplaceBlocks = (str: string) => { let dividerStart = str.indexOf(DIVIDER_, i) if (dividerStart === -1) { // if didnt find DIVIDER_, either writing originalStr or DIVIDER_ right now - const isWritingDIVIDER = endsWithAnyPrefixOf(str, DIVIDER_) + const writingDIVIDERlen = endsWithAnyPrefixOf(str, DIVIDER_)?.length ?? 0 blocks.push({ - orig: str.substring(origStart, str.length - (isWritingDIVIDER?.length ?? 0)), + orig: voidSubstr(str, origStart, str.length - writingDIVIDERlen), final: '', state: 'writingOriginal' }) return blocks } - const origStrDone = str.substring(origStart, dividerStart) + const origStrDone = voidSubstr(str, origStart, dividerStart) dividerStart += DIVIDER_.length i = dividerStart // wrote \n=====\n - const finalStartA = str.indexOf(FINAL, i) - const finalStartB = str.indexOf('\n' + FINAL, i) // go with B if possible, else fallback to A, it's more permissive - const isFINAL_ = finalStartB !== -1 && finalStartA === finalStartB // this logic is really important, otherwise we might look for FINAL_ at a much later part of the string - const usingFINAL = isFINAL_ ? '\n' + FINAL : FINAL + const fullFINALStart = str.indexOf(FINAL, i) + const fullFINALStart_ = str.indexOf('\n' + FINAL, i) // go with B if possible, else fallback to A, it's more permissive + const matchedFullFINAL_ = fullFINALStart_ !== -1 && fullFINALStart === fullFINALStart_ + 1 // this logic is really important, otherwise we might look for FINAL_ at a much later part of the string - let finalStart = isFINAL_ ? finalStartB : finalStartA - - if (finalStart === -1) { // if didnt find FINAL_, either writing finalStr or FINAL_ right now - const isWritingFINAL = endsWithAnyPrefixOf(str, usingFINAL) + let finalStart = matchedFullFINAL_ ? fullFINALStart_ : fullFINALStart + if (finalStart === -1) { // if didnt find FINAL_, either writing finalStr or FINAL or FINAL_ right now + const writingFINALlen = endsWithAnyPrefixOf(str, FINAL)?.length ?? 0 + const writingFINALlen_ = endsWithAnyPrefixOf(str, '\n' + FINAL)?.length ?? 0 // this gets priority + const usingWritingFINALlen = Math.max(writingFINALlen, writingFINALlen_) blocks.push({ orig: origStrDone, - final: str.substring(dividerStart, str.length - (isWritingFINAL?.length ?? 0)), + final: voidSubstr(str, dividerStart, str.length - usingWritingFINALlen), state: 'writingFinal' }) return blocks } - const finalStrDone = str.substring(dividerStart, finalStart) + const usingFINAL = matchedFullFINAL_ ? '\n' + FINAL : FINAL + const finalStrDone = voidSubstr(str, dividerStart, finalStart) finalStart += usingFINAL.length i = finalStart // wrote >>>>> FINAL @@ -494,7 +498,7 @@ export const extractReasoningOnFinalMessage = (fullText_: string, thinkTags: [st // ======= // X // Y -// >>>>>>> FINA`, { shape: [{ state: 'writingFinal', orig: 'A\nB', final: 'X\nY' }], } +// >>>>>>> UPDAT`, { shape: [{ state: 'writingFinal', orig: 'A\nB', final: 'X\nY' }], } // ], [ // `\ // \`\`\` From 027c0ee6bd80ea129ce118a15ee02282923ba6f6 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Tue, 4 Mar 2025 05:20:23 -0800 Subject: [PATCH 15/41] the retry loop works! --- .../contrib/void/browser/editCodeService.ts | 333 ++++++++++-------- .../src/markdown/ApplyBlockHoverButtons.tsx | 4 - 2 files changed, 178 insertions(+), 159 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index e8637609..4426b68b 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -1450,19 +1450,9 @@ class EditCodeService extends Disposable implements IEditCodeService { // can use this as a proxy to set the diffArea's stream state requestId let streamRequestIdRef: { current: string | null } = { current: null } - const getOnFinishEdit = () => { - const { onFinishEdit } = this._addToHistory(uri, { - onUndo: () => { onError_?.({ message: 'Edit was interrupted by pressing undo.', fullError: null }) } - }) - return onFinishEdit - } - const revertAndContinueHistory = () => { - this._undoHistory(uri) - onFinishEdit = getOnFinishEdit() - } - - - let onFinishEdit = getOnFinishEdit() + const { onFinishEdit } = this._addToHistory(uri, { + onUndo: () => { onError_?.({ message: 'Edit was interrupted by pressing undo.', fullError: null }) } + }) // TODO replace these with whatever block we're on initially if already started @@ -1471,7 +1461,6 @@ class EditCodeService extends Disposable implements IEditCodeService { originalCode: string, } - const addedTrackingZoneOfBlockNum: TrackingZone[] = [] const adding: Omit = { type: 'DiffZone', @@ -1511,12 +1500,12 @@ class EditCodeService extends Disposable implements IEditCodeService { } - const errMsgOfInvalidStr = (str: string & ReturnType) => { - return str === 'Not found' ? - 'I interrupted you because the latest ORIGINAL code could not be found in the file. Please output all SEARCH/REPLACE blocks again, making sure the code in ORIGINAL is identical to a code snippet in the file.' - : str === 'Not unique' ? - 'I interrupted you because the latest ORIGINAL code shows up multiple times in the file. Please output all SEARCH/REPLACE blocks again, making sure the code in each ORIGINAL section is unique in the file.' - : '' + const errMsgOfInvalidStr = (str: string & ReturnType, blockOrig: string) => { + return str === `Not found` ? + `The ORIGINAL code provided could not be found in the file. Please output all SEARCH/REPLACE blocks again, making sure the code in ORIGINAL is identical to a code snippet in the file. The ORIGINAL code provided: ${JSON.stringify(blockOrig)}` + : str === `Not unique` ? + `The ORIGINAL code provided shows up multiple times in the file. Please output all SEARCH/REPLACE blocks again, making sure the code in each ORIGINAL section is unique in the file. The ORIGINAL code provided: ${JSON.stringify(blockOrig)}` + : `` } @@ -1535,176 +1524,210 @@ class EditCodeService extends Disposable implements IEditCodeService { // refresh now in case onText takes a while to get 1st message this._refreshStylesAndDiffsInURI(uri) + // stateful + const addedTrackingZoneOfBlockNum: TrackingZone[] = [] + // stream style related let latestStreamLocationMutable: StreamLocationMutable | null = null let shouldUpdateOrigStreamStyle = true let oldBlocks: ExtractedSearchReplaceBlock[] = [] - // this generates >>>>>>> ORIGINAL <<<<<<< REPLACE blocks and and simultaneously applies it - let shouldSendAnotherMessage = true - let nMessagesSent = 0 - let currStreamingBlockNum = 0 - while (shouldSendAnotherMessage) { - shouldSendAnotherMessage = false - nMessagesSent += 1 + const retryLoop = async () => { + // this generates >>>>>>> ORIGINAL <<<<<<< REPLACE blocks and and simultaneously applies it + let shouldSendAnotherMessage = true + let nMessagesSent = 0 + let currStreamingBlockNum = 0 + while (shouldSendAnotherMessage) { + shouldSendAnotherMessage = false + nMessagesSent += 1 - streamRequestIdRef.current = this._llmMessageService.sendLLMMessage({ - messagesType: 'chatMessages', - useProviderFor: 'Apply', - logging: { loggingName: `generateSearchAndReplace` }, - messages, - onText: (params) => { - const { fullText } = params - // blocks are [done done done ... {writingFinal|writingOriginal}] - // ^ - // currStreamingBlockNum + let res: () => void = () => { } + const awaitable = new Promise((res_) => { res = res_ }) - const blocks = extractSearchReplaceBlocks(fullText) + streamRequestIdRef.current = this._llmMessageService.sendLLMMessage({ + messagesType: 'chatMessages', + useProviderFor: 'Apply', + logging: { loggingName: `generateSearchAndReplace` }, + messages, + onText: (params) => { + const { fullText } = params + // blocks are [done done done ... {writingFinal|writingOriginal}] + // ^ + // currStreamingBlockNum - for (let blockNum = currStreamingBlockNum; blockNum < blocks.length; blockNum += 1) { - const block = blocks[blockNum] + const blocks = extractSearchReplaceBlocks(fullText) - if (block.state === 'writingOriginal') { - // update stream state to the first line of original if some portion of original has been written - if (shouldUpdateOrigStreamStyle && block.orig.trim().length >= 20) { - const startingAtLine = diffZone._streamState.line ?? 1 // dont go backwards if already have a stream line - const originalRange = findTextInCode(block.orig, originalFileCode, startingAtLine) - if (typeof originalRange !== 'string') { - const [startLine, _] = convertOriginalRangeToFinalRange(originalRange) - diffZone._streamState.line = startLine - shouldUpdateOrigStreamStyle = false + for (let blockNum = currStreamingBlockNum; blockNum < blocks.length; blockNum += 1) { + const block = blocks[blockNum] + + if (block.state === 'writingOriginal') { + // update stream state to the first line of original if some portion of original has been written + if (shouldUpdateOrigStreamStyle && block.orig.trim().length >= 20) { + const startingAtLine = diffZone._streamState.line ?? 1 // dont go backwards if already have a stream line + const originalRange = findTextInCode(block.orig, originalFileCode, startingAtLine) + if (typeof originalRange !== 'string') { + const [startLine, _] = convertOriginalRangeToFinalRange(originalRange) + diffZone._streamState.line = startLine + shouldUpdateOrigStreamStyle = false + } } + // must be done writing original to move on to writing streamed content + continue } - // must be done writing original to move on to writing streamed content - continue - } - shouldUpdateOrigStreamStyle = true + shouldUpdateOrigStreamStyle = true - // if this is the first time we're seeing this block, add it as a diffarea so we can start streaming - if (!(blockNum in addedTrackingZoneOfBlockNum)) { - const originalBounds = findTextInCode(block.orig, originalFileCode) + // if this is the first time we're seeing this block, add it as a diffarea so we can start streaming + if (!(blockNum in addedTrackingZoneOfBlockNum)) { + const originalBounds = findTextInCode(block.orig, originalFileCode) - // if error - if (typeof originalBounds === 'string') { - console.error('Error in originalBounds, retrying.', originalBounds) - messages.push( - { role: 'assistant', content: fullText }, // latest output - { role: 'user', content: errMsgOfInvalidStr(originalBounds) } // user explanation of what's wrong + // if error + if (typeof originalBounds === 'string') { + const content = errMsgOfInvalidStr(originalBounds, block.orig) + console.log('Content', content) + messages.push( + { role: 'assistant', content: fullText }, // latest output + { role: 'user', content: content } // user explanation of what's wrong + ) + if (streamRequestIdRef.current) this._llmMessageService.abort(streamRequestIdRef.current) + + // REVERT + const numLines = this._getNumLines(uri) + if (numLines !== null) this._writeText(uri, originalFileCode, + { startLineNumber: 1, startColumn: 1, endLineNumber: numLines, endColumn: Number.MAX_SAFE_INTEGER }, + { shouldRealignDiffAreas: false } + ) + // reset state + diffZone.startLine = 1 + diffZone.endLine = numLines ?? 1 + if (diffZone._streamState.isStreaming) { + diffZone._streamState.line = 1 + } + + currStreamingBlockNum = 0 + latestStreamLocationMutable = null + shouldUpdateOrigStreamStyle = true + oldBlocks = [] + addedTrackingZoneOfBlockNum.slice(0, Infinity) // clear the array + + shouldSendAnotherMessage = true + this._refreshStylesAndDiffsInURI(uri) + res() + return + } + + const [startLine, endLine] = convertOriginalRangeToFinalRange(originalBounds) + + // otherwise if no error, add the position as a diffarea + const adding: Omit, 'diffareaid'> = { + type: 'TrackingZone', + startLine: startLine, + endLine: endLine, + _URI: uri, + metadata: { + originalBounds: [...originalBounds], + originalCode: block.orig, + }, + } + const trackingZone = this._addDiffArea(adding) + addedTrackingZoneOfBlockNum.push(trackingZone) + latestStreamLocationMutable = { line: startLine, addedSplitYet: false, col: 1, originalCodeStartLine: 1 } + } // <-- done adding diffarea + + + // should always be in streaming state here + if (!diffZone._streamState.isStreaming) { + console.error('DiffZone was not in streaming state in _initializeSearchAndReplaceStream') + continue + } + if (!latestStreamLocationMutable) continue + + // if a block is done, finish it by writing all + if (block.state === 'done') { + const { startLine: finalStartLine, endLine: finalEndLine } = addedTrackingZoneOfBlockNum[blockNum] + this._writeText(uri, block.final, + { startLineNumber: finalStartLine, startColumn: 1, endLineNumber: finalEndLine, endColumn: Number.MAX_SAFE_INTEGER }, // 1-indexed + { shouldRealignDiffAreas: true } ) - if (streamRequestIdRef.current) this._llmMessageService.abort(streamRequestIdRef.current) - shouldSendAnotherMessage = true - revertAndContinueHistory() + diffZone._streamState.line = finalEndLine + 1 + currStreamingBlockNum = blockNum + 1 continue } - const [startLine, endLine] = convertOriginalRangeToFinalRange(originalBounds) + // write the added text to the file + const deltaFinalText = block.final.substring((oldBlocks[blockNum]?.final ?? '').length, Infinity) + this._writeStreamedDiffZoneLLMText(uri, block.orig, block.final, deltaFinalText, latestStreamLocationMutable) + oldBlocks = blocks // oldblocks is only used if writingFinal - // otherwise if no error, add the position as a diffarea - const adding: Omit, 'diffareaid'> = { - type: 'TrackingZone', - startLine: startLine, - endLine: endLine, - _URI: uri, - metadata: { - originalBounds: [...originalBounds], - originalCode: block.orig, - }, - } - const trackingZone = this._addDiffArea(adding) - addedTrackingZoneOfBlockNum.push(trackingZone) - latestStreamLocationMutable = { line: startLine, addedSplitYet: false, col: 1, originalCodeStartLine: 1 } - } // <-- done adding diffarea + // const { endLine: currentEndLine } = addedTrackingZoneOfBlockNum[blockNum] // would be bad to do this because a lot of the bottom lines might be the same. more accurate to go with latestStreamLocationMutable + // diffZone._streamState.line = currentEndLine + diffZone._streamState.line = latestStreamLocationMutable.line - // should always be in streaming state here - if (!diffZone._streamState.isStreaming) { - console.error('DiffZone was not in streaming state in _initializeSearchAndReplaceStream') - continue + + } // end for + + this._refreshStylesAndDiffsInURI(uri) + }, + onFinalMessage: async (params) => { + const { fullText } = params + console.log('final message!!', fullText) + + // 1. wait 500ms and fix lint errors - call lint error workflow + // (update react state to say "Fixing errors") + const blocks = extractSearchReplaceBlocks(fullText) + + if (blocks.length === 0) { + this._notificationService.info(`Void: We ran Apply, but the LLM didn't output any changes.`) } - if (!latestStreamLocationMutable) continue + // writeover the whole file + let newCode = originalFileCode + for (let blockNum = addedTrackingZoneOfBlockNum.length - 1; blockNum >= 0; blockNum -= 1) { + const { originalBounds } = addedTrackingZoneOfBlockNum[blockNum].metadata + const finalCode = blocks[blockNum].final - // if a block is done, finish it by writing all - if (block.state === 'done') { - const { startLine: finalStartLine, endLine: finalEndLine } = addedTrackingZoneOfBlockNum[blockNum] - this._writeText(uri, block.final, - { startLineNumber: finalStartLine, startColumn: 1, endLineNumber: finalEndLine, endColumn: Number.MAX_SAFE_INTEGER }, // 1-indexed + if (finalCode === null) continue + + const [originalStart, originalEnd] = originalBounds + const lines = newCode.split('\n') + newCode = [ + ...lines.slice(0, (originalStart - 1)), + ...finalCode.split('\n'), + ...lines.slice((originalEnd - 1) + 1, Infinity) + ].join('\n') + } + const numLines = this._getNumLines(uri) + if (numLines !== null) { + this._writeText(uri, newCode, + { startLineNumber: 1, startColumn: 1, endLineNumber: numLines, endColumn: Number.MAX_SAFE_INTEGER }, { shouldRealignDiffAreas: true } ) - diffZone._streamState.line = finalEndLine + 1 - currStreamingBlockNum = blockNum + 1 - continue } - // write the added text to the file - const deltaFinalText = block.final.substring((oldBlocks[blockNum]?.final ?? '').length, Infinity) - this._writeStreamedDiffZoneLLMText(uri, block.orig, block.final, deltaFinalText, latestStreamLocationMutable) - oldBlocks = blocks // oldblocks is only used if writingFinal + onDone() - // const { endLine: currentEndLine } = addedTrackingZoneOfBlockNum[blockNum] // would be bad to do this because a lot of the bottom lines might be the same. more accurate to go with latestStreamLocationMutable - // diffZone._streamState.line = currentEndLine - diffZone._streamState.line = latestStreamLocationMutable.line + onFinalMessage_?.() + res() + }, + onError: (e) => { + this._notifyError(e) + onDone() + this._undoHistory(uri) + onError_?.(e) + res() + }, + }) - } // end for + await awaitable - this._refreshStylesAndDiffsInURI(uri) - }, - onFinalMessage: async (params) => { - const { fullText } = params - console.log('final message!!', fullText) + } // end while - // 1. wait 500ms and fix lint errors - call lint error workflow - // (update react state to say "Fixing errors") - const blocks = extractSearchReplaceBlocks(fullText) - - if (blocks.length === 0) { - this._notificationService.info(`Void: We ran Apply, but the LLM didn't output any changes.`) - } - - await new Promise(resolve => setTimeout(resolve, 500)) - - // writeover the whole file - let newCode = originalFileCode - for (let blockNum = addedTrackingZoneOfBlockNum.length - 1; blockNum >= 0; blockNum -= 1) { - const { originalBounds } = addedTrackingZoneOfBlockNum[blockNum].metadata - const finalCode = blocks[blockNum].final - - if (finalCode === null) continue - - const [originalStart, originalEnd] = originalBounds - const lines = newCode.split('\n') - newCode = [ - ...lines.slice(0, (originalStart - 1)), - ...finalCode.split('\n'), - ...lines.slice((originalEnd - 1) + 1, Infinity) - ].join('\n') - } - const numLines = this._getNumLines(uri) - if (numLines !== null) { - this._writeText(uri, newCode, - { startLineNumber: 1, startColumn: 1, endLineNumber: numLines, endColumn: Number.MAX_SAFE_INTEGER }, - { shouldRealignDiffAreas: true } - ) - } - - onDone() - - onFinalMessage_?.() - }, - onError: (e) => { - this._notifyError(e) - onDone() - this._undoHistory(uri) - - onError_?.(e) - }, - - }) - } + } // end retryLoop + retryLoop() return diffZone } 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 ec6107ba..c28859c2 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 @@ -55,8 +55,6 @@ const applyingURIOfApplyBoxIdRef: { current: { [applyBoxId: string]: URI | undef export const ApplyBlockHoverButtons = ({ codeStr, applyBoxId }: { codeStr: string, applyBoxId: string }) => { - console.log('applyboxid', applyBoxId, applyingURIOfApplyBoxIdRef) - const settingsState = useSettingsState() const isDisabled = !!isFeatureNameDisabled('Apply', settingsState) || !applyBoxId @@ -144,8 +142,6 @@ export const ApplyBlockHoverButtons = ({ codeStr, applyBoxId }: { codeStr: strin - console.log('streamStateRef.current', streamState()) - const currStreamState = streamState() return <> {currStreamState !== 'streaming' && } From a6c9b5f089c8443960403b434a04258cee7854e7 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Sun, 2 Mar 2025 00:04:41 -0800 Subject: [PATCH 16/41] edit message bug fixes --- .../contrib/void/browser/chatThreadService.ts | 32 +++++++++++++++++++ .../react/src/sidebar-tsx/SidebarChat.tsx | 16 ++++++++-- .../contrib/void/common/voidSettingsTypes.ts | 2 +- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index 8003a02e..b4a74d3c 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -176,6 +176,9 @@ export interface IChatThreadService { getCurrentThreadState: () => ThreadType['state'] setCurrentThreadState: (newState: Partial) => void + closeStagingSelectionsInCurrentThread(): void; + closeStagingSelectionsInMessage(messageIdx: number): void; + // call to edit a message editUserMessageAndStreamResponse({ userMessage, chatMode, messageIdx }: { userMessage: string, chatMode: ChatMode, messageIdx: number }): Promise; @@ -657,6 +660,35 @@ class ChatThreadService extends Disposable implements IChatThreadService { } + + closeStagingSelectionsInCurrentThread = () => { + const currThread = this.getCurrentThreadState() + + // close all stagingSelections + const closedStagingSelections = currThread.stagingSelections.map(s => ({ ...s, state: { ...s.state, isOpened: false } })) + + const newThread = currThread + newThread.stagingSelections = closedStagingSelections + + this.setCurrentThreadState(newThread) + + } + + closeStagingSelectionsInMessage = (messageIdx: number) => { + const currMessage = this.getCurrentMessageState(messageIdx) + + // close all stagingSelections + const closedStagingSelections = currMessage.stagingSelections.map(s => ({ ...s, state: { ...s.state, isOpened: false } })) + + const newMessage = currMessage + newMessage.stagingSelections = closedStagingSelections + + this.setCurrentMessageState(messageIdx, newMessage) + + } + + + getCurrentThreadState = () => { const currentThread = this.getCurrentThread() return currentThread.state 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 1feeee03..e8e5ba1d 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 @@ -418,7 +418,7 @@ export const SelectedFiles = ( {allSelections.map((selection, i) => { - const isThisSelectionOpened = (!!selection.selectionStr && selection.state.isOpened) //!!(selection.selectionStr && selectionIsOpened[i]) + const isThisSelectionOpened = (!!selection.selectionStr && selection.state.isOpened && type === 'staging') const isThisSelectionAFile = selection.selectionStr === null const isThisSelectionProspective = i > selections.length - 1 @@ -501,7 +501,7 @@ export const SelectedFiles = ( {isThisSelectionOpened ?
    { @@ -923,9 +923,10 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM const thread = chatThreadsService.getCurrentThread() chatThreadsService.cancelStreaming(thread.id) - // reset state + // update state setIsBeingEdited(false) chatThreadsService.setFocusedMessageIdx(undefined) + chatThreadsService.closeStagingSelectionsInMessage(messageIdx) // stream the edit const userMessage = textAreaRefState.value; @@ -1064,7 +1065,13 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM : 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' : '' } + ${role === 'user' && mode === 'display' ? 'cursor-pointer' : ''} `} + onClick={() => { + if (role === 'user' && mode === 'display') { + onOpenEdit() + } + }} > {chatbubbleContents} {isLoading && } @@ -1155,6 +1162,9 @@ export const SidebarChat = () => { if (isDisabled) return if (isStreaming) return + // update state + chatThreadsService.closeStagingSelectionsInCurrentThread() // close all selections + // send message to LLM const userMessage = textAreaRef.current?.value ?? '' await chatThreadsService.addUserMessageAndStreamResponse({ userMessage, chatMode: 'agent' }) diff --git a/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts b/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts index 29963ec9..a933fae5 100644 --- a/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts +++ b/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts @@ -57,7 +57,7 @@ export const defaultModelsOfProvider = { ], anthropic: [ // https://docs.anthropic.com/en/docs/about-claude/models 'claude-3-7-sonnet-latest', - // 'claude-3-5-sonnet-latest', + 'claude-3-5-sonnet-latest', 'claude-3-5-haiku-latest', 'claude-3-opus-latest', ], From ee6d5e47ec9d61e8f83eb2d38f7a5ff1a051cd65 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Mon, 3 Mar 2025 20:09:55 -0800 Subject: [PATCH 17/41] dropdown + styles --- .../react/src/sidebar-tsx/SidebarChat.tsx | 932 +++++++++--------- 1 file changed, 482 insertions(+), 450 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 e8e5ba1d..6e21bc57 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 @@ -1,3 +1,4 @@ + /*-------------------------------------------------------------------------------------- * Copyright 2025 Glass Devtools, Inc. All rights reserved. * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. @@ -7,7 +8,6 @@ import React, { ButtonHTMLAttributes, FormEvent, FormHTMLAttributes, Fragment, K import { useAccessor, useSidebarState, useChatThreadsState, useChatThreadsStreamState, useUriState, useSettingsState } from '../util/services.js'; -import { ChatMessage, StagingSelectionItem, ToolMessage } from '../../../chatThreadService.js'; import { BlockCode } from '../markdown/BlockCode.js'; import { ChatMarkdownRender, ChatMessageLocation } from '../markdown/ChatMarkdownRender.js'; @@ -19,13 +19,14 @@ import { ModelDropdown, } from '../void-settings-tsx/ModelDropdown.js'; import { SidebarThreadSelector } from './SidebarThreadSelector.js'; import { useScrollbarStyles } from '../util/useScrollbarStyles.js'; import { VOID_CTRL_L_ACTION_ID } from '../../../actionIDs.js'; -import { filenameToVscodeLanguage } from '../../../../common/helpers/detectLanguage.js'; import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js'; -import { ChevronRight, Pencil, X } from 'lucide-react'; +import { ChevronRight, Pencil, X, AlertTriangle } from 'lucide-react'; import { FeatureName, isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js'; import { WarningBox } from '../void-settings-tsx/WarningBox.js'; +import { ChatMessage, StagingSelectionItem, ToolMessage } from '../../../chatThreadService.js'; +import { filenameToVscodeLanguage } from '../../../../common/helpers/detectLanguage.js'; +import { ToolName } from '../../../toolsService.js'; -import { ToolResultType, ToolName } from '../../../toolsService.js'; @@ -151,7 +152,7 @@ interface VoidChatAreaProps { onAbort: () => void; isStreaming: boolean; isDisabled?: boolean; - divRef?: React.RefObject; + divRef?: React.RefObject; // UI customization featureName: FeatureName; @@ -190,20 +191,24 @@ export const VoidChatArea: React.FC = ({ return (
    { onClickAnywhere?.() }} + onKeyDown={(e: React.KeyboardEvent) => { + if (e.key === 'Escape' && isStreaming && onAbort) { + onAbort(); + } + }} > {/* Selections section */} {showSelections && selections && setSelections && ( @@ -443,7 +448,7 @@ export const SelectedFiles = ( border rounded-sm ${isThisSelectionProspective ? 'border-void-border-2' : isThisSelectionOpened - ? 'border-void-border-1 ring-1 ring-[#007FD4]' + ? 'border-void-border-1 ring-1 ring-void-blue' : 'border-void-border-1' } hover:border-void-border-1 @@ -502,7 +507,7 @@ export const SelectedFiles = (
    { e.stopPropagation(); // don't focus input box @@ -528,42 +533,33 @@ export const SelectedFiles = ( } -const actionTitleOfToolName: { [T in ToolName]: string } = { - 'read_file': 'Read file', - 'list_dir': 'Inspected folder', - 'pathname_search': 'Searched filename', - 'search': 'Searched', - - 'create_uri': 'Created URI', - 'delete_uri': 'Deleted URI', - 'edit': 'Edited file', - 'terminal_command': 'Ran terminal command', -} - - -const ToolResult = ({ - toolName, - actionParam, - actionNumResults, - children, - onClick, -}: { - toolName: ToolName; - actionParam: string; - actionNumResults?: number; +interface DropdownComponentProps { + title: string; + desc1: string; + desc2?: string; + numResults?: number; children?: React.ReactNode; onClick?: () => void; -}) => { +} + +const DropdownComponent = ({ + title, + desc1, + desc2, + numResults, + children, + onClick, +}: DropdownComponentProps) => { const [isExpanded, setIsExpanded] = useState(false); const isDropdown = !!children const isClickable = !!isDropdown || !!onClick return ( -
    +
    )}
    - {actionTitleOfToolName[toolName]} - {actionParam} - {actionNumResults !== undefined && ( + {title} + {desc1} + {desc2 && + {desc2} + } + {numResults !== undefined && ( - {`(`}{actionNumResults}{` result`}{actionNumResults !== 1 ? 's' : ''}{`)`} + {`(`}{numResults}{` result`}{numResults !== 1 ? 's' : ''}{`)`} )}
    @@ -591,7 +590,9 @@ const ToolResult = ({ // the py-1 here makes sure all elements in the container have py-2 total. this makes a nice animation effect during transition. className={`overflow-hidden transition-all duration-200 ease-in-out ${isExpanded ? 'opacity-100 py-1' : 'max-h-0 opacity-0'}`} > - {children} +
    + {children} +
    @@ -599,252 +600,9 @@ const ToolResult = ({ }; - -const ToolError = ({ toolName, errorMessage }: { toolName: T, errorMessage: string }) => { - return - - -} - - -const toolResultToComponent: { [T in ToolName]: (props: { message: ToolMessage }) => React.ReactNode } = { - 'read_file': ({ message }) => { - - const accessor = useAccessor() - const commandService = accessor.get('ICommandService') - if (message.result.type === 'error') return - - const { value, params } = message.result - return ( - -
    -
    { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }} - > - - {params.uri.fsPath} -
    - {value.hasNextPage && (
    AI can scroll for more content...
    )} -
    -
    - ) - }, - 'list_dir': ({ message }) => { - const accessor = useAccessor() - const commandService = accessor.get('ICommandService') - const explorerService = accessor.get('IExplorerService') - // message.result.hasNextPage = true - // message.result.itemsRemaining = 400 - if (message.result.type === 'error') return - - const { value, params } = message.result - return ( - -
    - {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...
    )} -
    -
    - ) - }, - 'pathname_search': ({ message }) => { - - const accessor = useAccessor() - const commandService = accessor.get('ICommandService') - if (message.result.type === 'error') return - - const { value, params } = message.result - return ( - -
    - {value.uris.map((uri, i) => ( -
    { commandService.executeCommand('vscode.open', uri, { preview: true }) }} - > - - {uri.fsPath.split('/').pop()} -
    - ))} - {value.hasNextPage && (
    More results available...
    )} -
    -
    - ) - }, - 'search': ({ message }) => { - const accessor = useAccessor() - const commandService = accessor.get('ICommandService') - if (message.result.type === 'error') return - - const { value, params } = message.result - return ( - -
    - {value.uris.map((uri, i) => ( -
    { commandService.executeCommand('vscode.open', uri, { preview: true }) }} - > - - {uri.fsPath.split('/').pop()} -
    - ))} - {value.hasNextPage && (
    More results available...
    )} -
    -
    - ) - }, - - // --- - - 'create_uri': ({ message }) => { - const accessor = useAccessor() - const commandService = accessor.get('ICommandService') - - if (message.result.type === 'error') return - - const { params } = message.result - return ( - { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }} - > -
    -
    { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }} - > - - {params.uri.fsPath.split('/').pop()} -
    -
    -
    - ) - }, - 'delete_uri': ({ message }) => { - const accessor = useAccessor() - const commandService = accessor.get('ICommandService') - - if (message.result.type === 'error') return - - const { params } = message.result - return ( - -
    -
    { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }} - > - - {params.uri.fsPath.split('/').pop()} -
    -
    -
    - ) - }, - 'edit': ({ message }) => { - const accessor = useAccessor() - const commandService = accessor.get('ICommandService') - - if (message.result.type === 'error') return - - const { params } = message.result - return ( - { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }} - > -
    -
    { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }} - > - - {params.uri.fsPath.split('/').pop()} -
    -
    -
    - ) - }, - 'terminal_command': ({ message }) => { - const accessor = useAccessor() - const commandService = accessor.get('ICommandService') - - if (message.result.type === 'error') return - - const { params } = message.result - return ( - -
    -
    - - - -
    -
    -
    - ) - } - -}; - - - -type ChatBubbleMode = 'display' | 'edit' -const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatMessage, messageIdx: number, isLoading?: boolean, }) => { +const UserMessageComponent = ({ chatMessage, messageIdx, isLoading }: ChatBubbleProps & { chatMessage: ChatMessage & { role: 'user' } }) => { const role = chatMessage.role - // Only show reasoning dropdown when there's actual content - const reasoningStr = (chatMessage.role === 'assistant' && chatMessage.reasoning?.trim()) || null - const hasReasoning = !!reasoningStr - - const [isReasoningOpen, setIsReasoningOpen] = useState(false) const accessor = useAccessor() const chatThreadsService = accessor.get('IChatThreadService') @@ -889,7 +647,7 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM } }, [chatMessage, role, mode, _justEnabledEdit, textAreaRefState, textAreaFnsRef.current, _justEnabledEdit.current, _mustInitialize.current]) - const EditSymbol = mode === 'display' ? Pencil : X + const onOpenEdit = () => { setIsBeingEdited(true) chatThreadsService.setFocusedMessageIdx(messageIdx) @@ -902,155 +660,95 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM chatThreadsService.setFocusedMessageIdx(undefined) } - // set chat bubble contents + + const EditSymbol = mode === 'display' ? Pencil : X + + let chatbubbleContents: React.ReactNode - if (role === 'user') { - if (mode === 'display') { - chatbubbleContents = <> - - {chatMessage.displayContent} - - } - else if (mode === 'edit') { - - const onSubmit = async () => { - - if (isDisabled) return; - if (!textAreaRefState) return; - if (messageIdx === undefined) return; - - // cancel any streams on this thread - const thread = chatThreadsService.getCurrentThread() - chatThreadsService.cancelStreaming(thread.id) - - // update state - setIsBeingEdited(false) - chatThreadsService.setFocusedMessageIdx(undefined) - chatThreadsService.closeStagingSelectionsInMessage(messageIdx) - - // stream the edit - const userMessage = textAreaRefState.value; - await chatThreadsService.editUserMessageAndStreamResponse({ userMessage, chatMode: 'agent', messageIdx, }) - } - - const onAbort = () => { - const threadId = chatThreadsService.state.currentThreadId - chatThreadsService.cancelStreaming(threadId) - } - - const onKeyDown = (e: KeyboardEvent) => { - if (e.key === 'Escape') { - onCloseEdit() - } - if (e.key === 'Enter' && !e.shiftKey) { - onSubmit() - } - } - - if (!chatMessage.content && !isLoading) { // don't show if empty and not loading (if loading, want to show) - return null - } - - chatbubbleContents = <> - - setIsDisabled(!text)} - onFocus={() => { - setIsFocused(true) - chatThreadsService.setFocusedMessageIdx(messageIdx); - }} - onBlur={() => { - setIsFocused(false) - }} - onKeyDown={onKeyDown} - fnsRef={textAreaFnsRef} - multiline={true} - /> - - - } - } - else if (role === 'assistant') { - const thread = chatThreadsService.getCurrentThread() - - const chatMessageLocation: ChatMessageLocation = { - threadId: thread.id, - messageIdx: messageIdx, - } - - - const reasoningDropdown = hasReasoning ? ( -
    -
    -
    setIsReasoningOpen(!isReasoningOpen)} - > - -
    - Reasoning - Model's step-by-step thinking -
    -
    -
    -
    - -
    -
    -
    -
    - ) : null - - chatbubbleContents = (<> - {/* Reasoning dropdown (conditional) */} - {reasoningDropdown} - {/* Main content */} - - ) - } - else if (role === 'tool') { - - const ToolComponent = toolResultToComponent[chatMessage.name] as ({ message }: { message: any }) => React.ReactNode // ts isnt smart enough to deal with the types here... - - chatbubbleContents = - - console.log('tool result:', chatMessage.name, chatMessage.paramsStr, chatMessage.result) - - } - else if (role === 'tool_request') { + if (mode === 'display') { chatbubbleContents = <> - {JSON.stringify(chatMessage.name, null, 2)}
    - {JSON.stringify(chatMessage.params, null, 2)} -
    { chatThreadsService.approveTool(chatMessage.voidToolId) }}>Accept
    -
    { chatThreadsService.rejectTool(chatMessage.voidToolId) }}>Reject
    + + {chatMessage.displayContent} - } + else if (mode === 'edit') { + + const onSubmit = async () => { + + if (isDisabled) return; + if (!textAreaRefState) return; + if (messageIdx === undefined) return; + + // cancel any streams on this thread + const thread = chatThreadsService.getCurrentThread() + chatThreadsService.cancelStreaming(thread.id) + + // update state + setIsBeingEdited(false) + chatThreadsService.setFocusedMessageIdx(undefined) + chatThreadsService.closeStagingSelectionsInMessage(messageIdx) + + // stream the edit + const userMessage = textAreaRefState.value; + await chatThreadsService.editUserMessageAndStreamResponse({ userMessage, chatMode: 'agent', messageIdx, }) + } + + const onAbort = () => { + const threadId = chatThreadsService.state.currentThreadId + chatThreadsService.cancelStreaming(threadId) + } + + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + onCloseEdit() + } + if (e.key === 'Enter' && !e.shiftKey) { + onSubmit() + } + } + + if (!chatMessage.content && !isLoading) { // don't show if empty and not loading (if loading, want to show). + return null + } + + chatbubbleContents = + setIsDisabled(!text)} + onFocus={() => { + setIsFocused(true) + chatThreadsService.setFocusedMessageIdx(messageIdx); + }} + onBlur={() => { + setIsFocused(false) + }} + onKeyDown={onKeyDown} + fnsRef={textAreaFnsRef} + multiline={true} + /> + + } + + return
    setIsHovered(true)} @@ -1059,29 +757,21 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM
    { - if (role === 'user' && mode === 'display') { - onOpenEdit() - } - }} + onClick={() => { if (mode === 'display') { onOpenEdit() } }} > {chatbubbleContents} - {isLoading && }
    - {/* edit button */} + {role === 'user' && } + +
    + + + +} + + +const AssistantMessageComponent = ({ chatMessage, isLoading, messageIdx }: ChatBubbleProps & { chatMessage: ChatMessage & { role: 'assistant' } }) => { + + const accessor = useAccessor() + const chatThreadsService = accessor.get('IChatThreadService') + + + const reasoningStr = chatMessage.reasoning?.trim() || null + const hasReasoning = !!reasoningStr + const thread = chatThreadsService.getCurrentThread() + + const chatMessageLocation: ChatMessageLocation = { + threadId: thread.id, + messageIdx: messageIdx, + } + + return
    + + {/* reasoning token (anthropic) */} + {hasReasoning && + + } + + {/* assistant message */} + + + {isLoading && } + +
    + +} + + + +const ToolError = ({ title, errorMessage }: { title: string, errorMessage: string }) => { + return ( +
    + +
    + {title} +
    {'Error: ' + errorMessage}
    +
    +
    + ) +} + + +const toolNameToTitle: Record = { + 'read_file': 'Read file', + 'list_dir': 'Inspected folder', + 'pathname_search': 'Searched filename', + 'search': 'Searched', + 'create_uri': 'Created file', + 'delete_uri': 'Deleted file', + 'edit': 'Edited file', + 'terminal_command': 'Ran terminal command' +} + + + + +const toolNameToComponent: { [T in ToolName]: (props: { chatMessage: ToolMessage }) => React.ReactNode } = { + 'read_file': ({ chatMessage }) => { + + const accessor = useAccessor() + const commandService = accessor.get('ICommandService') + const title = toolNameToTitle[chatMessage.name] + + if (chatMessage.result.type === 'error') return + + const { value, params } = chatMessage.result + return ( + { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }} + > +
    { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }} + > +
    + {params.uri.fsPath} +
    + {value.hasNextPage && (
    AI can scroll for more content...
    )} +
    + ) + }, + 'list_dir': ({ chatMessage }) => { + const accessor = useAccessor() + const commandService = accessor.get('ICommandService') + const explorerService = accessor.get('IExplorerService') + const title = toolNameToTitle[chatMessage.name] + // message.result.hasNextPage = true + // message.result.itemsRemaining = 400 + if (chatMessage.result.type === 'error') return + + const { value, params } = chatMessage.result + return ( + + + {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... +
    + )} +
    + + ) + }, + 'pathname_search': ({ chatMessage }) => { + + const accessor = useAccessor() + const commandService = accessor.get('ICommandService') + const title = toolNameToTitle[chatMessage.name] + if (chatMessage.result.type === 'error') return + + const { value, params } = chatMessage.result + return ( + + {value.uris.map((uri, i) => ( +
    { + commandService.executeCommand('vscode.open', uri, { preview: true }) + }} + > +
    + {uri.fsPath.split('/').pop()} +
    + )) + } + {value.hasNextPage && ( +
    + More results available... +
    + )} +
    + ) + }, + 'search': ({ chatMessage }) => { + const accessor = useAccessor() + const commandService = accessor.get('ICommandService') + const title = toolNameToTitle[chatMessage.name] + if (chatMessage.result.type === 'error') return + + const { value, params } = chatMessage.result + return ( + + {value.uris.map((uri, i) => ( +
    { commandService.executeCommand('vscode.open', uri, { preview: true }) }} + > +
    + {uri.fsPath.split('/').pop()} +
    + ))} + {value.hasNextPage && (
    More results available...
    )} +
    + ) + }, + + 'create_uri': ({ chatMessage }) => { + const accessor = useAccessor() + const commandService = accessor.get('ICommandService') + const title = toolNameToTitle[chatMessage.name] + + if (chatMessage.result.type === 'error') return + + const { params } = chatMessage.result + return ( + { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }} + /> + ) + }, + 'delete_uri': ({ chatMessage }) => { + const accessor = useAccessor() + const commandService = accessor.get('ICommandService') + const title = toolNameToTitle[chatMessage.name] + + if (chatMessage.result.type === 'error') return + + const { params } = chatMessage.result + return ( + { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }} + /> + ) + }, + 'edit': ({ chatMessage }) => { + const accessor = useAccessor() + const commandService = accessor.get('ICommandService') + const title = toolNameToTitle[chatMessage.name] + + if (chatMessage.result.type === 'error') return + + const { params } = chatMessage.result + return ( + { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }} + /> + ) + }, + 'terminal_command': ({ chatMessage }) => { + const accessor = useAccessor() + const commandService = accessor.get('ICommandService') + const title = toolNameToTitle[chatMessage.name] + + if (chatMessage.result.type === 'error') return + + const { params } = chatMessage.result + return ( + +
    +
    + +
    +
    + ) + } + +}; + + +type ChatBubbleMode = 'display' | 'edit' +type ChatBubbleProps = { chatMessage: ChatMessage, messageIdx: number, isLoading?: boolean, } +const ChatBubble = ({ chatMessage, isLoading, messageIdx }: ChatBubbleProps) => { + + const role = chatMessage.role + + if (role === 'user') { + + return + + } + + else if (role === 'assistant') { + + return + + } + else if (role === 'tool') { + + + const ToolMessageComponent = toolNameToComponent[chatMessage.name] as React.FC<{ chatMessage: any, messageIdx: any, isLoading: any }> // ts isnt smart enough... + + return + + } + + } @@ -1225,14 +1257,14 @@ export const SidebarChat = () => { key={currentThread.id} // force rerender on all children if id changes scrollContainerRef={scrollContainerRef} className={` - w-full h-auto - flex flex-col - overflow-x-hidden - overflow-y-auto - py-4 - ${pastMessagesHTML.length === 0 && !messageSoFar ? 'hidden' : ''} - `} - style={{ maxHeight: sidebarDimensions.height - historyDimensions.height - chatAreaDimensions.height - 36 }} // the height of the previousMessages is determined by all other heights + flex flex-col + px-4 py-4 space-y-4 + w-full h-auto + overflow-x-hidden + overflow-y-auto + ${pastMessagesHTML.length === 0 && !messageSoFar ? 'hidden' : ''} + `} + style={{ maxHeight: sidebarDimensions.height - historyDimensions.height - chatAreaDimensions.height - (25) }} // the height of the previousMessages is determined by all other heights > {/* previous messages */} {allMessagesHTML} @@ -1260,8 +1292,10 @@ export const SidebarChat = () => { const onKeyDown = useCallback((e: KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { onSubmit() + } else if (e.key === 'Escape' && isStreaming) { + onAbort() } - }, [onSubmit]) + }, [onSubmit, onAbort, isStreaming]) const inputForm =
    0 ? 'absolute bottom-0' : ''}`}> { > { chatThreadsService.setFocusedMessageIdx(undefined) }} @@ -1298,5 +1332,3 @@ export const SidebarChat = () => {
    } - - From 1b77fc7091e8a0a7d4bc2977737918ed59ad20ba Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Tue, 4 Mar 2025 00:26:20 -0800 Subject: [PATCH 18/41] prepare large refactor --- .../void/browser/react/src/void-settings-tsx/Settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx index f886fdff..8e40d6c5 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx @@ -649,7 +649,7 @@ export const Settings = () => { {/* content */} -
    +
    From ead1d9229f87df069e567222280c25e0c5f5b857 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Tue, 4 Mar 2025 05:39:42 -0800 Subject: [PATCH 19/41] fix markdown spacing draft --- .../react/src/markdown/ChatMarkdownRender.tsx | 181 +++++++++++++----- .../react/src/sidebar-tsx/SidebarChat.tsx | 3 + .../react/src/void-settings-tsx/Settings.tsx | 14 +- .../void/browser/react/tailwind.config.js | 78 +++++++- 4 files changed, 216 insertions(+), 60 deletions(-) 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 0d57ca81..65786cfe 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 @@ -15,6 +15,9 @@ export type ChatMessageLocation = { } +const cn = (className: string) => className?.split(' ').map(c => c ? `void-${c}` : '').join(' ') + + type ApplyBoxLocation = ChatMessageLocation & { tokenIdx: string } const getApplyBoxId = ({ threadId, messageIdx, tokenIdx }: ApplyBoxLocation) => { @@ -23,28 +26,93 @@ const getApplyBoxId = ({ threadId, messageIdx, tokenIdx }: ApplyBoxLocation) => -export const CodeSpan = ({ children, className }: { children: React.ReactNode, className?: string }) => { - return - {children} - +// all classnames must go in tailwind.config.js/safelist +export const noSpaceStyles = { + blockquote: 'pl-4 border-l-4 border-void-bg-2 italic', + br: '', + code: '', + codespan: 'bg-void-bg-1 px-1 rounded-sm font-mono font-medium break-all', + def: '', + del: 'line-through', + em: 'italic', + escape: '', + heading: { + h1: "text-4xl font-semibold pb-2 border-b border-void-bg-2", + h2: "text-3xl font-semibold pb-2 border-b border-void-bg-2", + h3: "text-2xl font-semibold", + h4: "text-xl font-semibold", + h5: "text-lg font-semibold", + h6: "text-base font-semibold text-gray-600" + }, + hr: 'border-t border-void-bg-2', + html: '', + image: 'max-w-full h-auto rounded', + link: 'underline cursor-pointer', + list: 'list-inside pl-2', + list_item: '', + paragraph: '', + space: '', + strong: 'font-semibold', + table: 'overflow-x-auto', + text: '', } -const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, tokenIdx }: { token: Token | string, nested?: boolean, noSpace?: boolean, chatMessageLocationForApply?: ChatMessageLocation, tokenIdx: string }): JSX.Element => { + +const defaultStyles = { + blockquote: 'mx-2 pl-4 border-l-4 border-void-bg-2 italic my-4', + br: '', + code: 'mx-2 my-4', + codespan: 'bg-void-bg-1 px-1 rounded-sm font-mono font-medium break-all', + def: '', + del: 'line-through', + em: 'italic', + escape: '', + heading: { + h1: 'mx-2 text-4xl font-semibold mt-6 mb-4 pb-2 border-b border-void-bg-2', + h2: 'mx-2 text-3xl font-semibold mt-6 mb-4 pb-2 border-b border-void-bg-2', + h3: 'mx-2 text-2xl font-semibold mt-6 mb-4', + h4: 'mx-2 text-xl font-semibold mt-6 mb-4', + h5: 'mx-2 text-lg font-semibold mt-6 mb-4', + h6: 'mx-2 text-base font-semibold mt-6 mb-4 text-gray-600' + }, + hr: 'mx-2 my-6 border-t border-void-bg-2', + html: 'mx-2 my-4', + image: 'mx-2 my-4 max-w-full h-auto rounded', + link: 'mx-2 underline', + list: 'mx-2 my-2 list-inside pl-2', + list_item: 'mx-2 mb-2', + paragraph: 'mx-2 my-4', + space: '', + strong: 'mx-2 font-semibold', + table: 'mx-2 my-4 overflow-x-auto', + text: '', +} + + + +type TokenClasses = typeof defaultStyles + +const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx, classes }: { token: Token | string, nested?: boolean, chatMessageLocationForApply?: ChatMessageLocation, tokenIdx: string, classes?: TokenClasses }): JSX.Element => { // deal with built-in tokens first (assume marked token) const t = token as MarkedToken + if(t.raw.trim() ===''){ + return <>; + } + + // compute the className + const defaultClassName = defaultStyles[t.type] + const classNameOverride = classes?.[t.type] + const _className = classNameOverride ?? defaultClassName + let className: string = '' + if (typeof defaultClassName === 'string') { + className = _className as string + } + if (t.type === "space") { - return {t.raw} + return {t.raw} } if (t.type === "code") { @@ -55,32 +123,28 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, toke tokenIdx: tokenIdx, }) : null - return
    + return
    } - /> + initValue={t.text} + language={t.lang === undefined ? undefined : nameToVscodeLanguage[t.lang]} + buttonsOnHover={applyBoxId && } + />
    } if (t.type === "heading") { - const HeadingTag = `h${t.depth}` as keyof JSX.IntrinsicElements - const headingClasses: { [h: string]: string } = { - h1: "text-4xl font-semibold mt-6 mb-4 pb-2 border-b border-void-bg-2", - h2: "text-3xl font-semibold mt-6 mb-4 pb-2 border-b border-void-bg-2", - h3: "text-2xl font-semibold mt-6 mb-4", - h4: "text-xl font-semibold mt-6 mb-4", - h5: "text-lg font-semibold mt-6 mb-4", - h6: "text-base font-semibold mt-6 mb-4 text-gray-600" - } - return {t.text} + + const HeadingTag = `h${t.depth}` as keyof typeof defaultStyles.heading + + const className = classes?.heading[HeadingTag] ?? defaultStyles.heading[HeadingTag] + + return {t.text} } if (t.type === "table") { return ( -
    - +
    +
    {t.header.map((cell: any, index: number) => ( @@ -100,7 +164,7 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, toke {row.map((cell: any, cellIndex: number) => (
    {cell.raw} @@ -115,22 +179,34 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, toke } if (t.type === "hr") { - return
    + return
    } if (t.type === "blockquote") { - return
    {t.text}
    + return
    {t.text}
    + } + + if (t.type === 'list_item') { +
  • + + !!!!!!!!!!!!! + + +
  • } if (t.type === "list") { const ListTag = t.ordered ? "ol" : "ul" + + const itemClassName = classes?.['list_item'] ?? defaultStyles['list_item'] + return ( {t.items.map((item, index) => ( -
  • +
  • {item.task && ( )} @@ -145,10 +221,10 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, toke // return ( // // {t.items.map((item, index) => ( - //
  • + //
  • // {item.task && ( // // )} @@ -164,26 +240,26 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, toke if (t.type === "paragraph") { const contents = <> {t.tokens.map((token, index) => ( - // assign a unique tokenId to nested components + // assign a unique tokenId to nested components ))} if (nested) return contents - return

    + return

    {contents}

    } if (t.type === "html") { return ( -

    +

    {t.raw}

    ) } if (t.type === "text" || t.type === "escape") { - return {t.raw} + return {t.raw} } if (t.type === "def") { @@ -193,7 +269,7 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, toke if (t.type === "link") { return ( { window.open(t.href) }} href={t.href} title={t.title ?? undefined} @@ -208,51 +284,52 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, toke src={t.href} alt={t.text} title={t.title ?? undefined} - className={`max4w-full h-auto rounded ${noSpace ? '' : 'my-4'}`} + className={cn(className)} /> } if (t.type === "strong") { - return {t.text} + return {t.text} } if (t.type === "em") { - return {t.text} + return {t.text} } // inline code if (t.type === "codespan") { return ( - + {t.text} - + ) } if (t.type === "br") { - return
    + return
    } // strikethrough if (t.type === "del") { - return {t.text} + return {t.text} } // default return (
    Unknown type: + {t.type} {t.raw}
    ) } -export const ChatMarkdownRender = ({ string, nested = false, noSpace, chatMessageLocationForApply }: { string: string, nested?: boolean, noSpace?: boolean, chatMessageLocationForApply?: ChatMessageLocation }) => { +export const ChatMarkdownRender = ({ string, nested = false, classes, chatMessageLocationForApply }: { string: string, nested?: boolean, classes?: TokenClasses, chatMessageLocationForApply?: ChatMessageLocation }) => { const tokens = marked.lexer(string); // https://marked.js.org/using_pro#renderer return ( <> {tokens.map((token, index) => ( - + ))} ) 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 6e21bc57..86df2e7c 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 @@ -1,3 +1,6 @@ +//!!!! merged + + /*-------------------------------------------------------------------------------------- * Copyright 2025 Glass Devtools, Inc. All rights reserved. diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx index 8e40d6c5..1ebbefc9 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx @@ -15,7 +15,7 @@ import { isWindows, isLinux, isMacintosh } from '../../../../../../../base/commo import { URI } from '../../../../../../../base/common/uri.js' import { env } from '../../../../../../../base/common/process.js' import { ModelDropdown } from './ModelDropdown.js' -import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js' +import { ChatMarkdownRender, noSpaceStyles } from '../markdown/ChatMarkdownRender.js' import { WarningBox } from './WarningBox.js' import { os } from '../../../../common/helpers/systemInfo.js' @@ -293,7 +293,7 @@ const ProviderSetting = ({ providerName, settingName }: { providerName: Provider isPasswordField={isPasswordField} /> {subTextMd === undefined ? null :
    - +
    } @@ -413,11 +413,11 @@ export const FeaturesTab = () => { {/*

    {`Void can access any model that you host locally. We automatically detect your local models by default.`}

    */}

    {`Void can access any model that you host locally. We automatically detect your local models by default.`}

    - - - - - + + + + + {/* TODO we should create UI for downloading models without user going into terminal */}
    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 38a94f83..228b2847 100644 --- a/src/vs/workbench/contrib/void/browser/react/tailwind.config.js +++ b/src/vs/workbench/contrib/void/browser/react/tailwind.config.js @@ -167,6 +167,82 @@ module.exports = { }, }, plugins: [], - prefix: 'void-' + prefix: 'void-', + safelist: [ + // Background colors + 'void-bg-void-bg-1', + + // Borders + 'void-border-b', + 'void-border-l-4', + 'void-border-t', + 'void-border-void-bg-2', + + // Typography + 'void-text-2xl', + 'void-text-3xl', + 'void-text-4xl', + 'void-text-base', + 'void-text-lg', + 'void-text-xl', + 'void-text-gray-600', + 'void-font-medium', + 'void-font-mono', + 'void-font-semibold', + 'void-italic', + 'void-line-through', + 'void-underline', + + // Spacing + 'void-mt-1', + 'void-mt-2', + 'void-mt-4', + 'void-mt-6', + 'void-mb-1', + 'void-mb-2', + 'void-mb-4', + 'void-mx-1', + 'void-mx-2', + 'void-mx-4', + 'void-my-1', + 'void-my-2', + 'void-my-4', + 'void-my-6', + 'void-pb-1', + 'void-pb-2', + 'void-pb-4', + 'void-pl-1', + 'void-pl-2', + 'void-pl-4', + 'void-px-1', + 'void-px-2', + 'void-px-4', + + // Sizing and layout + 'void-h-auto', + 'void-max-w-full', + 'void-overflow-x-auto', + + // Lists + 'void-list-inside', + 'void-list-decimal', + 'void-list-disc', + + // Effects and decoration + 'void-cursor-pointer', + 'void-ring-8', + 'void-ring-[#123456]', + 'void-rounded', + 'void-rounded-sm', + + // misc + 'void-break-all', + 'void-bg-void-bg-1', + 'void-px-1', + 'void-rounded-sm', + 'void-font-mono', + 'void-font-medium', + 'void-break-all' + ] } From cc6a231b3c9785306304f6e5a6b074cad8ef2472 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Wed, 5 Mar 2025 22:03:01 -0800 Subject: [PATCH 20/41] fix markdown stylings with prose --- package-lock.json | 53 +++++- package.json | 3 +- .../contrib/void/browser/react/build.js | 2 +- .../react/src/markdown/ChatMarkdownRender.tsx | 162 ++++++++---------- .../browser/react/src/sidebar-tsx/Sidebar.tsx | 9 +- .../contrib/void/browser/react/src/styles.css | 24 +++ .../void/browser/react/src/util/services.tsx | 6 +- .../react/src/void-settings-tsx/Settings.tsx | 16 +- .../void/browser/react/tailwind.config.js | 137 +++++---------- 9 files changed, 207 insertions(+), 205 deletions(-) diff --git a/package-lock.json b/package-lock.json index b83fb86d..4a403917 100644 --- a/package-lock.json +++ b/package-lock.json @@ -75,6 +75,7 @@ "devDependencies": { "@playwright/test": "^1.50.0", "@stylistic/eslint-plugin-ts": "^2.8.0", + "@tailwindcss/typography": "^0.5.16", "@types/cookie": "^0.3.3", "@types/debug": "^4.1.5", "@types/diff": "^7.0.1", @@ -168,7 +169,7 @@ "pump": "^1.0.1", "rcedit": "^1.1.0", "rimraf": "^2.7.1", - "scope-tailwind": "^1.0.6", + "scope-tailwind": "^1.0.9", "sinon": "^12.0.1", "sinon-test": "^3.1.3", "source-map": "0.6.1", @@ -3598,6 +3599,36 @@ "node": ">=10" } }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz", + "integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.castarray": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, + "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@thisismanta/pessimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@thisismanta/pessimist/-/pessimist-1.2.0.tgz", @@ -14039,6 +14070,13 @@ "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY= sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "dev": true }, + "node_modules/lodash.castarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", + "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.clone": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", @@ -14057,6 +14095,13 @@ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "dev": true }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -18820,9 +18865,9 @@ "dev": true }, "node_modules/scope-tailwind": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/scope-tailwind/-/scope-tailwind-1.0.6.tgz", - "integrity": "sha512-tkISLsaesYKKXL9YrLsRWFOD/FhrRVGKeinjgTuFtEidryLzwlBB3G17ArmHWHYcfdMp00XwnRMcGFkF8wwG6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/scope-tailwind/-/scope-tailwind-1.0.9.tgz", + "integrity": "sha512-sxtAKxJq143lYK/RCE36YGq13ficBZ9/9Z0TZa78k0AEiKNT5nH4kfhD8YAfEXR/qPR+G7tl9KL4UoHh+Cs93g==", "dev": true, "license": "AGPL-3.0-only", "dependencies": { diff --git a/package.json b/package.json index 91cf98d0..52387a3e 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,7 @@ "devDependencies": { "@playwright/test": "^1.50.0", "@stylistic/eslint-plugin-ts": "^2.8.0", + "@tailwindcss/typography": "^0.5.16", "@types/cookie": "^0.3.3", "@types/debug": "^4.1.5", "@types/diff": "^7.0.1", @@ -229,7 +230,7 @@ "pump": "^1.0.1", "rcedit": "^1.1.0", "rimraf": "^2.7.1", - "scope-tailwind": "^1.0.6", + "scope-tailwind": "^1.0.9", "sinon": "^12.0.1", "sinon-test": "^3.1.3", "source-map": "0.6.1", diff --git a/src/vs/workbench/contrib/void/browser/react/build.js b/src/vs/workbench/contrib/void/browser/react/build.js index ab4ea921..26b5bc37 100755 --- a/src/vs/workbench/contrib/void/browser/react/build.js +++ b/src/vs/workbench/contrib/void/browser/react/build.js @@ -74,7 +74,7 @@ function saveStylesFile() { } catch (err) { console.error('[scope-tailwind] Error saving styles.css:', err); } - }, 3000); + }, 4000); } const args = process.argv.slice(2); 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 65786cfe..b4f6232c 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 @@ -14,10 +14,6 @@ export type ChatMessageLocation = { messageIdx: number; } - -const cn = (className: string) => className?.split(' ').map(c => c ? `void-${c}` : '').join(' ') - - type ApplyBoxLocation = ChatMessageLocation & { tokenIdx: string } const getApplyBoxId = ({ threadId, messageIdx, tokenIdx }: ApplyBoxLocation) => { @@ -26,6 +22,7 @@ const getApplyBoxId = ({ threadId, messageIdx, tokenIdx }: ApplyBoxLocation) => + // all classnames must go in tailwind.config.js/safelist export const noSpaceStyles = { blockquote: 'pl-4 border-l-4 border-void-bg-2 italic', @@ -88,31 +85,17 @@ const defaultStyles = { text: '', } - - -type TokenClasses = typeof defaultStyles - -const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx, classes }: { token: Token | string, nested?: boolean, chatMessageLocationForApply?: ChatMessageLocation, tokenIdx: string, classes?: TokenClasses }): JSX.Element => { - +const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx }: { token: Token | string, nested?: boolean, chatMessageLocationForApply?: ChatMessageLocation, tokenIdx: string }): JSX.Element => { // deal with built-in tokens first (assume marked token) const t = token as MarkedToken - if(t.raw.trim() ===''){ + if (t.raw.trim() === '') { return <>; } - // compute the className - const defaultClassName = defaultStyles[t.type] - const classNameOverride = classes?.[t.type] - const _className = classNameOverride ?? defaultClassName - let className: string = '' - if (typeof defaultClassName === 'string') { - className = _className as string - } - if (t.type === "space") { - return {t.raw} + return {t.raw} } if (t.type === "code") { @@ -123,7 +106,7 @@ const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx, cla tokenIdx: tokenIdx, }) : null - return
    + return
    {t.text} + return {t.text} } if (t.type === "table") { return ( -
    - +
    +
    - + {t.header.map((cell: any, index: number) => ( - ))} @@ -160,13 +137,9 @@ const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx, cla {t.rows.map((row: any[], rowIndex: number) => ( - + {row.map((cell: any, cellIndex: number) => ( - ))} @@ -176,20 +149,54 @@ const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx, cla
    + {cell.raw}
    + {cell.raw}
    ) + // return ( + //
    + // + // + // + // {t.header.map((cell: any, index: number) => ( + // + // ))} + // + // + // + // {t.rows.map((row: any[], rowIndex: number) => ( + // + // {row.map((cell: any, cellIndex: number) => ( + // + // ))} + // + // ))} + // + //
    + // {cell.raw} + //
    + // {cell.raw} + //
    + //
    + // ) } if (t.type === "hr") { - return
    + return
    } if (t.type === "blockquote") { - return
    {t.text}
    + return
    {t.text}
    } if (t.type === 'list_item') { -
  • - - !!!!!!!!!!!!! + return
  • + +
  • @@ -198,68 +205,49 @@ const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx, cla if (t.type === "list") { const ListTag = t.ordered ? "ol" : "ul" - const itemClassName = classes?.['list_item'] ?? defaultStyles['list_item'] - return ( - + {t.items.map((item, index) => ( -
  • +
  • {item.task && ( - + )} - +
  • ))}
    ) - // attempt at indentation - // return ( - // - // {t.items.map((item, index) => ( - //
  • - // {item.task && ( - // - // )} - // - // - // - //
  • - // ))} - //
    - // ) } if (t.type === "paragraph") { const contents = <> {t.tokens.map((token, index) => ( - // assign a unique tokenId to nested components + ))} + if (nested) return contents - return

    + return

    {contents}

    } if (t.type === "html") { return ( -

    +

    {t.raw}

    ) } if (t.type === "text" || t.type === "escape") { - return {t.raw} + return {t.raw} } if (t.type === "def") { @@ -269,10 +257,10 @@ const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx, cla if (t.type === "link") { return (
    { window.open(t.href) }} href={t.href} title={t.title ?? undefined} + className='underline cursor-pointer hover:brightness-90 transition-all duration-200' > {t.text} @@ -284,52 +272,50 @@ const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx, cla src={t.href} alt={t.text} title={t.title ?? undefined} - className={cn(className)} + /> } if (t.type === "strong") { - return {t.text} + return {t.text} } if (t.type === "em") { - return {t.text} + return {t.text} } // inline code if (t.type === "codespan") { return ( - + {t.text} ) } if (t.type === "br") { - return
    + return
    } // strikethrough if (t.type === "del") { - return {t.text} + return {t.text} } // default return (
    - Unknown type: - {t.type} - {t.raw} + Unknown token rendered...
    ) } -export const ChatMarkdownRender = ({ string, nested = false, classes, chatMessageLocationForApply }: { string: string, nested?: boolean, classes?: TokenClasses, chatMessageLocationForApply?: ChatMessageLocation }) => { +export const ChatMarkdownRender = ({ string, nested = false, chatMessageLocationForApply }: { string: string, nested?: boolean, chatMessageLocationForApply?: ChatMessageLocation }) => { const tokens = marked.lexer(string); // https://marked.js.org/using_pro#renderer return ( <> {tokens.map((token, index) => ( - + ))} ) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx index 839a6679..dd49d08a 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx @@ -2,11 +2,6 @@ * Copyright 2025 Glass Devtools, Inc. All rights reserved. * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------*/ -import React, { useEffect, useState } from 'react' -import { mountFnGenerator } from '../util/mountFnGenerator.js' - -// import { SidebarSettings } from './SidebarSettings.js'; - import { useIsDark, useSidebarState } from '../util/services.js'; // import { SidebarThreadSelector } from './SidebarThreadSelector.js'; @@ -20,9 +15,9 @@ export const Sidebar = ({ className }: { className: string }) => { const sidebarState = useSidebarState() const { currentTab: tab } = sidebarState - // const isDark = useIsDark() + const isDark = useIsDark() return
    { colorThemeState = themeService.getColorTheme().type disposables.push( - themeService.onDidColorThemeChange(theme => { - colorThemeState = theme.theme.type + themeService.onDidColorThemeChange(({ theme }) => { + colorThemeState = theme.type colorThemeStateListeners.forEach(l => l(colorThemeState)) }) ) diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx index 1ebbefc9..79c8fdb8 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx @@ -15,7 +15,7 @@ import { isWindows, isLinux, isMacintosh } from '../../../../../../../base/commo import { URI } from '../../../../../../../base/common/uri.js' import { env } from '../../../../../../../base/common/process.js' import { ModelDropdown } from './ModelDropdown.js' -import { ChatMarkdownRender, noSpaceStyles } from '../markdown/ChatMarkdownRender.js' +import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js' import { WarningBox } from './WarningBox.js' import { os } from '../../../../common/helpers/systemInfo.js' @@ -293,7 +293,7 @@ const ProviderSetting = ({ providerName, settingName }: { providerName: Provider isPasswordField={isPasswordField} /> {subTextMd === undefined ? null :
    - +
    }
    @@ -412,12 +412,12 @@ export const FeaturesTab = () => { {/*

    {`Instructions:`}

    */} {/*

    {`Void can access any model that you host locally. We automatically detect your local models by default.`}

    */}

    {`Void can access any model that you host locally. We automatically detect your local models by default.`}

    -
    - - - - - +
    + + + + + {/* TODO we should create UI for downloading models without user going into terminal */}
    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 228b2847..be4f9a13 100644 --- a/src/vs/workbench/contrib/void/browser/react/tailwind.config.js +++ b/src/vs/workbench/contrib/void/browser/react/tailwind.config.js @@ -9,6 +9,29 @@ module.exports = { content: ['./src2/**/*.{jsx,tsx}'], // uses these files to decide how to transform the css file theme: { extend: { + typography: { + DEFAULT: { + css: { + '--tw-prose-body': 'var(--void-fg-1)', + '--tw-prose-headings': 'var(--void-fg-1)', + '--tw-prose-lead': 'var(--void-fg-2)', + '--tw-prose-links': 'var(--void-link-color)', + '--tw-prose-bold': 'var(--void-fg-1)', + '--tw-prose-counters': 'var(--void-fg-3)', + '--tw-prose-bullets': 'var(--void-fg-3)', + '--tw-prose-hr': 'var(--void-border-4)', + '--tw-prose-quotes': 'var(--void-fg-1)', + '--tw-prose-quote-borders': 'var(--void-border-2)', + '--tw-prose-captions': 'var(--void-fg-3)', + '--tw-prose-code': 'var(--void-fg-0)', + '--tw-prose-pre-code': 'var(--void-fg-0)', + '--tw-prose-pre-bg': 'var(--void-bg-1)', + '--tw-prose-th-borders': 'var(--void-border-4)', + '--tw-prose-td-borders': 'var(--void-border-4)', + }, + }, + + }, fontSize: { xs: '10px', sm: '11px', @@ -27,27 +50,29 @@ module.exports = { // common colors to use, ordered light to dark colors: { - 'void-bg-1': 'var(--vscode-input-background)', - 'void-bg-1-alt': 'var(--vscode-badge-background)', - 'void-bg-2': 'var(--vscode-sideBar-background)', - 'void-bg-2-alt': 'color-mix(in srgb, var(--vscode-sideBar-background) 30%, var(--vscode-editor-background) 70%)', - 'void-bg-3': 'var(--vscode-editor-background)', + 'void-bg-1': 'var(--void-bg-1)', + 'void-bg-1-alt': 'var(--void-bg-1-alt)', + 'void-bg-2': 'var(--void-bg-2)', + 'void-bg-2-alt': 'var(--void-bg-2-alt)', + 'void-bg-3': 'var(--void-bg-3)', - 'void-fg-1': 'var(--vscode-editor-foreground)', - 'void-fg-2': 'var(--vscode-input-foreground)', - 'void-fg-3': 'var(--vscode-input-placeholderForeground)', + 'void-fg-0': 'var(--void-fg-0)', + 'void-fg-1': 'var(--void-fg-1)', + 'void-fg-2': 'var(--void-fg-2)', + 'void-fg-3': 'var(--void-fg-3)', // 'void-fg-4': 'var(--vscode-tab-inactiveForeground)', - 'void-fg-4': 'var(--vscode-list-deemphasizedForeground)', + 'void-fg-4': 'var(--void-fg-4)', + 'void-warning': 'var(--void-warning)', - 'void-warning': 'var(--vscode-charts-yellow)', - - 'void-border-1': 'var(--vscode-commandCenter-activeBorder)', - 'void-border-2': 'var(--vscode-commandCenter-border)', - 'void-border-3': 'var(--vscode-commandCenter-inactiveBorder)', - 'void-border-4': 'var(--vscode-editorGroup-border)', + 'void-border-1': 'var(--void-border-1)', + 'void-border-2': 'var(--void-border-2)', + 'void-border-3': 'var(--void-border-3)', + 'void-border-4': 'var(--void-border-4)', + 'void-ring-color': 'var(--void-ring-color)', + 'void-link-color': 'var(--void-link-color)', vscode: { // see: https://code.visualstudio.com/api/extension-guides/webview#theming-webview-content @@ -166,83 +191,9 @@ module.exports = { }, }, }, - plugins: [], - prefix: 'void-', - safelist: [ - // Background colors - 'void-bg-void-bg-1', - - // Borders - 'void-border-b', - 'void-border-l-4', - 'void-border-t', - 'void-border-void-bg-2', - - // Typography - 'void-text-2xl', - 'void-text-3xl', - 'void-text-4xl', - 'void-text-base', - 'void-text-lg', - 'void-text-xl', - 'void-text-gray-600', - 'void-font-medium', - 'void-font-mono', - 'void-font-semibold', - 'void-italic', - 'void-line-through', - 'void-underline', - - // Spacing - 'void-mt-1', - 'void-mt-2', - 'void-mt-4', - 'void-mt-6', - 'void-mb-1', - 'void-mb-2', - 'void-mb-4', - 'void-mx-1', - 'void-mx-2', - 'void-mx-4', - 'void-my-1', - 'void-my-2', - 'void-my-4', - 'void-my-6', - 'void-pb-1', - 'void-pb-2', - 'void-pb-4', - 'void-pl-1', - 'void-pl-2', - 'void-pl-4', - 'void-px-1', - 'void-px-2', - 'void-px-4', - - // Sizing and layout - 'void-h-auto', - 'void-max-w-full', - 'void-overflow-x-auto', - - // Lists - 'void-list-inside', - 'void-list-decimal', - 'void-list-disc', - - // Effects and decoration - 'void-cursor-pointer', - 'void-ring-8', - 'void-ring-[#123456]', - 'void-rounded', - 'void-rounded-sm', - - // misc - 'void-break-all', - 'void-bg-void-bg-1', - 'void-px-1', - 'void-rounded-sm', - 'void-font-mono', - 'void-font-medium', - 'void-break-all' - ] + plugins: [ + require('@tailwindcss/typography') + ], + prefix: 'void-' } From 1086b6272a0b87ef26062917f30237174a33de98 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Thu, 6 Mar 2025 00:19:29 -0800 Subject: [PATCH 21/41] minor change --- .../contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 86df2e7c..31167102 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 @@ -774,7 +774,7 @@ const UserMessageComponent = ({ chatMessage, messageIdx, isLoading }: ChatBubble {role === 'user' && Date: Tue, 4 Mar 2025 18:26:56 -0800 Subject: [PATCH 22/41] update contrib --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2b4c1d00..2990b193 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,7 @@ There are a few ways to contribute: ### Codebase Guide -We highly recommend reading [this](https://github.com/microsoft/vscode/wiki/Source-Code-Organization) article on VSCode's sourcecode organization. +We [highly recommend reading this](https://github.com/microsoft/vscode/wiki/Source-Code-Organization) article on VSCode's sourcecode organization too. Void's codebase is pretty simple when you know what a service is and what `browser/` and `common/` mean, and the article covers all the jargon.