diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index 4ee81654..0a033524 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -641,15 +641,16 @@ class ChatThreadService extends Disposable implements IChatThreadService { resMessageIsDonePromise(toolCall) // resolve with tool calls }, - onError: (error) => { + onError: async (error) => { const messageSoFar = this.streamState[threadId]?.displayContentSoFar ?? '' const reasoningSoFar = this.streamState[threadId]?.reasoningSoFar ?? '' + this._setStreamState(threadId, { displayContentSoFar: undefined, reasoningSoFar: undefined, streamingToken: undefined, toolCallSoFar: undefined }, 'merge') if (nAttempts < CHAT_RETRIES) { nAttempts += 1 shouldRetry = true - this._setStreamState(threadId, { displayContentSoFar: undefined, reasoningSoFar: undefined, streamingToken: undefined, toolCallSoFar: undefined }, 'merge') - timeout(RETRY_DELAY).then(() => { resMessageIsDonePromise() }) + await timeout(RETRY_DELAY) + resMessageIsDonePromise() } else { // const toolCallSoFar = this.streamState[threadId]?.toolCallSoFar @@ -661,9 +662,9 @@ class ChatThreadService extends Disposable implements IChatThreadService { }, onAbort: () => { // stop the loop to free up the promise, but don't modify state (already handled by whatever stopped it) + aborted = true resMessageIsDonePromise() this._metricsService.capture('Agent Loop Done (Aborted)', { nMessagesSent, chatMode }) - aborted = true }, }) @@ -678,12 +679,12 @@ class ChatThreadService extends Disposable implements IChatThreadService { const toolCall = await messageIsDonePromise // wait for message to complete this._setStreamState(threadId, { streamingToken: undefined }, 'merge') // streaming message is done - if (shouldRetry) { - continue - } - if (aborted) { - return - } + // this is a complete hack to make it so if an error loop was aborted, we stop (because onAbort does not get called if error happens instantly) + // maybe we should remove all the abort stuff and just make it so that we only go by state? + if (!this.streamState[threadId]?.isRunning) { return } + + if (aborted) { return } + if (shouldRetry) { continue } // call tool if there is one const tool: RawToolCallObj | undefined = toolCall @@ -692,8 +693,9 @@ class ChatThreadService extends Disposable implements IChatThreadService { // stop if interrupted. we don't have to do this for llmMessage because we have a stream token for it and onAbort gets called, but we don't have the equivalent for tools. // just detect tool interruption which is the same as chat interruption right now - if (interrupted) { return } + if (!this.streamState[threadId]?.isRunning) { return } if (aborted) { return } + if (interrupted) { return } if (awaitingUserApproval) { console.log('awaiting...') @@ -1419,14 +1421,13 @@ We only need to do it for files that were edited since `from`, ie files between openNewThread() { // if a thread with 0 messages already exists, switch to it const { allThreads: currentThreads } = this.state - for (const threadId in currentThreads) { - if (currentThreads[threadId]!.messages.length === 0) { - - // switch to the thread - this.switchToThread(threadId) - - } - } + for (const threadId in currentThreads) { + if (currentThreads[threadId]!.messages.length === 0) { + // switch to the existing empty thread and exit + this.switchToThread(threadId) + return + } + } // otherwise, start a new thread const newThread = newThreadObject() 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 5f8c84f6..2e3ec7b1 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 @@ -1447,10 +1447,10 @@ export const ListableToolItem = ({ name, onClick, isSmall, className, showDot }: -const EditToolChildren = ({ uri, changeDescription }: { uri: URI | undefined, changeDescription: string }) => { +const EditToolChildren = ({ uri, changeDiff }: { uri: URI | undefined, changeDiff: string }) => { return
- +
} @@ -1980,7 +1980,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, componentParams.children = componentParams.desc2 = @@ -1997,7 +1997,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, componentParams.desc2 = } @@ -2010,7 +2010,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, componentParams.children = } @@ -2027,7 +2027,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, {/* content */} } @@ -2638,7 +2638,7 @@ const EditToolSoFar = ({ toolCallSoFar, }: { toolCallSoFar: RawToolCallObj }) => > 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 e11369e3..6e96aa22 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 @@ -295,7 +295,7 @@ const TrashButton = ({ threadId }: { threadId: string }) => { onClick={() => { setIsTrashPressed(true); }} data-tooltip-id='void-tooltip' data-tooltip-place='top' - data-tooltip-content='Delete thread?' + data-tooltip-content='Delete thread' /> ) } @@ -386,7 +386,7 @@ const PastThreadElement = ({ pastThread, idx, hoveredIdx, setHoveredIdx, isRunni {firstMsg} -
+
{idx === hoveredIdx ? <> {/* trash icon */} 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 a0e69fd8..887214a9 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 @@ -153,6 +153,36 @@ const AddButton = ({ disabled, text = 'Add', ...props }: { disabled?: boolean, t } +// ConfirmButton prompts for a second click to confirm an action, cancels if clicking outside +const ConfirmButton = ({ children, onConfirm, className }: { children: React.ReactNode, onConfirm: () => void, className?: string }) => { + const [confirm, setConfirm] = useState(false); + const ref = useRef(null); + useEffect(() => { + if (!confirm) return; + const handleClickOutside = (e: MouseEvent) => { + if (ref.current && !ref.current.contains(e.target as Node)) { + setConfirm(false); + } + }; + document.addEventListener('click', handleClickOutside); + return () => document.removeEventListener('click', handleClickOutside); + }, [confirm]); + return ( +
+ { + if (!confirm) { + setConfirm(true); + } else { + onConfirm(); + setConfirm(false); + } + }}> + {confirm ? `Confirm Reset` : children} + +
+ ); +}; + // shows a providerName dropdown if no `providerName` is given export const AddModelInputBox = ({ providerName: permanentProviderName, className, compact }: { providerName?: ProviderName, className?: string, compact?: boolean }) => { @@ -907,33 +937,7 @@ export const Settings = () => { {/* separator */}
- {/* Download & Upload Settings and Chats */} -
- - { fileInputSettingsRef.current?.click() }}> - Upload Settings - - onDownload('Settings')}> - Download Settings - - - - - { fileInputChatsRef.current?.click() }}> - Upload Chats - - onDownload('Chats')}> - Download Chats - - - - { voidSettingsService.resetState() }}> - Reset Settings - - { chatThreadsService.resetState() }}> - Reset Chats - -
+ {/* Models section (formerly FeaturesTab) */} {/* Models section (formerly FeaturesTab) */} @@ -1112,10 +1116,10 @@ export const Settings = () => { {/* General section (formerly GeneralTab) */}
-

One-Click Switch

-

{`Transfer your settings from another editor to Void in one click.`}

+

One-Click Switch

+

{`Transfer your settings from another editor to Void in one click.`}

-
+
@@ -1123,6 +1127,41 @@ export const Settings = () => {
+ {/* Import/Export section, as its own block right after One-Click Switch */} +
+

Import/Export

+
+ {/* Settings Subcategory */} +
+

Settings

+ + { fileInputSettingsRef.current?.click() }}> + Import Settings + + onDownload('Settings')}> + Export Settings + + { voidSettingsService.resetState(); }}> + Reset Settings + +
+ {/* Chats Subcategory */} +
+

