From 7e8af9c2ef5e7edcbb41d116043a2cc5d01a3e96 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Wed, 2 Apr 2025 00:25:23 -0700 Subject: [PATCH] improve Apply state and misc other improvements for Apply --- .../contrib/void/browser/editCodeService.ts | 61 ++-- .../void/browser/editCodeServiceInterface.ts | 17 +- .../src/markdown/ApplyBlockHoverButtons.tsx | 283 +++++++++--------- .../src/quick-edit-tsx/QuickEditChat.tsx | 7 +- .../react/src/sidebar-tsx/SidebarChat.tsx | 33 +- .../contrib/void/browser/toolsService.ts | 7 +- .../void/browser/voidCommandBarService.ts | 2 +- .../void/common/chatThreadServiceTypes.ts | 2 +- .../contrib/void/common/prompt/prompts.ts | 18 +- 9 files changed, 231 insertions(+), 199 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index a701abec..58b3a76c 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -40,7 +40,7 @@ import { ICommandService } from '../../../../platform/commands/common/commands.j import { ILLMMessageService } from '../common/sendLLMMessageService.js'; import { LLMChatMessage, OnError, errorDetails } from '../common/sendLLMMessageTypes.js'; import { IMetricsService } from '../common/metricsService.js'; -import { IEditCodeService, AddCtrlKOpts, StartApplyingOpts } from './editCodeServiceInterface.js'; +import { IEditCodeService, AddCtrlKOpts, StartApplyingOpts, CallBeforeStartApplyingOpts } from './editCodeServiceInterface.js'; import { IVoidSettingsService } from '../common/voidSettingsService.js'; import { FeatureName } from '../common/voidSettingsTypes.js'; import { IVoidModelService } from '../common/voidModelService.js'; @@ -252,9 +252,9 @@ class EditCodeService extends Disposable implements IEditCodeService { onDidAddOrDeleteDiffZones = this._onDidAddOrDeleteDiffZones.event; // diffZone: [uri], diffs, isStreaming // listen on change diffs, change streaming (uri is const) - private readonly _onDidChangeDiffsInDiffZone = new Emitter<{ uri: URI, diffareaid: number }>(); + private readonly _onDidChangeDiffsInDiffZoneNotStreaming = new Emitter<{ uri: URI, diffareaid: number }>(); private readonly _onDidChangeStreamingInDiffZone = new Emitter<{ uri: URI, diffareaid: number }>(); - onDidChangeDiffsInDiffZone = this._onDidChangeDiffsInDiffZone.event; + onDidChangeDiffsInDiffZoneNotStreaming = this._onDidChangeDiffsInDiffZoneNotStreaming.event; onDidChangeStreamingInDiffZone = this._onDidChangeStreamingInDiffZone.event; // ctrlKZone: [uri], isStreaming // listen on change streaming @@ -994,7 +994,7 @@ class EditCodeService extends Disposable implements IEditCodeService { if (diffArea?.type !== 'DiffZone') continue // fire changed diffs (this is the only place Diffs are added) if (!diffArea._streamState.isStreaming) { - this._onDidChangeDiffsInDiffZone.fire({ uri, diffareaid: diffArea.diffareaid }) + this._onDidChangeDiffsInDiffZoneNotStreaming.fire({ uri, diffareaid: diffArea.diffareaid }) } } } @@ -1160,29 +1160,50 @@ class EditCodeService extends Disposable implements IEditCodeService { + private _getURIBeforeStartApplying(opts: CallBeforeStartApplyingOpts) { + // SR + if (opts.from === 'ClickApply') { + const uri = this._uriOfGivenURI(opts.uri) + if (!uri) return + return uri + } + else if (opts.from === 'QuickEdit') { + const { diffareaid } = opts + const ctrlKZone = this.diffAreaOfId[diffareaid] + if (ctrlKZone?.type !== 'CtrlKZone') return + const { _URI: uri } = ctrlKZone + return uri + } + return + } + public async callBeforeStartApplying(opts: CallBeforeStartApplyingOpts) { + const uri = this._getURIBeforeStartApplying(opts) + if (!uri) return + await this._voidModelService.initializeModel(uri) + } // the applyDonePromise this returns can reject, and should be caught with .catch - public async startApplying(opts: StartApplyingOpts): Promise<[URI, Promise] | null> { + public startApplying(opts: StartApplyingOpts): [URI, Promise] | null { let res: [DiffZone, Promise] | undefined = undefined if (opts.from === 'QuickEdit') { - res = await this._initializeWriteoverStream(opts) // rewrite + res = this._initializeWriteoverStream(opts) // rewrite } else if (opts.from === 'ClickApply') { if (this._settingsService.state.globalSettings.enableFastApply) { const numCharsInFile = this._fileLengthOfGivenURI(opts.uri) if (numCharsInFile === null) return null if (numCharsInFile < 1000) { // slow apply for short files (especially important for empty files) - res = await this._initializeWriteoverStream(opts) + res = this._initializeWriteoverStream(opts) } else { - res = await this._initializeSearchAndReplaceStream(opts) // fast apply + res = this._initializeSearchAndReplaceStream(opts) // fast apply } } else { - res = await this._initializeWriteoverStream(opts) // rewrite + res = this._initializeWriteoverStream(opts) // rewrite } } @@ -1278,6 +1299,7 @@ class EditCodeService extends Disposable implements IEditCodeService { _removeStylesFns: new Set(), } + console.log('FIRING START STREAMING IN DIFFZONE!!!') const diffZone = this._addDiffArea(adding) this._onDidChangeStreamingInDiffZone.fire({ uri, diffareaid: diffZone.diffareaid }) this._onDidAddOrDeleteDiffZones.fire({ uri }) @@ -1308,19 +1330,17 @@ class EditCodeService extends Disposable implements IEditCodeService { } - private async _initializeWriteoverStream(opts: StartApplyingOpts): Promise<[DiffZone, Promise] | undefined> { + private _initializeWriteoverStream(opts: StartApplyingOpts): [DiffZone, Promise] | undefined { const { from, } = opts - let uri: URI - let startRange: 'fullFile' | [number, number] + const uri = this._getURIBeforeStartApplying(opts) + if (!uri) return + let startRange: 'fullFile' | [number, number] let ctrlKZoneIfQuickEdit: CtrlKZone | null = null if (from === 'ClickApply') { - const uri_ = this._uriOfGivenURI(opts.uri) - if (!uri_) return - uri = uri_ startRange = 'fullFile' } else if (from === 'QuickEdit') { @@ -1328,15 +1348,13 @@ class EditCodeService extends Disposable implements IEditCodeService { const ctrlKZone = this.diffAreaOfId[diffareaid] if (ctrlKZone?.type !== 'CtrlKZone') return ctrlKZoneIfQuickEdit = ctrlKZone - const { startLine: startLine_, endLine: endLine_, _URI } = ctrlKZone - uri = _URI + const { startLine: startLine_, endLine: endLine_ } = ctrlKZone startRange = [startLine_, endLine_] } else { throw new Error(`Void: diff.type not recognized on: ${from}`) } - await this._voidModelService.initializeModel(uri) const { model } = this._voidModelService.getModel(uri) if (!model) return @@ -1530,13 +1548,12 @@ class EditCodeService extends Disposable implements IEditCodeService { } - private async _initializeSearchAndReplaceStream(opts: StartApplyingOpts & { from: 'ClickApply' }): Promise<[DiffZone, Promise] | undefined> { - const { from, applyStr, uri: givenURI, } = opts + private _initializeSearchAndReplaceStream(opts: StartApplyingOpts & { from: 'ClickApply' }): [DiffZone, Promise] | undefined { + const { from, applyStr, } = opts - const uri = this._uriOfGivenURI(givenURI) + const uri = this._getURIBeforeStartApplying(opts) if (!uri) return - await this._voidModelService.initializeModel(uri) const { model } = this._voidModelService.getModel(uri) if (!model) return diff --git a/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts b/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts index 8173f837..4be6e23c 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts @@ -13,7 +13,15 @@ import { Diff, DiffArea } from './editCodeService.js'; export type StartBehavior = 'accept-conflicts' | 'reject-conflicts' | 'keep-conflicts' -export type StartApplyingOpts = ({ +export type CallBeforeStartApplyingOpts = { + from: 'QuickEdit'; + diffareaid: number; // id of the CtrlK area (contains text selection) +} | { + from: 'ClickApply'; + uri: 'current' | URI; +} + +export type StartApplyingOpts = { from: 'QuickEdit'; diffareaid: number; // id of the CtrlK area (contains text selection) startBehavior: StartBehavior; @@ -22,7 +30,7 @@ export type StartApplyingOpts = ({ applyStr: string; uri: 'current' | URI; startBehavior: StartBehavior; -}) +} @@ -37,7 +45,8 @@ export const IEditCodeService = createDecorator('editCodeServi export interface IEditCodeService { readonly _serviceBrand: undefined; - startApplying(opts: StartApplyingOpts): Promise<[URI, Promise] | null>; + callBeforeStartApplying(opts: CallBeforeStartApplyingOpts): Promise; + startApplying(opts: StartApplyingOpts): [URI, Promise] | null; addCtrlKZone(opts: AddCtrlKOpts): number | undefined; removeCtrlKZone(opts: { diffareaid: number }): void; @@ -49,7 +58,7 @@ export interface IEditCodeService { // events onDidAddOrDeleteDiffZones: Event<{ uri: URI }>; - onDidChangeDiffsInDiffZone: Event<{ uri: URI; diffareaid: number }>; // only fires when not streaming!!! streaming would be too much + onDidChangeDiffsInDiffZoneNotStreaming: Event<{ uri: URI; diffareaid: number }>; // only fires when not streaming!!! streaming would be too much onDidChangeStreamingInDiffZone: Event<{ uri: URI; diffareaid: number }>; onDidChangeStreamingInCtrlKZone: Event<{ uri: URI; diffareaid: number }>; 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 1069b635..054d1fc9 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,10 +3,9 @@ import { useAccessor, useCommandBarState, useCommandBarURIListener, useSettingsS import { usePromise, useRefState } from '../util/helpers.js' import { isFeatureNameDisabled } from '../../../../common/voidSettingsTypes.js' import { URI } from '../../../../../../../base/common/uri.js' -import { FileSymlink, LucideIcon, RotateCw } from 'lucide-react' +import { FileSymlink, LucideIcon, RotateCw, Terminal } from 'lucide-react' import { Check, X, Square, Copy, Play, } from 'lucide-react' import { getBasename, ListableToolItem, ToolChildrenWrapper } from '../sidebar-tsx/SidebarChat.js' -import { ChatMarkdownRender } from './ChatMarkdownRender.js' enum CopyButtonText { Idle = 'Copy', @@ -64,9 +63,9 @@ export const IconShell1 = ({ onClick, Icon, disabled, className }: IconButtonPro // // ) -const COPY_FEEDBACK_TIMEOUT = 1000 // amount of time to say 'Copied!' +const COPY_FEEDBACK_TIMEOUT = 1500 // amount of time to say 'Copied!' -const CopyButton = ({ codeStr }: { codeStr: string }) => { +export const CopyButton = ({ codeStr }: { codeStr: string }) => { const accessor = useAccessor() const metricsService = accessor.get('IMetricsService') @@ -94,11 +93,6 @@ const CopyButton = ({ codeStr }: { codeStr: string }) => { } -// state persisted for duration of react only -// TODO change this to use type `ChatThreads.applyBoxState[applyBoxId]` -const applyingURIOfApplyBoxIdRef: { current: { [applyBoxId: string]: URI | undefined } } = { current: {} } - - export const JumpToFileButton = ({ uri }: { uri: URI | 'current' }) => { @@ -113,164 +107,78 @@ export const JumpToFileButton = ({ uri }: { uri: URI | 'current' }) => { }} /> ) - return jumpToFileButton } -export const useApplyButtonHTML = ({ codeStr, applyBoxId, uri }: { codeStr: string, applyBoxId: string, uri: URI | 'current' }) => { + + +export const JumpToTerminalButton = ({ onClick }: { onClick: () => void }) => { + return ( + + ) +} + + +// state persisted for duration of react only +// TODO change this to use type `ChatThreads.applyBoxState[applyBoxId]` +const applyingURIOfApplyBoxIdRef: { current: { [applyBoxId: string]: URI | undefined } } = { current: {} } + +const getUriBeingApplied = (applyBoxId: string) => { + return applyingURIOfApplyBoxIdRef.current[applyBoxId] ?? null +} + + +export const useApplyButtonState = ({ applyBoxId, uri }: { applyBoxId: string, uri: URI | 'current' }) => { const settingsState = useSettingsState() const isDisabled = !!isFeatureNameDisabled('Apply', settingsState) || !applyBoxId const accessor = useAccessor() - const editCodeService = accessor.get('IEditCodeService') const voidCommandBarService = accessor.get('IVoidCommandBarService') - const metricsService = accessor.get('IMetricsService') const [_, rerender] = useState(0) - const getUriBeingApplied = useCallback(() => { - return applyingURIOfApplyBoxIdRef.current[applyBoxId] ?? null - }, [applyBoxId]) - const getStreamState = useCallback(() => { - const uri = getUriBeingApplied() + const uri = getUriBeingApplied(applyBoxId) + console.log('uri',uri?.fsPath) if (!uri) return 'idle-no-changes' return voidCommandBarService.getStreamState(uri) - }, [voidCommandBarService, getUriBeingApplied]) + }, [voidCommandBarService, applyBoxId]) // listen for stream updates on this box - - useCommandBarURIListener(useCallback((uri_) => { const shouldUpdate = ( - getUriBeingApplied()?.fsPath === uri_.fsPath + getUriBeingApplied(applyBoxId)?.fsPath === uri_.fsPath || (uri !== 'current' && uri.fsPath === uri_.fsPath) ) - if (!shouldUpdate) return - rerender(c => c + 1) - }, [applyBoxId, editCodeService, getUriBeingApplied, uri]) - ) - - const onClickSubmit = useCallback(async () => { - if (isDisabled) return - if (getStreamState() === 'streaming') return - const [newApplyingUri, applyDonePromise] = await editCodeService.startApplying({ - from: 'ClickApply', - applyStr: codeStr, - uri: uri, - startBehavior: 'keep-conflicts', - }) ?? [] - // catch any errors by interrupting the stream - applyDonePromise?.catch(e => { if (newApplyingUri) editCodeService.interruptURIStreaming({ uri: newApplyingUri }) }) - - applyingURIOfApplyBoxIdRef.current[applyBoxId] = newApplyingUri ?? undefined - - rerender(c => c + 1) - metricsService.capture('Apply Code', { length: codeStr.length }) // capture the length only - }, [isDisabled, getStreamState, editCodeService, codeStr, uri, applyBoxId, metricsService]) - - - const onInterrupt = useCallback(() => { - if (getStreamState() !== 'streaming') return - const uri = getUriBeingApplied() - if (!uri) return - - editCodeService.interruptURIStreaming({ uri }) - metricsService.capture('Stop Apply', {}) - }, [getStreamState, getUriBeingApplied, editCodeService, metricsService]) - - const onAccept = useCallback(() => { - const uri = getUriBeingApplied() - if (uri) editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'accept', removeCtrlKs: false }) - }, [getUriBeingApplied, editCodeService]) - - const onReject = useCallback(() => { - const uri = getUriBeingApplied() - if (uri) editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'reject', removeCtrlKs: false }) - }, [getUriBeingApplied, editCodeService]) - - const onReapply = useCallback(() => { - onReject() - onClickSubmit() - }, [onReject, onClickSubmit]) + if (shouldUpdate) { + rerender(c => c + 1) + console.log('rerendering....') + } + }, [applyBoxId, applyBoxId, uri])) const currStreamState = getStreamState() + console.log('curr stream state', currStreamState) - const copyButton = ( - - ) - - const playButton = ( - - ) - - const stopButton = ( - - ) - - const reapplyButton = ( - - ) - - const acceptButton = ( - - ) - - const rejectButton = ( - - ) - - - - let buttonsHTML = <> - - if (currStreamState === 'streaming') { - buttonsHTML = <> - - {copyButton} - {stopButton} - + return { + getStreamState, + isDisabled, + currStreamState, } +} - if (currStreamState === 'idle-no-changes') { - buttonsHTML = <> - - {copyButton} - {playButton} - - } - if (currStreamState === 'idle-has-changes') { - buttonsHTML = <> - - {reapplyButton} - {rejectButton} - {acceptButton} - - } +export const StatusIndicatorHTML = ({ applyBoxId, uri }: { applyBoxId: string, uri: URI | 'current' }) => { + const { currStreamState } = useApplyButtonState({ applyBoxId, uri }) - const statusIndicatorHTML =
+ return
+} - return { - statusIndicatorHTML, - buttonsHTML, +export const ApplyButtonsHTML = ({ codeStr, applyBoxId, uri }: { codeStr: string, applyBoxId: string, uri: URI | 'current' }) => { + const accessor = useAccessor() + const editCodeService = accessor.get('IEditCodeService') + const metricsService = accessor.get('IMetricsService') + + const { + currStreamState, + isDisabled, + getStreamState, + } = useApplyButtonState({ applyBoxId, uri }) + + const onClickSubmit = useCallback(async () => { + if (isDisabled) return + if (getStreamState() === 'streaming') return + const opts = { + from: 'ClickApply', + applyStr: codeStr, + uri: uri, + startBehavior: 'reject-conflicts', + } as const + + await editCodeService.callBeforeStartApplying(opts) + const [newApplyingUri, applyDonePromise] = editCodeService.startApplying(opts) ?? [] + + // catch any errors by interrupting the stream + applyDonePromise?.catch(e => { if (newApplyingUri) editCodeService.interruptURIStreaming({ uri: newApplyingUri }) }) + + applyingURIOfApplyBoxIdRef.current[applyBoxId] = newApplyingUri ?? undefined + + // rerender(c => c + 1) + metricsService.capture('Apply Code', { length: codeStr.length }) // capture the length only + }, [isDisabled, getStreamState, editCodeService, codeStr, uri, applyBoxId, metricsService]) + + + const onInterrupt = useCallback(() => { + if (getStreamState() !== 'streaming') return + const uri = getUriBeingApplied(applyBoxId) + if (!uri) return + + editCodeService.interruptURIStreaming({ uri }) + metricsService.capture('Stop Apply', {}) + }, [getStreamState, applyBoxId, editCodeService, metricsService]) + + const onAccept = useCallback(() => { + const uri = getUriBeingApplied(applyBoxId) + if (uri) editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'accept', removeCtrlKs: false }) + }, [applyBoxId, editCodeService]) + + const onReject = useCallback(() => { + const uri = getUriBeingApplied(applyBoxId) + if (uri) editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'reject', removeCtrlKs: false }) + }, [applyBoxId, editCodeService]) + + // const onReapply = useCallback(() => { + // onReject() + // onClickSubmit() + // }, [onReject, onClickSubmit]) + + + if (currStreamState === 'streaming') { + return + } + + if (currStreamState === 'idle-no-changes') { + return + } + + if (currStreamState === 'idle-has-changes') { + return <> + {/* */} + + + } } - - - export const BlockCodeApplyWrapper = ({ children, initValue, @@ -305,10 +292,10 @@ export const BlockCodeApplyWrapper = ({ language: string; uri: URI | 'current', }) => { - - const { statusIndicatorHTML, buttonsHTML } = useApplyButtonHTML({ codeStr: initValue, applyBoxId, uri }) const accessor = useAccessor() const commandService = accessor.get('ICommandService') + const { currStreamState } = useApplyButtonState({ applyBoxId, uri }) + const name = uri !== 'current' ?
- {statusIndicatorHTML} + {name}
- {buttonsHTML} + + {currStreamState === 'idle-no-changes' && } +
diff --git a/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx index 42e9b81b..d541dc43 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx @@ -63,11 +63,14 @@ export const QuickEditChat = ({ if (isStreamingRef.current) return textAreaFnsRef.current?.disable() - const [newApplyingUri, applyDonePromise] = await editCodeService.startApplying({ + const opts = { from: 'QuickEdit', diffareaid, startBehavior: 'keep-conflicts', - }) ?? [] + } as const + + await editCodeService.callBeforeStartApplying(opts) + const [newApplyingUri, applyDonePromise] = editCodeService.startApplying(opts) ?? [] // catch any errors by interrupting the stream applyDonePromise?.catch(e => { if (newApplyingUri) editCodeService.interruptCtrlKStreaming({ diffareaid }) }) 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 71934968..7e948af7 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 @@ -28,7 +28,7 @@ import { getModelCapabilities, getIsResoningEnabledState } from '../../../../com import { AlertTriangle, Ban, ChevronRight, Dot, Pencil, X } from 'lucide-react'; import { ChatMessage, StagingSelectionItem, ToolMessage, ToolRequestApproval } from '../../../../common/chatThreadServiceTypes.js'; import { ToolCallParams, ToolName, toolNames, ToolNameWithApproval } from '../../../../common/toolsServiceTypes.js'; -import { JumpToFileButton, useApplyButtonHTML } from '../markdown/ApplyBlockHoverButtons.js'; +import { ApplyButtonsHTML, CopyButton, JumpToFileButton, JumpToTerminalButton, StatusIndicatorHTML, useApplyButtonState } from '../markdown/ApplyBlockHoverButtons.js'; import { IsRunningType } from '../../../chatThreadService.js'; @@ -733,7 +733,7 @@ const ToolHeaderWrapper = ({ {/* left */}
{title} - {desc1} + {desc1}
{/* right */} @@ -1197,7 +1197,7 @@ const titleOfToolName = { running: (isFolder: boolean) => loadingTitleWrapper(`Deleting ${folderFileStr(isFolder)}`) }, 'edit': { done: `Edited file`, proposed: 'Edit file', running: loadingTitleWrapper('Editing file') }, - 'terminal_command': { done: `Ran terminal command`, proposed: 'Run terminal command', running: loadingTitleWrapper('Running terminal command') } + 'terminal_command': { done: `Ran terminal`, proposed: 'Run terminal', running: loadingTitleWrapper('Running terminal') } } as const satisfies Record @@ -1345,13 +1345,6 @@ export const ListableToolItem = ({ name, onClick, isSmall, className, showDot }:
} -const EditToolApplyButton = ({ changeDescription, applyBoxId, uri }: { changeDescription: string, applyBoxId: string, uri: URI }) => { - const { statusIndicatorHTML, buttonsHTML } = useApplyButtonHTML({ codeStr: changeDescription, applyBoxId, uri }) - return
- {statusIndicatorHTML} - {buttonsHTML} -
-} const EditToolChildren = ({ uri, changeDescription }: { uri: URI, changeDescription: string }) => { @@ -1362,6 +1355,15 @@ const EditToolChildren = ({ uri, changeDescription }: { uri: URI, changeDescript } +const EditToolHeaderButtons = ({ applyBoxId, uri, codeStr }: { applyBoxId: string, uri: URI, codeStr: string }) => { + const { currStreamState } = useApplyButtonState({ applyBoxId, uri }) + return
+ + + {currStreamState === 'idle-no-changes' && } + +
+} type ToolRequestState = 'awaiting_user' | 'running' @@ -1682,10 +1684,11 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent } = { messageIdx: messageIdx, tokenIdx: 'N/A', }) - componentParams.desc2 = } @@ -1764,6 +1767,10 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent } = { const { command } = params const { terminalId, resolveReason, result } = value + componentParams.desc2 = { terminalToolsService.openTerminal(terminalId) }} + /> + const resultStr = resolveReason.type === 'done' ? (resolveReason.exitCode !== 0 ? `\nError: exit code ${resolveReason.exitCode}` : null) : resolveReason.type === 'bgtask' ? null : resolveReason.type === 'timeout' ? `\n(partial results; request timed out)` : @@ -2052,7 +2059,7 @@ export const SidebarChat = () => { const proposed = toolNameSoFar && toolNames.includes(toolNameSoFar as ToolName) ? titleOfToolName[toolNameSoFar as ToolName]?.proposed : toolNameSoFar const toolTitle = typeof proposed === 'function' ? proposed(null) : proposed const currStreamingToolHTML = toolIsLoading ? - Getting parameters} /> + Generating} /> : null const allMessagesHTML = [...previousMessagesHTML, currStreamingMessageHTML, currStreamingToolHTML] diff --git a/src/vs/workbench/contrib/void/browser/toolsService.ts b/src/vs/workbench/contrib/void/browser/toolsService.ts index 314bc6ad..636a62b8 100644 --- a/src/vs/workbench/contrib/void/browser/toolsService.ts +++ b/src/vs/workbench/contrib/void/browser/toolsService.ts @@ -353,12 +353,15 @@ export class ToolsService implements IToolsService { if (this.commandBarService.getStreamState(uri) === 'streaming') { throw new Error(`The Apply model was already running. This can happen if two agents try editing the same file at the same time. Please try again in a moment.`) } - const res = await editCodeService.startApplying({ + const opts = { uri, applyStr: changeDescription, from: 'ClickApply', startBehavior: 'keep-conflicts', - }) + } as const + + await editCodeService.callBeforeStartApplying(opts) + const res = editCodeService.startApplying(opts) if (!res) throw new Error(`The Apply model did not start running on ${basename(uri.fsPath)}. Please try again.`) const [diffZoneURI, applyDonePromise] = res diff --git a/src/vs/workbench/contrib/void/browser/voidCommandBarService.ts b/src/vs/workbench/contrib/void/browser/voidCommandBarService.ts index 7711068d..529680da 100644 --- a/src/vs/workbench/contrib/void/browser/voidCommandBarService.ts +++ b/src/vs/workbench/contrib/void/browser/voidCommandBarService.ts @@ -173,7 +173,7 @@ export class VoidCommandBarService extends Disposable implements IVoidCommandBar } })) - this._register(this._editCodeService.onDidChangeDiffsInDiffZone(e => { + this._register(this._editCodeService.onDidChangeDiffsInDiffZoneNotStreaming(e => { for (const uri of this._listenToTheseURIs) { if (e.uri.fsPath !== uri.fsPath) continue // --- sortedURIs: no change diff --git a/src/vs/workbench/contrib/void/common/chatThreadServiceTypes.ts b/src/vs/workbench/contrib/void/common/chatThreadServiceTypes.ts index b4af2b89..aadf06f5 100644 --- a/src/vs/workbench/contrib/void/common/chatThreadServiceTypes.ts +++ b/src/vs/workbench/contrib/void/common/chatThreadServiceTypes.ts @@ -14,7 +14,7 @@ export type ToolMessage = { result: | { type: 'success'; params: ToolCallParams[T]; value: ToolResultType[T], } | { type: 'error'; params: ToolCallParams[T] | undefined; value: string } - | { type: 'rejected'; params: ToolCallParams[T] } + | { type: 'rejected'; params: ToolCallParams[T] } // user rejected } export type ToolRequestApproval = { role: 'tool_request'; diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index 29ac971a..b3b921df 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -42,10 +42,14 @@ ${tripleTick[1]}` // ======================================================== tools ======================================================== const paginationHelper = { - desc: `Very large results may be paginated (indicated in the result). Pagination fails gracefully if out of bounds or invalid page number.`, + desc: `Very large results may be paginated (a note will always be included if pagination took place). Pagination fails gracefully if out of bounds or invalid page number.`, param: { pageNumber: { type: 'number', description: 'The page number (default is the first page = 1).' }, } } as const +const uriParam = (object: string) => ({ + uri: { type: 'string', description: `The FULL path to the ${object}.` } +}) + export const voidTools = { // --- context-gathering (read/search/list) --- @@ -53,16 +57,16 @@ export const voidTools = { name: 'read_file', description: `Returns file contents of a given URI. ${paginationHelper.desc}`, params: { - uri: { type: 'string', description: undefined }, + ...uriParam('file'), ...paginationHelper.param, }, }, list_dir: { name: 'list_dir', - description: `Returns all file names and folder names in a given URI. ${paginationHelper.desc}`, + description: `Returns all file names and folder names in a given folder. ${paginationHelper.desc}`, params: { - uri: { type: 'string', description: undefined }, + ...uriParam('folder'), ...paginationHelper.param, }, }, @@ -91,7 +95,7 @@ export const voidTools = { name: 'create_uri', description: `Create 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 }, + ...uriParam('file or folder'), }, }, @@ -99,7 +103,7 @@ export const voidTools = { name: 'delete_uri', description: `Delete a file or folder at the given path. Fails gracefully if the file or folder does not exist.`, params: { - uri: { type: 'string', description: undefined }, + ...uriParam('file or folder'), params: { type: 'string', description: 'Return -r here to delete this URI and all descendants (if applicable). Default is the empty string.' } }, }, @@ -108,7 +112,7 @@ export const voidTools = { name: 'edit', description: `Edits the contents of a file, given the file's URI and a description. Fails gracefully if the file does not exist.`, params: { - uri: { type: 'string', description: undefined }, + ...uriParam('file'), changeDescription: { type: 'string', description: `\ - Your changeDescription should be a brief code description of the change you want to make, with comments like "// ... existing code ..." to condense your writing.