mirror of
https://github.com/voideditor/void
synced 2026-05-24 01:48:25 +00:00
Merge pull request #474 from voideditor/model-selection
Accept|Reject on Edit tool fix
This commit is contained in:
commit
301418bd12
12 changed files with 310 additions and 158 deletions
|
|
@ -1124,8 +1124,8 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
return
|
||||
}
|
||||
|
||||
public async callBeforeStartApplying(opts: CallBeforeStartApplyingOpts) {
|
||||
const uri = this._getURIBeforeStartApplying(opts)
|
||||
public async callBeforeApplyOrEdit(givenURI: URI | 'current') {
|
||||
const uri = this._uriOfGivenURI(givenURI)
|
||||
if (!uri) return
|
||||
await this._voidModelService.initializeModel(uri)
|
||||
await this._voidModelService.saveModel(uri) // save the URI
|
||||
|
|
@ -1200,7 +1200,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
}
|
||||
|
||||
|
||||
public instantlyApplyNewContent({ uri, newContent }: { uri: URI, newContent: string }) {
|
||||
public instantlyRewriteFile({ uri, newContent }: { uri: URI, newContent: string }) {
|
||||
// start diffzone
|
||||
const res = this._startStreamingDiffZone({
|
||||
uri,
|
||||
|
|
|
|||
|
|
@ -44,10 +44,10 @@ export interface IEditCodeService {
|
|||
|
||||
processRawKeybindingText(keybindingStr: string): string;
|
||||
|
||||
callBeforeStartApplying(opts: CallBeforeStartApplyingOpts): Promise<void>;
|
||||
callBeforeApplyOrEdit(uri: URI | 'current'): Promise<void>;
|
||||
startApplying(opts: StartApplyingOpts): [URI, Promise<void>] | null;
|
||||
instantlyApplySearchReplaceBlocks(opts: { uri: URI; searchReplaceBlocks: string }): void;
|
||||
instantlyApplyNewContent(opts: { uri: URI; newContent: string }): void;
|
||||
instantlyRewriteFile(opts: { uri: URI; newContent: string }): void;
|
||||
addCtrlKZone(opts: AddCtrlKOpts): number | undefined;
|
||||
removeCtrlKZone(opts: { diffareaid: number }): void;
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ const extensionBlacklist = [
|
|||
'codeium.codeium',
|
||||
'saoudrizwan.claude-dev', // cline
|
||||
'rooveterinaryinc.roo-cline', // roo
|
||||
'supermaven.supermaven' // supermaven
|
||||
// 'github.copilot',
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@
|
|||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { useState, useEffect, useCallback, useRef, Fragment } from 'react'
|
||||
import { useAccessor, useCommandBarState, useCommandBarURIListener, useSettingsState } from '../util/services.js'
|
||||
import { usePromise, useRefState } from '../util/helpers.js'
|
||||
import { isFeatureNameDisabled } from '../../../../common/voidSettingsTypes.js'
|
||||
import { URI } from '../../../../../../../base/common/uri.js'
|
||||
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 { getBasename, ListableToolItem, voidOpenFileFn, ToolChildrenWrapper } from '../sidebar-tsx/SidebarChat.js'
|
||||
import { PlacesType, VariantType } from 'react-tooltip'
|
||||
|
||||
enum CopyButtonText {
|
||||
|
|
@ -24,8 +24,9 @@ type IconButtonProps = {
|
|||
Icon: LucideIcon
|
||||
}
|
||||
|
||||
export const IconShell1 = ({ onClick, Icon, disabled, className, ...props }: IconButtonProps & React.ButtonHTMLAttributes<HTMLButtonElement>) => (
|
||||
<button
|
||||
export const IconShell1 = ({ onClick, Icon, disabled, className, ...props }: IconButtonProps & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
|
||||
|
||||
return <button
|
||||
disabled={disabled}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
|
|
@ -34,19 +35,19 @@ export const IconShell1 = ({ onClick, Icon, disabled, className, ...props }: Ico
|
|||
}}
|
||||
// border border-void-border-1 rounded
|
||||
className={`
|
||||
size-[18px]
|
||||
p-[2px]
|
||||
flex items-center justify-center
|
||||
text-sm text-void-fg-3
|
||||
hover:brightness-110
|
||||
disabled:opacity-50 disabled:cursor-not-allowed
|
||||
${className}
|
||||
size-[18px]
|
||||
p-[2px]
|
||||
flex items-center justify-center
|
||||
text-sm text-void-fg-3
|
||||
hover:brightness-110
|
||||
disabled:opacity-50 disabled:cursor-not-allowed
|
||||
${className}
|
||||
`}
|
||||
{...props}
|
||||
>
|
||||
<Icon />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// export const IconShell2 = ({ onClick, title, Icon, disabled, className }: IconButtonProps) => (
|
||||
|
|
@ -108,7 +109,7 @@ export const JumpToFileButton = ({ uri, ...props }: { uri: URI | 'current' } & R
|
|||
<IconShell1
|
||||
Icon={FileSymlink}
|
||||
onClick={() => {
|
||||
commandService.executeCommand('vscode.open', uri, { preview: true })
|
||||
voidOpenFileFn(uri, accessor)
|
||||
}}
|
||||
{...tooltipPropsForApplyBlock({ tooltipName: 'Go to file' })}
|
||||
{...props}
|
||||
|
|
@ -131,48 +132,41 @@ export const JumpToTerminalButton = ({ onClick }: { onClick: () => void }) => {
|
|||
|
||||
// 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 _applyingURIOfApplyBoxIdRef: { current: { [applyBoxId: string]: URI | undefined } } = { current: {} }
|
||||
|
||||
const getUriBeingApplied = (applyBoxId: string) => {
|
||||
return applyingURIOfApplyBoxIdRef.current[applyBoxId] ?? null
|
||||
return _applyingURIOfApplyBoxIdRef.current[applyBoxId] ?? null
|
||||
}
|
||||
|
||||
|
||||
export const useApplyButtonState = ({ applyBoxId, uri }: { applyBoxId: string, uri: URI | 'current' }) => {
|
||||
|
||||
const settingsState = useSettingsState()
|
||||
const isDisabled = !!isFeatureNameDisabled('Apply', settingsState) || !applyBoxId
|
||||
|
||||
export const useApplyStreamState = ({ applyBoxId }: { applyBoxId: string }) => {
|
||||
const accessor = useAccessor()
|
||||
const voidCommandBarService = accessor.get('IVoidCommandBarService')
|
||||
|
||||
const [_, rerender] = useState(0)
|
||||
|
||||
const getStreamState = useCallback(() => {
|
||||
const uri = getUriBeingApplied(applyBoxId)
|
||||
if (!uri) return 'idle-no-changes'
|
||||
return voidCommandBarService.getStreamState(uri)
|
||||
}, [voidCommandBarService, applyBoxId])
|
||||
|
||||
|
||||
const [currStreamStateRef, setStreamState] = useRefState(getStreamState())
|
||||
|
||||
const setApplying = useCallback((uri: URI | undefined) => {
|
||||
_applyingURIOfApplyBoxIdRef.current[applyBoxId] = uri ?? undefined
|
||||
setStreamState(getStreamState())
|
||||
}, [setStreamState, getStreamState, applyBoxId])
|
||||
|
||||
// listen for stream updates on this box
|
||||
useCommandBarURIListener(useCallback((uri_) => {
|
||||
const shouldUpdate = (
|
||||
getUriBeingApplied(applyBoxId)?.fsPath === uri_.fsPath
|
||||
|| (uri !== 'current' && uri.fsPath === uri_.fsPath)
|
||||
)
|
||||
if (shouldUpdate) {
|
||||
rerender(c => c + 1)
|
||||
const uri = getUriBeingApplied(applyBoxId)
|
||||
if (uri?.fsPath === uri_.fsPath) {
|
||||
setStreamState(getStreamState())
|
||||
}
|
||||
}, [applyBoxId, uri]))
|
||||
|
||||
const currStreamState = getStreamState()
|
||||
}, [setStreamState, applyBoxId, getStreamState]))
|
||||
|
||||
|
||||
return {
|
||||
getStreamState,
|
||||
isDisabled,
|
||||
currStreamState,
|
||||
}
|
||||
return { currStreamStateRef, setApplying }
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -202,10 +196,24 @@ const tooltipPropsForApplyBlock = ({ tooltipName, color = undefined, position =
|
|||
'data-tooltip-offset': offset,
|
||||
})
|
||||
|
||||
export const useEditToolStreamState = ({ applyBoxId, uri }: { applyBoxId: string, uri: URI }) => {
|
||||
const accessor = useAccessor()
|
||||
const voidCommandBarService = accessor.get('IVoidCommandBarService')
|
||||
const [streamState, setStreamState] = useState(voidCommandBarService.getStreamState(uri))
|
||||
// listen for stream updates on this box
|
||||
useCommandBarURIListener(useCallback((uri_) => {
|
||||
const shouldUpdate = uri.fsPath === uri_.fsPath
|
||||
if (shouldUpdate) { setStreamState(voidCommandBarService.getStreamState(uri)) }
|
||||
}, [voidCommandBarService, applyBoxId, uri]))
|
||||
|
||||
return { streamState, }
|
||||
}
|
||||
|
||||
export const StatusIndicatorForApplyButton = ({ applyBoxId, uri }: { applyBoxId: string, uri: URI | 'current' } & React.HTMLAttributes<HTMLDivElement>) => {
|
||||
|
||||
const { currStreamState } = useApplyButtonState({ applyBoxId, uri })
|
||||
const { currStreamStateRef } = useApplyStreamState({ applyBoxId })
|
||||
const currStreamState = currStreamStateRef.current
|
||||
|
||||
|
||||
const color = (
|
||||
currStreamState === 'idle-no-changes' ? 'dark' :
|
||||
|
|
@ -231,74 +239,88 @@ export const StatusIndicatorForApplyButton = ({ applyBoxId, uri }: { applyBoxId:
|
|||
}
|
||||
|
||||
|
||||
export const ApplyButtonsHTML = ({ codeStr, applyBoxId, uri }: { codeStr: string, applyBoxId: string, uri: URI | 'current' }) => {
|
||||
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 settingsState = useSettingsState()
|
||||
const isDisabled = !!isFeatureNameDisabled('Apply', settingsState) || !applyBoxId
|
||||
|
||||
const { currStreamStateRef, setApplying } = useApplyStreamState({ applyBoxId })
|
||||
|
||||
|
||||
const onClickSubmit = useCallback(async () => {
|
||||
if (isDisabled) return
|
||||
if (getStreamState() === 'streaming') return
|
||||
const opts = {
|
||||
if (currStreamStateRef.current === 'streaming') return
|
||||
|
||||
await editCodeService.callBeforeApplyOrEdit(uri)
|
||||
|
||||
const [newApplyingUri, applyDonePromise] = editCodeService.startApplying({
|
||||
from: 'ClickApply',
|
||||
applyStr: codeStr,
|
||||
uri: uri,
|
||||
startBehavior: 'reject-conflicts',
|
||||
} as const
|
||||
|
||||
await editCodeService.callBeforeStartApplying(opts)
|
||||
const [newApplyingUri, applyDonePromise] = editCodeService.startApplying(opts) ?? []
|
||||
}) ?? []
|
||||
console.log('setting!!!', newApplyingUri)
|
||||
setApplying(newApplyingUri)
|
||||
|
||||
// catch any errors by interrupting the stream
|
||||
applyDonePromise?.catch(e => {
|
||||
const uri = getUriBeingApplied(applyBoxId)
|
||||
if (uri) editCodeService.interruptURIStreaming({ uri: uri })
|
||||
})
|
||||
|
||||
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])
|
||||
|
||||
}, [setApplying, currStreamStateRef, editCodeService, codeStr, uri, applyBoxId, metricsService])
|
||||
|
||||
|
||||
const onInterrupt = useCallback(() => {
|
||||
if (getStreamState() !== 'streaming') return
|
||||
const onClickStop = useCallback(() => {
|
||||
if (currStreamStateRef.current !== 'streaming') return
|
||||
const uri = getUriBeingApplied(applyBoxId)
|
||||
if (!uri) return
|
||||
|
||||
editCodeService.interruptURIStreaming({ uri })
|
||||
metricsService.capture('Stop Apply', {})
|
||||
}, [getStreamState, applyBoxId, editCodeService, metricsService])
|
||||
}, [currStreamStateRef, applyBoxId, editCodeService, metricsService])
|
||||
|
||||
const onAccept = useCallback(() => {
|
||||
const uri = getUriBeingApplied(applyBoxId)
|
||||
if (uri) editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'accept', removeCtrlKs: false })
|
||||
}, [applyBoxId, editCodeService])
|
||||
if (uri) editCodeService.acceptOrRejectAllDiffAreas({ uri: uri, behavior: 'accept', removeCtrlKs: false })
|
||||
}, [uri, applyBoxId, editCodeService])
|
||||
|
||||
const onReject = useCallback(() => {
|
||||
const uri = getUriBeingApplied(applyBoxId)
|
||||
if (uri) editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'reject', removeCtrlKs: false })
|
||||
}, [applyBoxId, editCodeService])
|
||||
if (uri) editCodeService.acceptOrRejectAllDiffAreas({ uri: uri, behavior: 'reject', removeCtrlKs: false })
|
||||
}, [uri, applyBoxId, editCodeService])
|
||||
|
||||
|
||||
const currStreamState = currStreamStateRef.current
|
||||
console.log('currStreamState...', currStreamState)
|
||||
|
||||
if (currStreamState === 'streaming') {
|
||||
return <IconShell1
|
||||
|
||||
Icon={Square}
|
||||
onClick={onInterrupt}
|
||||
|
||||
onClick={onClickStop}
|
||||
{...tooltipPropsForApplyBlock({ tooltipName: 'Stop' })}
|
||||
/>
|
||||
}
|
||||
|
||||
if (currStreamState === 'idle-no-changes') {
|
||||
if (isDisabled) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
if (currStreamState === 'idle-no-changes') {
|
||||
return <IconShell1
|
||||
Icon={Play}
|
||||
onClick={onClickSubmit}
|
||||
|
|
@ -307,6 +329,76 @@ export const ApplyButtonsHTML = ({ codeStr, applyBoxId, uri }: { codeStr: string
|
|||
}
|
||||
|
||||
if (currStreamState === 'idle-has-changes') {
|
||||
return <Fragment>
|
||||
<IconShell1
|
||||
Icon={X}
|
||||
onClick={onReject}
|
||||
{...tooltipPropsForApplyBlock({ tooltipName: 'Remove' })}
|
||||
/>
|
||||
<IconShell1
|
||||
Icon={Check}
|
||||
onClick={onAccept}
|
||||
{...tooltipPropsForApplyBlock({ tooltipName: 'Keep' })}
|
||||
/>
|
||||
</Fragment>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const EditToolButtonsHTML = ({
|
||||
codeStr,
|
||||
applyBoxId,
|
||||
uri,
|
||||
type,
|
||||
}: {
|
||||
codeStr: string,
|
||||
applyBoxId: string,
|
||||
} & ({
|
||||
uri: URI,
|
||||
type: 'edit_file' | 'rewrite_file'
|
||||
})
|
||||
) => {
|
||||
const accessor = useAccessor()
|
||||
const editCodeService = accessor.get('IEditCodeService')
|
||||
const metricsService = accessor.get('IMetricsService')
|
||||
|
||||
const { streamState } = useEditToolStreamState({ applyBoxId, uri })
|
||||
const settingsState = useSettingsState()
|
||||
|
||||
const isDisabled = !!isFeatureNameDisabled('Chat', settingsState) || !applyBoxId
|
||||
|
||||
const onClickSubmit = useCallback(async () => {
|
||||
await editCodeService.callBeforeApplyOrEdit(uri)
|
||||
if (type === 'edit_file') {
|
||||
editCodeService.instantlyApplySearchReplaceBlocks({ uri, searchReplaceBlocks: codeStr })
|
||||
}
|
||||
else if (type === 'rewrite_file') {
|
||||
editCodeService.instantlyRewriteFile({ uri, newContent: codeStr })
|
||||
}
|
||||
}, [type, editCodeService, codeStr, uri, applyBoxId, metricsService])
|
||||
|
||||
const onAccept = useCallback(() => {
|
||||
editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'accept', removeCtrlKs: false })
|
||||
}, [uri, applyBoxId, editCodeService])
|
||||
|
||||
const onReject = useCallback(() => {
|
||||
editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'reject', removeCtrlKs: false })
|
||||
}, [uri, applyBoxId, editCodeService])
|
||||
|
||||
if (isDisabled) return null
|
||||
|
||||
if (streamState === 'idle-no-changes') {
|
||||
return <IconShell1
|
||||
Icon={RotateCw}
|
||||
onClick={onClickSubmit}
|
||||
{...tooltipPropsForApplyBlock({ tooltipName: 'Reapply' })}
|
||||
/>
|
||||
}
|
||||
|
||||
if (streamState === 'idle-has-changes') {
|
||||
return <>
|
||||
<IconShell1
|
||||
Icon={X}
|
||||
|
|
@ -340,7 +432,8 @@ export const BlockCodeApplyWrapper = ({
|
|||
}) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const { currStreamState } = useApplyButtonState({ applyBoxId, uri })
|
||||
const { currStreamStateRef } = useApplyStreamState({ applyBoxId })
|
||||
const currStreamState = currStreamStateRef.current
|
||||
|
||||
|
||||
const name = uri !== 'current' ?
|
||||
|
|
@ -348,7 +441,7 @@ export const BlockCodeApplyWrapper = ({
|
|||
name={<span className='not-italic'>{getBasename(uri.fsPath)}</span>}
|
||||
isSmall={true}
|
||||
showDot={false}
|
||||
onClick={() => { commandService.executeCommand('vscode.open', uri, { preview: true }) }}
|
||||
onClick={() => { voidOpenFileFn(uri, accessor) }}
|
||||
/>
|
||||
: <span>{language}</span>
|
||||
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ import { marked, MarkedToken, Token } from 'marked'
|
|||
import { convertToVscodeLang, detectLanguage } from '../../../../common/helpers/languageHelpers.js'
|
||||
import { BlockCodeApplyWrapper } from './ApplyBlockHoverButtons.js'
|
||||
import { useAccessor } from '../util/services.js'
|
||||
import { ScrollType } from '../../../../../../../editor/common/editorCommon.js'
|
||||
import { URI } from '../../../../../../../base/common/uri.js'
|
||||
import { isAbsolute } from '../../../../../../../base/common/path.js'
|
||||
import { separateOutFirstLine } from '../../../../common/helpers/util.js'
|
||||
import { BlockCode } from '../util/inputs.js'
|
||||
import { CodespanLocationLink } from '../../../../common/chatThreadServiceTypes.js'
|
||||
import { voidOpenFileFn } from '../sidebar-tsx/SidebarChat.js'
|
||||
|
||||
|
||||
export type ChatMessageLocation = {
|
||||
|
|
@ -134,27 +134,10 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string
|
|||
|
||||
|
||||
const onClick = () => {
|
||||
if (!link || !link.selection) return;
|
||||
|
||||
if (!link) return;
|
||||
const selection = link.selection
|
||||
|
||||
// open the file
|
||||
commandService.executeCommand('vscode.open', link.uri).then(() => {
|
||||
|
||||
// select the text
|
||||
setTimeout(() => {
|
||||
if (!selection) return;
|
||||
|
||||
const editor = editorService.getActiveCodeEditor()
|
||||
if (!editor) return;
|
||||
|
||||
editor.setSelection(selection)
|
||||
editor.revealRange(selection, ScrollType.Immediate)
|
||||
|
||||
}, 50) // needed when document was just opened and needs to initialize
|
||||
|
||||
})
|
||||
|
||||
// Use the updated voidOpenFileFn to open the file and handle selection
|
||||
voidOpenFileFn(link.uri, accessor, [link.selection.startLineNumber, link.selection.endLineNumber]);
|
||||
}
|
||||
|
||||
return <Codespan
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export const QuickEditChat = ({
|
|||
startBehavior: 'keep-conflicts',
|
||||
} as const
|
||||
|
||||
await editCodeService.callBeforeStartApplying(opts)
|
||||
await editCodeService.callBeforeApplyOrEdit(opts)
|
||||
const [newApplyingUri, applyDonePromise] = editCodeService.startApplying(opts) ?? []
|
||||
// catch any errors by interrupting the stream
|
||||
applyDonePromise?.catch(e => { if (newApplyingUri) editCodeService.interruptCtrlKStreaming({ diffareaid }) })
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import React, { ButtonHTMLAttributes, FormEvent, FormHTMLAttributes, Fragment, K
|
|||
|
||||
|
||||
import { useAccessor, useChatThreadsState, useChatThreadsStreamState, useSettingsState, useActiveURI, useCommandBarState, useFullChatThreadsStreamState } from '../util/services.js';
|
||||
import { ScrollType } from '../../../../../../../editor/common/editorCommon.js';
|
||||
|
||||
import { ChatMarkdownRender, ChatMessageLocation, getApplyBoxId } from '../markdown/ChatMarkdownRender.js';
|
||||
import { URI } from '../../../../../../../base/common/uri.js';
|
||||
|
|
@ -18,12 +19,13 @@ import { PastThreadsList } from './SidebarThreadSelector.js';
|
|||
import { VOID_CTRL_L_ACTION_ID } from '../../../actionIDs.js';
|
||||
import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js';
|
||||
import { ChatMode, displayInfoOfProviderName, FeatureName, isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js';
|
||||
import { ICommandService } from '../../../../../../../platform/commands/common/commands.js';
|
||||
import { WarningBox } from '../void-settings-tsx/WarningBox.js';
|
||||
import { getModelCapabilities, getIsReasoningEnabledState } from '../../../../common/modelCapabilities.js';
|
||||
import { AlertTriangle, File, Ban, Check, ChevronRight, Dot, FileIcon, Pencil, Undo, Undo2, X, Flag, Copy as CopyIcon, Info, CirclePlus, Ellipsis, CircleEllipsis, Folder, ALargeSmall, TypeOutline, Text } from 'lucide-react';
|
||||
import { ChatMessage, CheckpointEntry, StagingSelectionItem, ToolMessage } from '../../../../common/chatThreadServiceTypes.js';
|
||||
import { approvalTypeOfToolName, LintErrorItem, ToolApprovalType, toolApprovalTypes, ToolCallParams } from '../../../../common/toolsServiceTypes.js';
|
||||
import { ApplyButtonsHTML, CopyButton, IconShell1, JumpToFileButton, JumpToTerminalButton, StatusIndicator, StatusIndicatorForApplyButton, useApplyButtonState } from '../markdown/ApplyBlockHoverButtons.js';
|
||||
import { CopyButton, EditToolButtonsHTML, IconShell1, JumpToFileButton, JumpToTerminalButton, StatusIndicator, StatusIndicatorForApplyButton, useApplyStreamState, useEditToolStreamState } from '../markdown/ApplyBlockHoverButtons.js';
|
||||
import { IsRunningType } from '../../../chatThreadService.js';
|
||||
import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectAllBg, rejectBg, rejectBorder } from '../../../../common/helpers/colors.js';
|
||||
import { MAX_FILE_CHARS_PAGE, MAX_TERMINAL_INACTIVE_TIME, ToolName, toolNames } from '../../../../common/prompt/prompts.js';
|
||||
|
|
@ -530,6 +532,49 @@ export const getBasename = (pathStr: string, parts: number = 1) => {
|
|||
}
|
||||
|
||||
|
||||
|
||||
// Open file utility function
|
||||
export const voidOpenFileFn = (
|
||||
uri: URI,
|
||||
accessor: ReturnType<typeof useAccessor>,
|
||||
range?: [number, number]
|
||||
) => {
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const editorService = accessor.get('ICodeEditorService')
|
||||
|
||||
// Get editor selection from CodeSelection range
|
||||
let editorSelection = undefined;
|
||||
|
||||
// If we have a selection, create an editor selection from the range
|
||||
if (range) {
|
||||
editorSelection = {
|
||||
startLineNumber: range[0],
|
||||
startColumn: 1,
|
||||
endLineNumber: range[1],
|
||||
endColumn: Number.MAX_SAFE_INTEGER,
|
||||
};
|
||||
}
|
||||
|
||||
// open the file
|
||||
commandService.executeCommand('vscode.open', uri).then(() => {
|
||||
|
||||
// select the text
|
||||
setTimeout(() => {
|
||||
if (!editorSelection) return;
|
||||
|
||||
const editor = editorService.getActiveCodeEditor()
|
||||
if (!editor) return;
|
||||
|
||||
editor.setSelection(editorSelection)
|
||||
editor.revealRange(editorSelection, ScrollType.Immediate)
|
||||
|
||||
}, 50) // needed when document was just opened and needs to initialize
|
||||
|
||||
})
|
||||
|
||||
};
|
||||
|
||||
|
||||
export const SelectedFiles = (
|
||||
{ type, selections, setSelections, showProspectiveSelections, messageIdx, }:
|
||||
| { type: 'past', selections: StagingSelectionItem[]; setSelections?: undefined, showProspectiveSelections?: undefined, messageIdx: number, }
|
||||
|
|
@ -640,11 +685,7 @@ export const SelectedFiles = (
|
|||
setSelections([...selections, selection])
|
||||
}
|
||||
else if (selection.type === 'File') { // open files
|
||||
|
||||
commandService.executeCommand('vscode.open', selection.uri, {
|
||||
preview: true,
|
||||
// preserveFocus: false,
|
||||
});
|
||||
voidOpenFileFn(selection.uri, accessor);
|
||||
|
||||
const wasAddedAsCurrentFile = selection.state.wasAddedAsCurrentFile
|
||||
if (wasAddedAsCurrentFile) {
|
||||
|
|
@ -658,10 +699,7 @@ export const SelectedFiles = (
|
|||
}
|
||||
}
|
||||
else if (selection.type === 'CodeSelection') {
|
||||
commandService.executeCommand('vscode.open', selection.uri, {
|
||||
preview: true,
|
||||
// TODO!!! open in range
|
||||
});
|
||||
voidOpenFileFn(selection.uri, accessor, selection.range);
|
||||
}
|
||||
else if (selection.type === 'Folder') {
|
||||
// TODO!!! reveal in tree
|
||||
|
|
@ -714,6 +752,7 @@ type ToolHeaderParams = {
|
|||
icon?: React.ReactNode;
|
||||
title: React.ReactNode;
|
||||
desc1: React.ReactNode;
|
||||
desc1OnClick?: () => void;
|
||||
desc2?: React.ReactNode;
|
||||
isError?: boolean;
|
||||
info?: string;
|
||||
|
|
@ -733,6 +772,7 @@ const ToolHeaderWrapper = ({
|
|||
icon,
|
||||
title,
|
||||
desc1,
|
||||
desc1OnClick,
|
||||
desc1Info,
|
||||
desc2,
|
||||
numResults,
|
||||
|
|
@ -754,37 +794,51 @@ const ToolHeaderWrapper = ({
|
|||
const isDropdown = children !== undefined // null ALLOWS dropdown
|
||||
const isClickable = !!(isDropdown || onClick)
|
||||
|
||||
const isDesc1Clickable = !!desc1OnClick
|
||||
|
||||
const desc1HTML = <span
|
||||
className={`text-void-fg-4 text-xs italic truncate ml-2
|
||||
${isDesc1Clickable ? 'cursor-pointer hover:brightness-125 transition-all duration-150' : ''}
|
||||
`}
|
||||
onClick={desc1OnClick}
|
||||
{...desc1Info ? {
|
||||
'data-tooltip-id': 'void-tooltip',
|
||||
'data-tooltip-content': desc1Info,
|
||||
'data-tooltip-place': 'top',
|
||||
'data-tooltip-delay-show': 1000,
|
||||
} : {}}
|
||||
>{desc1}</span>
|
||||
|
||||
return (<div className=''>
|
||||
<div className={`w-full border border-void-border-3 rounded px-2 py-1 bg-void-bg-3 overflow-hidden ${className}`}>
|
||||
{/* header */}
|
||||
<div className={`select-none flex items-center min-h-[24px]`}>
|
||||
<div className={`flex items-center w-full gap-x-2 overflow-hidden justify-between ${isRejected ? 'line-through' : ''}`}>
|
||||
{/* left */}
|
||||
<div className={`
|
||||
ml-1
|
||||
<div // container for if desc1 is clickable
|
||||
className='ml-1 flex items-center overflow-hidden'
|
||||
>
|
||||
{/* title eg "> Edited File" */}
|
||||
<div className={`
|
||||
flex items-center min-w-0 overflow-hidden grow
|
||||
${isClickable ? 'cursor-pointer hover:brightness-125 transition-all duration-150' : ''}
|
||||
`}
|
||||
onClick={() => {
|
||||
if (isDropdown) { setIsOpen(v => !v); }
|
||||
if (onClick) { onClick(); }
|
||||
}}
|
||||
>
|
||||
{isDropdown && (<ChevronRight
|
||||
className={`
|
||||
onClick={() => {
|
||||
if (isDropdown) { setIsOpen(v => !v); }
|
||||
if (onClick) { onClick(); }
|
||||
}}
|
||||
>
|
||||
{isDropdown && (<ChevronRight
|
||||
className={`
|
||||
text-void-fg-3 mr-0.5 h-4 w-4 flex-shrink-0 transition-transform duration-100 ease-[cubic-bezier(0.4,0,0.2,1)]
|
||||
${isExpanded ? 'rotate-90' : ''}
|
||||
`}
|
||||
/>)}
|
||||
<span className="text-void-fg-3 flex-shrink-0">{title}</span>
|
||||
<span className="text-void-fg-4 text-xs italic truncate ml-2"
|
||||
{...desc1Info ? {
|
||||
'data-tooltip-id': 'void-tooltip',
|
||||
'data-tooltip-content': desc1Info,
|
||||
'data-tooltip-place': 'top',
|
||||
'data-tooltip-delay-show': 1000,
|
||||
} : {}}
|
||||
>{desc1}</span>
|
||||
/>)}
|
||||
<span className="text-void-fg-3 flex-shrink-0">{title}</span>
|
||||
|
||||
{!isDesc1Clickable && desc1HTML}
|
||||
</div>
|
||||
{isDesc1Clickable && desc1HTML}
|
||||
</div>
|
||||
|
||||
{/* right */}
|
||||
|
|
@ -849,8 +903,9 @@ const EditTool = ({ toolMessage, threadId, messageIdx, content }: Parameters<Res
|
|||
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor)
|
||||
const icon = null
|
||||
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, }
|
||||
const { rawParams, params, name } = toolMessage
|
||||
const desc1OnClick = () => voidOpenFileFn(params.uri, accessor)
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1OnClick, desc1Info, isError, icon, isRejected, }
|
||||
|
||||
if (toolMessage.type === 'running_now' || toolMessage.type === 'tool_request') {
|
||||
componentParams.children = <ToolChildrenWrapper className='bg-void-bg-3'>
|
||||
|
|
@ -859,7 +914,7 @@ const EditTool = ({ toolMessage, threadId, messageIdx, content }: Parameters<Res
|
|||
code={content}
|
||||
/>
|
||||
</ToolChildrenWrapper>
|
||||
componentParams.desc2 = <JumpToFileButton uri={params.uri} />
|
||||
// JumpToFileButton removed in favor of FileLinkText
|
||||
}
|
||||
else if (toolMessage.type === 'success' || toolMessage.type === 'rejected' || toolMessage.type === 'tool_error') {
|
||||
// add apply box
|
||||
|
|
@ -872,6 +927,7 @@ const EditTool = ({ toolMessage, threadId, messageIdx, content }: Parameters<Res
|
|||
applyBoxId={applyBoxId}
|
||||
uri={params.uri}
|
||||
codeStr={content}
|
||||
toolName={name}
|
||||
/>
|
||||
|
||||
// add children
|
||||
|
|
@ -1364,12 +1420,12 @@ const getTitle = (toolMessage: Pick<ChatMessage & { role: 'tool' }, 'name' | 'ty
|
|||
|
||||
|
||||
const toolNameToDesc = (toolName: ToolName, _toolParams: ToolCallParams[ToolName] | undefined, accessor: ReturnType<typeof useAccessor>): {
|
||||
desc1: string,
|
||||
desc1: React.ReactNode,
|
||||
desc1Info?: string,
|
||||
} => {
|
||||
|
||||
if (!_toolParams) {
|
||||
return { desc1: '' };
|
||||
return { desc1: '', };
|
||||
}
|
||||
|
||||
const x = {
|
||||
|
|
@ -1620,14 +1676,13 @@ const BottomChildren = ({ children, title }: { children: React.ReactNode, title:
|
|||
}
|
||||
|
||||
|
||||
const EditToolHeaderButtons = ({ applyBoxId, uri, codeStr }: { applyBoxId: string, uri: URI, codeStr: string }) => {
|
||||
const { currStreamState } = useApplyButtonState({ applyBoxId, uri })
|
||||
const EditToolHeaderButtons = ({ applyBoxId, uri, codeStr, toolName }: { applyBoxId: string, uri: URI, codeStr: string, toolName: 'edit_file' | 'rewrite_file' }) => {
|
||||
const { streamState } = useEditToolStreamState({ applyBoxId, uri })
|
||||
return <div className='flex items-center gap-1'>
|
||||
|
||||
|
||||
<StatusIndicatorForApplyButton applyBoxId={applyBoxId} uri={uri} />
|
||||
<JumpToFileButton uri={uri} />
|
||||
{currStreamState === 'idle-no-changes' && <CopyButton codeStr={codeStr} toolTipName='Copy' />}
|
||||
{streamState === 'idle-no-changes' && <CopyButton codeStr={codeStr} toolTipName='Copy' />}
|
||||
<EditToolButtonsHTML type={toolName} codeStr={codeStr} applyBoxId={applyBoxId} uri={uri} />
|
||||
</div>
|
||||
}
|
||||
|
||||
|
|
@ -1781,16 +1836,18 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, }
|
||||
|
||||
let range: [number, number] | undefined = undefined
|
||||
if (toolMessage.params.startLine !== null || toolMessage.params.endLine !== null) {
|
||||
const start = toolMessage.params.startLine === null ? `1` : `${toolMessage.params.startLine}`
|
||||
const end = toolMessage.params.endLine === null ? `` : `${toolMessage.params.endLine}`
|
||||
const addStr = `(${start}-${end})`
|
||||
componentParams.desc1 += ` ${addStr}`
|
||||
range = [params.startLine || 1, params.endLine || 1]
|
||||
}
|
||||
|
||||
if (toolMessage.type === 'success') {
|
||||
const { result } = toolMessage
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor, range) }
|
||||
if (result.hasNextPage && params.pageNumber === 1) // first page
|
||||
componentParams.desc2 = `(truncated after ${Math.round(MAX_FILE_CHARS_PAGE) / 1000}k)`
|
||||
else if (params.pageNumber > 1) // subsequent pages
|
||||
|
|
@ -1798,7 +1855,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
}
|
||||
else if (toolMessage.type === 'tool_error') {
|
||||
const { result } = toolMessage
|
||||
componentParams.desc2 = <JumpToFileButton uri={params.uri} />
|
||||
// JumpToFileButton removed in favor of FileLinkText
|
||||
componentParams.bottomChildren = <BottomChildren title='Error'>
|
||||
<CodeChildren>
|
||||
{result}
|
||||
|
|
@ -1889,7 +1946,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
name={`${child.name}${child.isDirectory ? '/' : ''}`}
|
||||
className='w-full overflow-auto'
|
||||
onClick={() => {
|
||||
commandService.executeCommand('vscode.open', child.uri, { preview: true })
|
||||
voidOpenFileFn(child.uri, accessor)
|
||||
// commandService.executeCommand('workbench.view.explorer'); // open in explorer folders view instead
|
||||
// explorerService.select(child.uri, true);
|
||||
}}
|
||||
|
|
@ -1940,7 +1997,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
{result.uris.map((uri, i) => (<ListableToolItem key={i}
|
||||
name={getBasename(uri.fsPath)}
|
||||
className='w-full overflow-auto'
|
||||
onClick={() => { commandService.executeCommand('vscode.open', uri, { preview: true }) }}
|
||||
onClick={() => { voidOpenFileFn(uri, accessor) }}
|
||||
/>))}
|
||||
{result.hasNextPage &&
|
||||
<ListableToolItem name={'Results truncated.'} isSmall={true} className='w-full overflow-auto' />
|
||||
|
|
@ -1995,7 +2052,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
{result.uris.map((uri, i) => (<ListableToolItem key={i}
|
||||
name={getBasename(uri.fsPath)}
|
||||
className='w-full overflow-auto'
|
||||
onClick={() => { commandService.executeCommand('vscode.open', uri, { preview: true }) }}
|
||||
onClick={() => { voidOpenFileFn(uri, accessor) }}
|
||||
/>))}
|
||||
{result.hasNextPage &&
|
||||
<ListableToolItem name={`Results truncated.`} isSmall={true} className='w-full overflow-auto' />
|
||||
|
|
@ -2085,7 +2142,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
|
||||
if (toolMessage.type === 'success') {
|
||||
const { result } = toolMessage
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) }
|
||||
if (result.lintErrors)
|
||||
componentParams.children = <LintErrorChildren lintErrors={result.lintErrors} />
|
||||
else
|
||||
|
|
@ -2094,7 +2151,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
}
|
||||
else if (toolMessage.type === 'tool_error') {
|
||||
const { result } = toolMessage
|
||||
if (params) componentParams.desc2 = <JumpToFileButton uri={params.uri} />
|
||||
// JumpToFileButton removed in favor of FileLinkText
|
||||
componentParams.bottomChildren = <BottomChildren title='Error'>
|
||||
<CodeChildren>
|
||||
{result}
|
||||
|
|
@ -2126,14 +2183,14 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
|
||||
if (toolMessage.type === 'success') {
|
||||
const { result } = toolMessage
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) }
|
||||
}
|
||||
else if (toolMessage.type === 'rejected') {
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) }
|
||||
}
|
||||
else if (toolMessage.type === 'tool_error') {
|
||||
const { result } = toolMessage
|
||||
if (params) { componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) } }
|
||||
if (params) { componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) } }
|
||||
componentParams.bottomChildren = <BottomChildren title='Error'>
|
||||
<CodeChildren>
|
||||
{result}
|
||||
|
|
@ -2168,14 +2225,14 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
|
||||
if (toolMessage.type === 'success') {
|
||||
const { result } = toolMessage
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) }
|
||||
}
|
||||
else if (toolMessage.type === 'rejected') {
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) }
|
||||
}
|
||||
else if (toolMessage.type === 'tool_error') {
|
||||
const { result } = toolMessage
|
||||
if (params) { componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) } }
|
||||
if (params) { componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) } }
|
||||
componentParams.bottomChildren = <BottomChildren title='Error'>
|
||||
<CodeChildren>
|
||||
{result}
|
||||
|
|
@ -2184,11 +2241,11 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
}
|
||||
else if (toolMessage.type === 'running_now') {
|
||||
const { result } = toolMessage
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) }
|
||||
}
|
||||
else if (toolMessage.type === 'tool_request') {
|
||||
const { result } = toolMessage
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) }
|
||||
}
|
||||
|
||||
return <ToolHeaderWrapper {...componentParams} />
|
||||
|
|
@ -2558,7 +2615,7 @@ const CommandBarInChat = () => {
|
|||
|
||||
const fileNameHTML = <div
|
||||
className="flex items-center gap-1.5 text-void-fg-3 hover:brightness-125 transition-all duration-200 cursor-pointer"
|
||||
onClick={() => commandService.executeCommand('vscode.open', uri, { preview: true })}
|
||||
onClick={() => voidOpenFileFn(uri, accessor)}
|
||||
>
|
||||
{/* <FileIcon size={14} className="text-void-fg-3" /> */}
|
||||
<span className="text-void-fg-3">{basename}</span>
|
||||
|
|
@ -2683,6 +2740,9 @@ const CommandBarInChat = () => {
|
|||
|
||||
const EditToolSoFar = ({ toolCallSoFar, }: { toolCallSoFar: RawToolCallObj }) => {
|
||||
|
||||
|
||||
const accessor = useAccessor()
|
||||
|
||||
const uri = toolCallSoFar.rawParams.uri ? URI.file(toolCallSoFar.rawParams.uri) : undefined
|
||||
|
||||
const title = titleOfToolName[toolCallSoFar.name].proposed
|
||||
|
|
@ -2695,11 +2755,13 @@ const EditToolSoFar = ({ toolCallSoFar, }: { toolCallSoFar: RawToolCallObj }) =>
|
|||
<IconLoading />
|
||||
</span>
|
||||
|
||||
const desc1OnClick = () => { uri && voidOpenFileFn(uri, accessor) }
|
||||
|
||||
// If URI has not been specified
|
||||
return <ToolHeaderWrapper
|
||||
title={title}
|
||||
desc1={desc1}
|
||||
desc2={uri && <JumpToFileButton uri={uri} />}
|
||||
desc1OnClick={desc1OnClick}
|
||||
>
|
||||
<EditToolChildren
|
||||
uri={uri}
|
||||
|
|
|
|||
|
|
@ -220,13 +220,14 @@ const PastThreadElement = ({ pastThread, idx, hoveredIdx, setHoveredIdx, isRunni
|
|||
const numMessages = pastThread.messages.filter((msg) => msg.role === 'assistant' || msg.role === 'user').length;
|
||||
|
||||
const detailsHTML = <span
|
||||
className='gap-1 inline-flex items-center'
|
||||
// data-tooltip-id='void-tooltip'
|
||||
// data-tooltip-content={`Last modified ${formatTime(new Date(pastThread.lastModified))}`}
|
||||
// data-tooltip-place='top'
|
||||
>
|
||||
<span>{`(${numMessages})`}</span>
|
||||
<span className='opacity-60'>{numMessages}</span>
|
||||
{` `}
|
||||
{formatDate(new Date(pastThread.lastModified))}
|
||||
{/* {` messages `} */}
|
||||
</span>
|
||||
|
||||
return <div
|
||||
|
|
@ -249,8 +250,13 @@ const PastThreadElement = ({ pastThread, idx, hoveredIdx, setHoveredIdx, isRunni
|
|||
:
|
||||
null}
|
||||
{/* name */}
|
||||
<span className="truncate overflow-hidden text-ellipsis">{firstMsg}</span>
|
||||
<span className="truncate overflow-hidden text-ellipsis"
|
||||
data-tooltip-id='void-tooltip'
|
||||
data-tooltip-content={numMessages + ' messages'}
|
||||
data-tooltip-place='top'
|
||||
>{firstMsg}</span>
|
||||
|
||||
{/* <span className='opacity-60'>{`(${numMessages})`}</span> */}
|
||||
</span>
|
||||
|
||||
<div className="flex items-center gap-x-1 opacity-60">
|
||||
|
|
|
|||
|
|
@ -9,10 +9,13 @@ type ReturnType<T> = [
|
|||
|
||||
// use this if state might be too slow to catch
|
||||
export const useRefState = <T,>(initVal: T): ReturnType<T> => {
|
||||
const [_, _setState] = useState(false)
|
||||
// this actually makes a difference being an int, not a boolean.
|
||||
// if it's a boolean and changes happen to fast, it goes with old values and leads to *very* weird bugs (like returning JSX, but not actually rendering it)
|
||||
const [_s, _setState] = useState(0)
|
||||
|
||||
const ref = useRef<T>(initVal)
|
||||
const setState = useCallback((newVal: T) => {
|
||||
_setState(n => !n) // call rerender
|
||||
_setState(n => n + 1) // call rerender
|
||||
ref.current = newVal
|
||||
}, [])
|
||||
return [ref, setState]
|
||||
|
|
|
|||
|
|
@ -400,7 +400,8 @@ export class ToolsService implements IToolsService {
|
|||
if (this.commandBarService.getStreamState(uri) === 'streaming') {
|
||||
throw new Error(`Another LLM is currently making changes to this file. Please stop streaming for now and ask the user to resume later.`)
|
||||
}
|
||||
editCodeService.instantlyApplyNewContent({ uri, newContent })
|
||||
await editCodeService.callBeforeApplyOrEdit(uri)
|
||||
editCodeService.instantlyRewriteFile({ uri, newContent })
|
||||
// at end, get lint errors
|
||||
const lintErrorsPromise = Promise.resolve().then(async () => {
|
||||
await timeout(2000)
|
||||
|
|
@ -415,6 +416,7 @@ export class ToolsService implements IToolsService {
|
|||
if (this.commandBarService.getStreamState(uri) === 'streaming') {
|
||||
throw new Error(`Another LLM is currently making changes to this file. Please stop streaming for now and ask the user to resume later.`)
|
||||
}
|
||||
await editCodeService.callBeforeApplyOrEdit(uri)
|
||||
editCodeService.instantlyApplySearchReplaceBlocks({ uri, searchReplaceBlocks })
|
||||
|
||||
// at end, get lint errors
|
||||
|
|
|
|||
|
|
@ -463,7 +463,7 @@ ${directoryStr}
|
|||
details.push(`Many tools only work if the user has a workspace open.`)
|
||||
}
|
||||
else {
|
||||
details.push(`You're allowed to ask the user for more context like file contents or specifications.`)
|
||||
details.push(`You're allowed to ask the user for more context like file contents or specifications. If this comes up, tell them to reference files and folders by typing @.`)
|
||||
}
|
||||
|
||||
if (mode === 'agent') {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ export const approvalTypeOfToolName: Partial<{ [T in ToolName]?: 'edits' | 'term
|
|||
'edit_file': 'edits',
|
||||
'run_command': 'terminal',
|
||||
'run_persistent_command': 'terminal',
|
||||
'open_persistent_terminal': 'terminal',
|
||||
'kill_persistent_terminal': 'terminal',
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue