This commit is contained in:
Andrew Pareles 2025-04-15 01:51:59 -07:00
parent 6ce37f7627
commit 321876932a
4 changed files with 80 additions and 53 deletions

View file

@ -322,12 +322,12 @@ export const ApplyButtonsHTML = ({ codeStr, applyBoxId, reapplyIcon, uri }: { co
<IconShell1
Icon={X}
onClick={onReject}
{...tooltipPropsForApplyBlock({ tooltipName: 'Reject file' })}
{...tooltipPropsForApplyBlock({ tooltipName: 'Remove' })}
/>
<IconShell1
Icon={Check}
onClick={onAccept}
{...tooltipPropsForApplyBlock({ tooltipName: 'Accept file' })}
{...tooltipPropsForApplyBlock({ tooltipName: 'Keep' })}
/>
</>
}

View file

@ -20,14 +20,13 @@ import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js';
import { ChatMode, FeatureName, isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js';
import { WarningBox } from '../void-settings-tsx/WarningBox.js';
import { getModelCapabilities, getIsReasoningEnabledState } from '../../../../common/modelCapabilities.js';
import { AlertTriangle, Ban, Check, ChevronRight, Dot, FileIcon, Pencil, Undo, Undo2, X } from 'lucide-react';
import { AlertTriangle, Ban, Check, ChevronRight, Dot, FileIcon, Pencil, Undo, Undo2, X, Flag } from 'lucide-react';
import { ChatMessage, CheckpointEntry, StagingSelectionItem, ToolMessage } from '../../../../common/chatThreadServiceTypes.js';
import { LintErrorItem, ToolCallParams, ToolNameWithApproval } from '../../../../common/toolsServiceTypes.js';
import { ApplyButtonsHTML, CopyButton, IconShell1, JumpToFileButton, JumpToTerminalButton, StatusIndicator, StatusIndicatorForApplyButton, useApplyButtonState } from '../markdown/ApplyBlockHoverButtons.js';
import { IsRunningType } from '../../../chatThreadService.js';
import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectAllBg, rejectBg, rejectBorder } from '../../../../common/helpers/colors.js';
import { ToolName, toolNames } from '../../../../common/prompt/prompts.js';
import { error } from 'console';
import { RawToolCallObj } from '../../../../common/sendLLMMessageTypes.js';
import { MAX_FILE_CHARS_PAGE } from '../../../toolsService.js';
@ -1355,21 +1354,31 @@ const LintErrorChildren = ({ lintErrors }: { lintErrors: LintErrorItem[] }) => {
}
const EditToolLintErrors = ({ lintErrors }: { lintErrors: LintErrorItem[] }) => {
if (lintErrors.length === 0) return null;
const [isOpen, setIsOpen] = useState(false);
return (
<div className="w-full px-2">
<ToolHeaderWrapper className='!border-t-0' title={'Lint errors'} desc1={''} isOpen={isOpen} onClick={() => { setIsOpen(o => !o) }} >
<LintErrorChildren lintErrors={lintErrors} />
</ToolHeaderWrapper>
</div>
)
if (lintErrors.length === 0) return null;
const [isOpen, setIsOpen] = useState(false);
return (
<div className="w-full px-2 mt-0.5">
<div
className={`flex items-center cursor-pointer select-none transition-colors duration-150 pl-0 py-0.5 rounded group`}
onClick={() => setIsOpen(o => !o)}
style={{background: 'none'}}
>
<ChevronRight
className={`mr-1 h-4 w-4 flex-shrink-0 transition-transform duration-100 text-void-fg-4 group-hover:text-void-fg-3 ${isOpen ? 'rotate-90' : ''}`}
/>
<span className="font-medium text-void-fg-4 group-hover:text-void-fg-3 text-xs">Lint errors</span>
</div>
<div
className={`overflow-hidden transition-all duration-200 ease-in-out ${isOpen ? 'opacity-100 py-1' : 'max-h-0 opacity-0'} text-xs pl-5`}
>
<div className="flex flex-col gap-0.5 overflow-x-auto whitespace-nowrap text-void-fg-4 opacity-90 border-l-2 border-void-warning px-2 py-0.5">
{lintErrors.map((error, i) => (
<div key={i} className="">Lines {error.startLineNumber}-{error.endLineNumber}: {error.message}</div>
))}
</div>
</div>
</div>
);
}
@ -1445,7 +1454,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
const { params, result } = toolMessage
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
if (result.hasNextPage && params.pageNumber === 1) // first page
componentParams.desc2 = `(first ${Math.round(MAX_FILE_CHARS_PAGE) / 1000}k)`
componentParams.desc2 = `(truncated after ${Math.round(MAX_FILE_CHARS_PAGE) / 1000}k)`
else if (params.pageNumber > 1) // subsequent pages
componentParams.desc2 = `(part ${params.pageNumber})`
}
@ -1927,23 +1936,23 @@ const Checkpoint = ({ message, threadId, messageIdx, isCheckpointGhost, threadIs
const chatThreadService = accessor.get('IChatThreadService')
return <div
className={`flex items-center justify-center px-2 `}
>
<div
className={`flex items-center justify-center px-2 `}
>
<div
className={`
text-xs
text-void-fg-3
cursor-pointer select-none
${isCheckpointGhost ? 'opacity-50' : 'opacity-100'}
text-xs
text-void-fg-3
cursor-pointer select-none
${isCheckpointGhost ? 'opacity-50' : 'opacity-100'}
`}
onClick={() => {
if (threadIsRunning) return
chatThreadService.jumpToCheckpointBeforeMessageIdx({ threadId, messageIdx, jumpToUserModified: true })
}}
>
Checkpoint
</div>
</div>
>
Checkpoint
</div>
</div>
}
@ -2345,30 +2354,30 @@ const CommandBarInChat = () => {
const EditToolSoFar = ({ toolCallSoFar, }: { toolCallSoFar: RawToolCallObj }) => {
const uri = URI.file(toolCallSoFar.rawParams.uri ?? 'unknown')
const uri = URI.file(toolCallSoFar.rawParams.uri ?? 'unknown')
const title = titleOfToolName['edit_file'].proposed
const title = titleOfToolName['edit_file'].proposed
const uriDone = toolCallSoFar.doneParams.includes('uri')
const desc1 = <span className='flex items-center'>
{uriDone ?
getBasename(toolCallSoFar.rawParams['uri'] ?? 'unknown')
: `Generating`}
<IconLoading />
</span>
const uriDone = toolCallSoFar.doneParams.includes('uri')
const desc1 = <span className='flex items-center'>
{uriDone ?
getBasename(toolCallSoFar.rawParams['uri'] ?? 'unknown')
: `Generating`}
<IconLoading />
</span>
// If URI has not been specified
return <ToolHeaderWrapper
title={title}
desc1={desc1}
desc2={<JumpToFileButton uri={uri} />}
title={title}
desc1={desc1}
desc2={<JumpToFileButton uri={uri} />}
>
<EditToolChildren
uri={uri}
changeDescription={toolCallSoFar.rawParams.change_description ?? ''}
/>
<IconLoading />
</ToolHeaderWrapper>
</ToolHeaderWrapper>

View file

@ -25,6 +25,7 @@ import { Disposable } from '../../../../base/common/lifecycle.js';
import { localize2 } from '../../../../nls.js';
import { StagingSelectionItem } from '../common/chatThreadServiceTypes.js';
import { IChatThreadService } from './chatThreadService.js';
import { getActiveWindow } from '../../../../base/browser/dom.js';
// ---------- Register commands and keybindings ----------
@ -225,9 +226,13 @@ registerAction2(class extends Action2 {
metricsService.capture('Chat Navigation', { type: 'New Chat' })
stateService.setState({ isHistoryOpen: false, currentTab: 'chat' })
stateService.fireFocusChat()
const chatThreadService = accessor.get(IChatThreadService)
chatThreadService.openNewThread()
// focus
stateService.fireFocusChat()
const window = getActiveWindow()
window.requestAnimationFrame(() => stateService.fireFocusChat())
}
})

View file

@ -256,7 +256,12 @@ const _sendOpenAICompatibleChat = ({ messages, onText, onFinalMessage, onError,
fullReasoningSoFar += newReasoning
}
onText({ fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar })
onText({
fullText: fullTextSoFar,
fullReasoning: fullReasoningSoFar,
toolCall: isAToolName(toolName) ? { name: toolName, rawParams: {}, isDone: false, doneParams: [], id: toolId } : undefined,
})
}
// on final
if (!fullTextSoFar && !fullReasoningSoFar && !toolName) {
@ -402,6 +407,14 @@ const sendAnthropicChat = ({ messages, providerName, onText, onFinalMessage, onE
let fullToolName = ''
let fullToolParams = ''
const runOnText = () => {
onText({
fullText,
fullReasoning,
toolCall: isAToolName(fullToolName) ? { name: fullToolName, rawParams: {}, isDone: false, doneParams: [], id: 'dummy' } : undefined,
})
}
// there are no events for tool_use, it comes in at the end
stream.on('streamEvent', e => {
// start block
@ -409,22 +422,22 @@ const sendAnthropicChat = ({ messages, providerName, onText, onFinalMessage, onE
if (e.content_block.type === 'text') {
if (fullText) fullText += '\n\n' // starting a 2nd text block
fullText += e.content_block.text
onText({ fullText, fullReasoning, })
runOnText()
}
else if (e.content_block.type === 'thinking') {
if (fullReasoning) fullReasoning += '\n\n' // starting a 2nd reasoning block
fullReasoning += e.content_block.thinking
onText({ fullText, fullReasoning, })
runOnText()
}
else if (e.content_block.type === 'redacted_thinking') {
console.log('delta', e.content_block.type)
if (fullReasoning) fullReasoning += '\n\n' // starting a 2nd reasoning block
fullReasoning += '[redacted_thinking]'
onText({ fullText, fullReasoning, })
runOnText()
}
else if (e.content_block.type === 'tool_use') {
fullToolName += e.content_block.name ?? '' // anthropic gives us the tool name in the start block
onText({ fullText, fullReasoning, })
runOnText()
}
}
@ -432,15 +445,15 @@ const sendAnthropicChat = ({ messages, providerName, onText, onFinalMessage, onE
else if (e.type === 'content_block_delta') {
if (e.delta.type === 'text_delta') {
fullText += e.delta.text
onText({ fullText, fullReasoning, })
runOnText()
}
else if (e.delta.type === 'thinking_delta') {
fullReasoning += e.delta.thinking
onText({ fullText, fullReasoning, })
runOnText()
}
else if (e.delta.type === 'input_json_delta') { // tool use
fullToolParams += e.delta.partial_json ?? '' // anthropic gives us the partial delta (string) here - https://docs.anthropic.com/en/api/messages-streaming
onText({ fullText, fullReasoning, })
runOnText()
}
}
})