Chat

+ + { fileInputChatsRef.current?.click() }}> + Import Chats + + onDownload('Chats')}> + Export Chats + + { chatThreadsService.resetState(); }}> + Reset Chats + +
+
+
+
@@ -1131,23 +1170,17 @@ export const Settings = () => {

{`IDE settings, keyboard settings, and theme customization.`}

-
- { commandService.executeCommand('workbench.action.openSettings') }}> +
+ { commandService.executeCommand('workbench.action.openSettings') }}> General Settings -
-
- { commandService.executeCommand('workbench.action.openGlobalKeybindings') }}> + { commandService.executeCommand('workbench.action.openGlobalKeybindings') }}> Keyboard Settings -
-
- { commandService.executeCommand('workbench.action.selectTheme') }}> + { commandService.executeCommand('workbench.action.selectTheme') }}> Theme Settings -
-
- { nativeHostService.showItemInFolder(environmentService.logsHome.fsPath) }}> + { nativeHostService.showItemInFolder(environmentService.logsHome.fsPath) }}> Open Logs
diff --git a/src/vs/workbench/contrib/void/browser/toolsService.ts b/src/vs/workbench/contrib/void/browser/toolsService.ts index b6dcf819..bdacb18e 100644 --- a/src/vs/workbench/contrib/void/browser/toolsService.ts +++ b/src/vs/workbench/contrib/void/browser/toolsService.ts @@ -242,10 +242,10 @@ export class ToolsService implements IToolsService { }, edit_file: (params: RawToolParamsObj) => { - const { uri: uriStr, change_description: changeDescriptionUnknown } = params + const { uri: uriStr, change_diff: changeDiffUnknown } = params const uri = validateURI(uriStr) - const changeDescription = validateStr('changeDescription', changeDescriptionUnknown) - return { uri, changeDescription } + const changeDiff = validateStr('changeDiff', changeDiffUnknown) + return { uri, changeDiff } }, // --- @@ -383,14 +383,14 @@ export class ToolsService implements IToolsService { return { result: {} } }, - edit_file: async ({ uri, changeDescription }) => { + edit_file: async ({ uri, changeDiff }) => { await voidModelService.initializeModel(uri) if (this.commandBarService.getStreamState(uri) === 'streaming') { - throw new Error(`Another LLM is currently making changes to this file. Please stop streaming for now and resume later.`) + throw new Error(`Another LLM is currently making changes to this file. Please stop streaming for now and ask the user to resume later.`) } const opts = { uri, - applyStr: changeDescription, + applyStr: changeDiff, from: 'ClickApply', startBehavior: 'keep-conflicts', } as const diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index ab88d57b..7ce1292b 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -204,12 +204,14 @@ export const voidTools = { description: `Edits the contents of a file given the file's URI and a description.`, params: { ...uriParam('file'), - change_description: { + change_diff: { description: `\ -Your description MUST be wrapped in triple backticks. \ -A code description of the change you want to make, with comments like "// ... existing code ..." to condense your writing. \ -NEVER re-write the whole file. Bias towards writing as little as possible. \ -Here's an example of a good description:\n${editToolDescriptionExample}` +A code diff describing the change to make to the file. \ +Your DIFF is the only context that will be given to another LLM to apply the change, so it must be accurate and complete. \ +Your DIFF MUST be wrapped in triple backticks. \ +NEVER re-write the whole file. Always bias towards writing as little as possible. \ +Use comments like "// ... existing code ..." to condense your writing. \ +Here's an example of a good output:\n${editToolDescriptionExample}` } }, }, @@ -506,7 +508,7 @@ export const DIVIDER = `=======` export const FINAL = `>>>>>>> UPDATED` export const searchReplace_systemMessage = `\ -You are a coding assistant that takes in a diff describing of a change to make, and outputs SEARCH/REPLACE code blocks which implement the change. +You are a coding assistant that takes in a diff, and outputs SEARCH/REPLACE code blocks to implement the change(s) in the diff. The diff will be labeled \`DIFF\` and the original file will be labeled \`ORIGINAL_FILE\`. Format your SEARCH/REPLACE blocks as follows: @@ -518,11 +520,11 @@ ${DIVIDER} ${FINAL} ${tripleTick[1]} -1. Your SEARCH/REPLACE block(s) must implement the change EXACTLY. +1. Your SEARCH/REPLACE block(s) must implement the diff EXACTLY. Do NOT leave anything out. -2. Assume any comments in the diff are PART OF THE CHANGE. Include them in the output. +2. You are allowed to output multiple SEARCH/REPLACE blocks to implement the change. -3. You are allowed to output multiple SEARCH/REPLACE blocks. +3. Assume any comments in the diff are PART OF THE CHANGE. Include them in the output. 4. Your output should consist ONLY of SEARCH/REPLACE blocks. Do NOT output any text or explanations before or after this. diff --git a/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts b/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts index 56bbf6fa..b234689c 100644 --- a/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts +++ b/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts @@ -42,7 +42,7 @@ export type ToolCallParams = { 'search_in_file': { uri: URI, query: string, isRegex: boolean }, 'read_lint_errors': { uri: URI }, // --- - 'edit_file': { uri: URI, changeDescription: string }, + 'edit_file': { uri: URI, changeDiff: string }, 'create_file_or_folder': { uri: URI, isFolder: boolean }, 'delete_file_or_folder': { uri: URI, isRecursive: boolean, isFolder: boolean }, // ---