mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
add visual feedback for tool that's being loaded (eg edit tool)
This commit is contained in:
parent
eed71d9582
commit
6a6cb56d87
6 changed files with 109 additions and 57 deletions
|
|
@ -117,6 +117,8 @@ export type ThreadStreamState = {
|
|||
streamingToken?: string;
|
||||
messageSoFar?: string;
|
||||
reasoningSoFar?: string;
|
||||
toolNameSoFar?: string;
|
||||
toolParamsSoFar?: string;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -874,11 +876,13 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
modelSelection,
|
||||
modelSelectionOptions,
|
||||
logging: { loggingName: `Chat - ${chatMode}`, loggingExtras: { threadId, nMessagesSent, chatMode } },
|
||||
onText: ({ fullText, fullReasoning }) => { this._setStreamState(threadId, { messageSoFar: fullText, reasoningSoFar: fullReasoning }, 'merge') },
|
||||
onText: ({ fullText, fullReasoning, fullToolName, fullToolParams }) => {
|
||||
this._setStreamState(threadId, { messageSoFar: fullText, reasoningSoFar: fullReasoning, toolNameSoFar: fullToolName, toolParamsSoFar: fullToolParams }, 'merge')
|
||||
},
|
||||
onFinalMessage: async ({ fullText, toolCalls, fullReasoning, anthropicReasoning }) => {
|
||||
this._addMessageToThread(threadId, { role: 'assistant', content: fullText, reasoning: fullReasoning, anthropicReasoning })
|
||||
// added to history and no longer streaming this, so clear messages so far and streamingToken (but do not stop isRunning)
|
||||
this._setStreamState(threadId, { messageSoFar: undefined, reasoningSoFar: undefined, streamingToken: undefined, }, 'merge')
|
||||
this._setStreamState(threadId, { messageSoFar: undefined, reasoningSoFar: undefined, streamingToken: undefined, toolNameSoFar: undefined, toolParamsSoFar: undefined }, 'merge')
|
||||
// resolve with tool calls
|
||||
resMessageIsDonePromise(toolCalls)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1695,14 +1695,17 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
|
||||
// if this is the first time we're seeing this block, add it as a diffarea so we can start streaming in it
|
||||
if (!(blockNum in addedTrackingZoneOfBlockNum)) {
|
||||
|
||||
|
||||
const originalBounds = findTextInCode(block.orig, originalFileCode)
|
||||
// if error
|
||||
if (typeof originalBounds === 'string') {
|
||||
console.log('Error finding text in code:')
|
||||
console.log('--------------Error finding text in code:')
|
||||
console.log('originalFileCode', { originalFileCode })
|
||||
console.log('fullText', { fullText })
|
||||
console.log('error:', originalBounds)
|
||||
console.log('block.orig:', block.orig)
|
||||
console.log('---------')
|
||||
const content = errContentOfInvalidStr(originalBounds, block.orig, blockNum, blocks)
|
||||
messages.push(
|
||||
{ role: 'assistant', content: fullText, anthropicReasoning: null }, // latest output
|
||||
|
|
@ -1710,10 +1713,14 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
)
|
||||
|
||||
// REVERT ALL BLOCKS
|
||||
currStreamingBlockNum = 0
|
||||
latestStreamLocationMutable = null
|
||||
shouldUpdateOrigStreamStyle = true
|
||||
oldBlocks = []
|
||||
for (const trackingZone of addedTrackingZoneOfBlockNum)
|
||||
this._deleteTrackingZone(trackingZone)
|
||||
addedTrackingZoneOfBlockNum.splice(0, Infinity)
|
||||
|
||||
this._writeURIText(uri, originalFileCode, 'wholeFileRange', { shouldRealignDiffAreas: true })
|
||||
|
||||
// abort and resolve
|
||||
|
|
@ -1729,7 +1736,14 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
return
|
||||
}
|
||||
|
||||
console.log('---------adding-------')
|
||||
console.log('CURRENT TEXT!!!', { current: model?.getValue() })
|
||||
console.log('block', deepClone(block))
|
||||
console.log('origBounds', originalBounds)
|
||||
|
||||
|
||||
const [startLine, endLine] = convertOriginalRangeToFinalRange(originalBounds)
|
||||
console.log('start end', startLine, endLine)
|
||||
|
||||
// otherwise if no error, add the position as a diffarea
|
||||
const adding: Omit<TrackingZone<SearchReplaceDiffAreaMetadata>, 'diffareaid'> = {
|
||||
|
|
@ -1802,9 +1816,9 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
addedTrackingZoneOfBlockNum.sort((a, b) => a.metadata.originalBounds[0] - b.metadata.originalBounds[0])
|
||||
|
||||
const { model } = this._voidModelService.getModel(uri)
|
||||
console.log('CURRENT!!!', { current: model?.getValue() })
|
||||
console.log('ADDED', addedTrackingZoneOfBlockNum)
|
||||
console.log('BLOX', blocks)
|
||||
console.log('CURRENT TEXT!!!', { current: model?.getValue() })
|
||||
console.log('addedTrackingZoneOfBlockNum', addedTrackingZoneOfBlockNum)
|
||||
console.log('blocks', deepClone(blocks))
|
||||
|
||||
for (let blockNum = addedTrackingZoneOfBlockNum.length - 1; blockNum >= 0; blockNum -= 1) {
|
||||
const { originalBounds } = addedTrackingZoneOfBlockNum[blockNum].metadata
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import { WarningBox } from '../void-settings-tsx/WarningBox.js';
|
|||
import { getModelCapabilities, getIsResoningEnabledState } from '../../../../common/modelCapabilities.js';
|
||||
import { AlertTriangle, Ban, ChevronRight, Dot, Pencil, X } from 'lucide-react';
|
||||
import { ChatMessage, StagingSelectionItem, ToolMessage, ToolRequestApproval } from '../../../../common/chatThreadServiceTypes.js';
|
||||
import { ToolCallParams, ToolName, ToolNameWithApproval } from '../../../../common/toolsServiceTypes.js';
|
||||
import { ToolCallParams, ToolName, toolNames, ToolNameWithApproval } from '../../../../common/toolsServiceTypes.js';
|
||||
import { JumpToFileButton, useApplyButtonHTML } from '../markdown/ApplyBlockHoverButtons.js';
|
||||
import { IsRunningType } from '../../../chatThreadService.js';
|
||||
|
||||
|
|
@ -1040,7 +1040,7 @@ const ProseWrapper = ({ children }: { children: React.ReactNode }) => {
|
|||
{children}
|
||||
</div>
|
||||
}
|
||||
const AssistantMessageComponent = ({ chatMessage, isCommitted, messageIdx, isLast, chatIsRunning }: { chatMessage: ChatMessage & { role: 'assistant' }, messageIdx: number, isCommitted: boolean, isLast: boolean, chatIsRunning: IsRunningType }) => {
|
||||
const AssistantMessageComponent = ({ chatMessage, isCommitted, messageIdx, isLast, chatIsRunning, isToolBeingWritten }: { chatMessage: ChatMessage & { role: 'assistant' }, messageIdx: number, isCommitted: boolean, isLast: boolean, chatIsRunning: IsRunningType, isToolBeingWritten: boolean }) => {
|
||||
|
||||
const accessor = useAccessor()
|
||||
const chatThreadsService = accessor.get('IChatThreadService')
|
||||
|
|
@ -1058,7 +1058,8 @@ const AssistantMessageComponent = ({ chatMessage, isCommitted, messageIdx, isLas
|
|||
}
|
||||
|
||||
const isEmpty = !chatMessage.content && !chatMessage.reasoning
|
||||
const isLastAndLoading = !isCommitted && isLast && (chatIsRunning === 'message' || chatIsRunning === 'awaiting_user')
|
||||
const isLoading = !isCommitted && !isToolBeingWritten && (chatIsRunning === 'message' || chatIsRunning === 'awaiting_user')
|
||||
const isLastAndLoading = isLast && isLoading
|
||||
if (isEmpty && !isLastAndLoading) return null
|
||||
|
||||
return <>
|
||||
|
|
@ -1083,7 +1084,7 @@ const AssistantMessageComponent = ({ chatMessage, isCommitted, messageIdx, isLas
|
|||
isLinkDetectionEnabled={true}
|
||||
/>
|
||||
{/* loading indicator */}
|
||||
{!isCommitted && <IconLoading className='opacity-50 text-sm' />}
|
||||
{isLoading && <IconLoading className='opacity-50 text-sm' />}
|
||||
</ProseWrapper>
|
||||
</>
|
||||
|
||||
|
|
@ -1117,19 +1118,19 @@ const loadingTitleWrapper = (item: React.ReactNode) => {
|
|||
</span>
|
||||
}
|
||||
const folderFileStr = (isFolder: boolean) => isFolder ? 'folder' : 'file'
|
||||
const toolNameToTitle = {
|
||||
const titleOfToolName = {
|
||||
'read_file': { done: 'Read file', proposed: 'Read file', running: loadingTitleWrapper('Reading file') },
|
||||
'list_dir': { done: 'Inspected folder', proposed: 'Inspect folder', running: loadingTitleWrapper('Inspecting folder') },
|
||||
'pathname_search': { done: 'Searched by file name', proposed: 'Search by file name', running: loadingTitleWrapper('Searching by file name') },
|
||||
'text_search': { done: 'Searched', proposed: 'Search text', running: loadingTitleWrapper('Searching') },
|
||||
'create_uri': {
|
||||
done: (isFolder: boolean) => `Created ${folderFileStr(isFolder)}`,
|
||||
proposed: (isFolder: boolean) => `Create ${folderFileStr(isFolder)}`,
|
||||
proposed: (isFolder: boolean | null) => isFolder === null ? 'Create URI' : `Create ${folderFileStr(isFolder)}`,
|
||||
running: (isFolder: boolean) => loadingTitleWrapper(`Creating ${folderFileStr(isFolder)}`)
|
||||
},
|
||||
'delete_uri': {
|
||||
done: (isFolder: boolean) => `Deleted ${folderFileStr(isFolder)}`,
|
||||
proposed: (isFolder: boolean) => `Delete ${folderFileStr(isFolder)}`,
|
||||
proposed: (isFolder: boolean | null) => isFolder === null ? 'Delete URI' : `Delete ${folderFileStr(isFolder)}`,
|
||||
running: (isFolder: boolean) => loadingTitleWrapper(`Deleting ${folderFileStr(isFolder)}`)
|
||||
},
|
||||
'edit': { done: `Edited file`, proposed: 'Edit file', running: loadingTitleWrapper('Editing file') },
|
||||
|
|
@ -1259,7 +1260,7 @@ export const ToolChildrenWrapper = ({ children, className }: { children: React.R
|
|||
</div>
|
||||
</div>
|
||||
}
|
||||
export const ErrorChildren = ({ children }: { children: React.ReactNode }) => {
|
||||
export const CodeChildren = ({ children }: { children: React.ReactNode }) => {
|
||||
return <div className='bg-void-bg-3 p-1 rounded-sm font-mono overflow-auto text-sm'>
|
||||
<div className='!select-text cursor-auto'>
|
||||
{children}
|
||||
|
|
@ -1315,7 +1316,7 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent<T> } = {
|
|||
resultWrapper: ({ toolMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const title = toolMessage.result.type === 'success' ? toolNameToTitle[toolMessage.name].done : toolNameToTitle[toolMessage.name].proposed
|
||||
const title = toolMessage.result.type === 'success' ? titleOfToolName[toolMessage.name].done : titleOfToolName[toolMessage.name].proposed
|
||||
const { uri } = toolMessage.result.params ?? {}
|
||||
const desc1 = uri ? getBasename(uri.fsPath) : '';
|
||||
const icon = null
|
||||
|
|
@ -1334,9 +1335,9 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent<T> } = {
|
|||
const { value, params } = toolMessage.result
|
||||
if (params) componentParams.desc2 = <JumpToFileButton uri={params.uri} />
|
||||
componentParams.children = <ToolChildrenWrapper>
|
||||
<ErrorChildren>
|
||||
<CodeChildren>
|
||||
{value}
|
||||
</ErrorChildren>
|
||||
</CodeChildren>
|
||||
</ToolChildrenWrapper>
|
||||
}
|
||||
|
||||
|
|
@ -1349,7 +1350,7 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent<T> } = {
|
|||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const explorerService = accessor.get('IExplorerService')
|
||||
const title = toolMessage.result.type === 'success' ? toolNameToTitle[toolMessage.name].done : toolNameToTitle[toolMessage.name].proposed
|
||||
const title = toolMessage.result.type === 'success' ? titleOfToolName[toolMessage.name].done : titleOfToolName[toolMessage.name].proposed
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
|
||||
const icon = null
|
||||
|
||||
|
|
@ -1381,9 +1382,9 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent<T> } = {
|
|||
else {
|
||||
const { value, params } = toolMessage.result
|
||||
componentParams.children = <ToolChildrenWrapper>
|
||||
<ErrorChildren>
|
||||
<CodeChildren>
|
||||
{value}
|
||||
</ErrorChildren>
|
||||
</CodeChildren>
|
||||
</ToolChildrenWrapper>
|
||||
}
|
||||
|
||||
|
|
@ -1396,7 +1397,7 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent<T> } = {
|
|||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const isError = toolMessage.result.type === 'error'
|
||||
const title = toolMessage.result.type === 'success' ? toolNameToTitle[toolMessage.name].done : toolNameToTitle[toolMessage.name].proposed
|
||||
const title = toolMessage.result.type === 'success' ? titleOfToolName[toolMessage.name].done : titleOfToolName[toolMessage.name].proposed
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
|
||||
const icon = null
|
||||
|
||||
|
|
@ -1424,9 +1425,9 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent<T> } = {
|
|||
else {
|
||||
const { value, params } = toolMessage.result
|
||||
componentParams.children = <ToolChildrenWrapper>
|
||||
<ErrorChildren>
|
||||
<CodeChildren>
|
||||
{value}
|
||||
</ErrorChildren>
|
||||
</CodeChildren>
|
||||
</ToolChildrenWrapper>
|
||||
}
|
||||
|
||||
|
|
@ -1439,7 +1440,7 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent<T> } = {
|
|||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const isError = toolMessage.result.type === 'error'
|
||||
const title = toolMessage.result.type === 'success' ? toolNameToTitle[toolMessage.name].done : toolNameToTitle[toolMessage.name].proposed
|
||||
const title = toolMessage.result.type === 'success' ? titleOfToolName[toolMessage.name].done : titleOfToolName[toolMessage.name].proposed
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
|
||||
const icon = null
|
||||
|
||||
|
|
@ -1467,9 +1468,9 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent<T> } = {
|
|||
else {
|
||||
const { value, params } = toolMessage.result
|
||||
componentParams.children = <ToolChildrenWrapper>
|
||||
<ErrorChildren>
|
||||
<CodeChildren>
|
||||
{value}
|
||||
</ErrorChildren>
|
||||
</CodeChildren>
|
||||
</ToolChildrenWrapper>
|
||||
}
|
||||
return <ToolHeaderWrapper {...componentParams} />
|
||||
|
|
@ -1485,7 +1486,7 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent<T> } = {
|
|||
const explorerService = accessor.get('IExplorerService')
|
||||
const isError = false
|
||||
const isFolder = toolRequest.params.isFolder
|
||||
const title = toolRequestState === 'awaiting_user' ? toolNameToTitle[toolRequest.name].proposed(isFolder) : toolNameToTitle[toolRequest.name].running(isFolder)
|
||||
const title = toolRequestState === 'awaiting_user' ? titleOfToolName[toolRequest.name].proposed(isFolder) : titleOfToolName[toolRequest.name].running(isFolder)
|
||||
const desc1 = toolNameToDesc(toolRequest.name, toolRequest.params)
|
||||
const icon = null
|
||||
|
||||
|
|
@ -1499,7 +1500,7 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent<T> } = {
|
|||
const isError = toolMessage.result.type === 'error'
|
||||
const isRejected = toolMessage.result.type === 'rejected'
|
||||
const isFolder = toolMessage.result.params?.isFolder ?? false
|
||||
const title = toolMessage.result.type === 'success' ? toolNameToTitle[toolMessage.name].done(isFolder) : toolNameToTitle[toolMessage.name].proposed(isFolder)
|
||||
const title = toolMessage.result.type === 'success' ? titleOfToolName[toolMessage.name].done(isFolder) : titleOfToolName[toolMessage.name].proposed(isFolder)
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
|
||||
const icon = null
|
||||
|
||||
|
|
@ -1517,9 +1518,9 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent<T> } = {
|
|||
const { params, value } = toolMessage.result
|
||||
if (params) { componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) } }
|
||||
componentParams.children = componentParams.children = <ToolChildrenWrapper>
|
||||
<ErrorChildren>
|
||||
<CodeChildren>
|
||||
{value}
|
||||
</ErrorChildren>
|
||||
</CodeChildren>
|
||||
</ToolChildrenWrapper>
|
||||
}
|
||||
|
||||
|
|
@ -1532,7 +1533,7 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent<T> } = {
|
|||
const commandService = accessor.get('ICommandService')
|
||||
const isError = false
|
||||
const isFolder = toolRequest.params.isFolder
|
||||
const title = toolRequestState === 'awaiting_user' ? toolNameToTitle[toolRequest.name].proposed(isFolder) : toolNameToTitle[toolRequest.name].running(isFolder)
|
||||
const title = toolRequestState === 'awaiting_user' ? titleOfToolName[toolRequest.name].proposed(isFolder) : titleOfToolName[toolRequest.name].running(isFolder)
|
||||
const desc1 = toolNameToDesc(toolRequest.name, toolRequest.params)
|
||||
const icon = null
|
||||
|
||||
|
|
@ -1549,7 +1550,7 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent<T> } = {
|
|||
const isFolder = toolMessage.result.params?.isFolder ?? false
|
||||
const isError = toolMessage.result.type === 'error'
|
||||
const isRejected = toolMessage.result.type === 'rejected'
|
||||
const title = toolMessage.result.type === 'success' ? toolNameToTitle[toolMessage.name].done(isFolder) : toolNameToTitle[toolMessage.name].proposed(isFolder)
|
||||
const title = toolMessage.result.type === 'success' ? titleOfToolName[toolMessage.name].done(isFolder) : titleOfToolName[toolMessage.name].proposed(isFolder)
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
|
||||
const icon = null
|
||||
|
||||
|
|
@ -1567,9 +1568,9 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent<T> } = {
|
|||
const { params, value } = toolMessage.result
|
||||
if (params) { componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) } }
|
||||
componentParams.children = componentParams.children = <ToolChildrenWrapper>
|
||||
<ErrorChildren>
|
||||
<CodeChildren>
|
||||
{value}
|
||||
</ErrorChildren>
|
||||
</CodeChildren>
|
||||
</ToolChildrenWrapper>
|
||||
}
|
||||
|
||||
|
|
@ -1580,7 +1581,7 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent<T> } = {
|
|||
requestWrapper: ({ toolRequest, messageIdx, toolRequestState, threadId }) => {
|
||||
const accessor = useAccessor()
|
||||
const isError = false
|
||||
const title = toolRequestState === 'awaiting_user' ? toolNameToTitle[toolRequest.name].proposed : toolNameToTitle[toolRequest.name].running
|
||||
const title = toolRequestState === 'awaiting_user' ? titleOfToolName[toolRequest.name].proposed : titleOfToolName[toolRequest.name].running
|
||||
const desc1 = toolNameToDesc(toolRequest.name, toolRequest.params)
|
||||
const icon = null
|
||||
|
||||
|
|
@ -1602,7 +1603,7 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent<T> } = {
|
|||
const accessor = useAccessor()
|
||||
const isError = toolMessage.result.type === 'error'
|
||||
const isRejected = toolMessage.result.type === 'rejected'
|
||||
const title = toolMessage.result.type === 'success' ? toolNameToTitle[toolMessage.name].done : toolNameToTitle[toolMessage.name].proposed
|
||||
const title = toolMessage.result.type === 'success' ? titleOfToolName[toolMessage.name].done : titleOfToolName[toolMessage.name].proposed
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
|
||||
const icon = null
|
||||
|
||||
|
|
@ -1641,9 +1642,9 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent<T> } = {
|
|||
if (params) {
|
||||
componentParams.children = <ToolChildrenWrapper className='bg-void-bg-3'>
|
||||
{/* error */}
|
||||
<ErrorChildren>
|
||||
<CodeChildren>
|
||||
{value}
|
||||
</ErrorChildren>
|
||||
</CodeChildren>
|
||||
|
||||
{/* content */}
|
||||
<EditToolChildren
|
||||
|
|
@ -1653,9 +1654,9 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent<T> } = {
|
|||
</ToolChildrenWrapper>
|
||||
}
|
||||
else {
|
||||
componentParams.children = <ErrorChildren>
|
||||
componentParams.children = <CodeChildren>
|
||||
{value}
|
||||
</ErrorChildren>
|
||||
</CodeChildren>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1669,7 +1670,7 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent<T> } = {
|
|||
const commandService = accessor.get('ICommandService')
|
||||
const terminalToolsService = accessor.get('ITerminalToolService')
|
||||
const isError = false
|
||||
const title = toolRequestState === 'awaiting_user' ? toolNameToTitle[toolRequest.name].proposed : toolNameToTitle[toolRequest.name].running
|
||||
const title = toolRequestState === 'awaiting_user' ? titleOfToolName[toolRequest.name].proposed : titleOfToolName[toolRequest.name].running
|
||||
const desc1 = toolNameToDesc(toolRequest.name, toolRequest.params)
|
||||
const icon = null
|
||||
|
||||
|
|
@ -1688,7 +1689,7 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent<T> } = {
|
|||
const commandService = accessor.get('ICommandService')
|
||||
const terminalToolsService = accessor.get('ITerminalToolService')
|
||||
const isError = toolMessage.result.type === 'error'
|
||||
const title = toolMessage.result.type === 'success' ? toolNameToTitle[toolMessage.name].done : toolNameToTitle[toolMessage.name].proposed
|
||||
const title = toolMessage.result.type === 'success' ? titleOfToolName[toolMessage.name].done : titleOfToolName[toolMessage.name].proposed
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
|
||||
const icon = null
|
||||
|
||||
|
|
@ -1753,9 +1754,10 @@ type ChatBubbleProps = {
|
|||
isLast: boolean, // includes the streaming message (if streaming, isLast is false except for the streaming message)
|
||||
chatIsRunning: IsRunningType,
|
||||
threadId: string,
|
||||
isToolBeingWritten: boolean,
|
||||
}
|
||||
|
||||
const ChatBubble = ({ chatMessage, isCommitted, messageIdx, isLast, chatIsRunning, threadId }: ChatBubbleProps) => {
|
||||
const ChatBubble = ({ chatMessage, isCommitted, messageIdx, isLast, chatIsRunning, threadId, isToolBeingWritten }: ChatBubbleProps) => {
|
||||
const role = chatMessage.role
|
||||
|
||||
if (role === 'user') {
|
||||
|
|
@ -1772,6 +1774,7 @@ const ChatBubble = ({ chatMessage, isCommitted, messageIdx, isLast, chatIsRunnin
|
|||
isCommitted={isCommitted}
|
||||
chatIsRunning={chatIsRunning}
|
||||
isLast={isLast}
|
||||
isToolBeingWritten={isToolBeingWritten}
|
||||
/>
|
||||
}
|
||||
else if (role === 'tool_request') {
|
||||
|
|
@ -1838,6 +1841,10 @@ export const SidebarChat = () => {
|
|||
const messageSoFar = currThreadStreamState?.messageSoFar
|
||||
const reasoningSoFar = currThreadStreamState?.reasoningSoFar
|
||||
|
||||
const toolNameSoFar = currThreadStreamState?.toolNameSoFar
|
||||
const toolParamsSoFar = currThreadStreamState?.toolParamsSoFar
|
||||
const toolIsLoading = !!toolNameSoFar && toolNameSoFar === 'edit' // show loading for slow tools (right now just edit)
|
||||
|
||||
// ----- SIDEBAR CHAT state (local) -----
|
||||
|
||||
// state of current message
|
||||
|
|
@ -1902,6 +1909,7 @@ export const SidebarChat = () => {
|
|||
chatIsRunning={isRunning}
|
||||
isLast={isLast}
|
||||
threadId={threadId}
|
||||
isToolBeingWritten={toolIsLoading}
|
||||
/>
|
||||
})
|
||||
}, [previousMessages, isRunning, currentThread, numMessages])
|
||||
|
|
@ -1921,9 +1929,17 @@ export const SidebarChat = () => {
|
|||
chatIsRunning={isRunning}
|
||||
isLast={true}
|
||||
threadId={threadId}
|
||||
isToolBeingWritten={toolIsLoading}
|
||||
/> : null
|
||||
|
||||
const allMessagesHTML = [...previousMessagesHTML, currStreamingMessageHTML]
|
||||
|
||||
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 ?
|
||||
<ToolHeaderWrapper key={getChatBubbleId(currentThread.id, streamingChatIdx + 1)} title={toolTitle} desc1={<IconLoading />} />
|
||||
: null
|
||||
|
||||
const allMessagesHTML = [...previousMessagesHTML, currStreamingMessageHTML, currStreamingToolHTML]
|
||||
|
||||
const threadSelector = <div
|
||||
className={`w-full ${isHistoryOpen ? '' : 'hidden'} ring-2 ring-widget-shadow ring-inset z-10`}
|
||||
|
|
|
|||
|
|
@ -264,7 +264,7 @@ export const extractReasoningOnTextWrapper = (onText: OnText, thinkTags: [string
|
|||
onText_(params)
|
||||
}
|
||||
|
||||
const newOnText: OnText = ({ fullText: fullText_ }) => {
|
||||
const newOnText: OnText = ({ fullText: fullText_, ...p }) => {
|
||||
// until found the first think tag, keep adding to fullText
|
||||
if (!foundTag1) {
|
||||
const endsWithTag1 = endsWithAnyPrefixOf(fullText_, thinkTags[0])
|
||||
|
|
@ -282,7 +282,7 @@ export const extractReasoningOnTextWrapper = (onText: OnText, thinkTags: [string
|
|||
fullTextSoFar += fullText_.substring(0, tag1Index)
|
||||
// Update latestAddIdx to after the first tag
|
||||
latestAddIdx = tag1Index + thinkTags[0].length
|
||||
onText({ fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar })
|
||||
onText({ ...p, fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar })
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -290,7 +290,7 @@ export const extractReasoningOnTextWrapper = (onText: OnText, thinkTags: [string
|
|||
// add the text to fullText
|
||||
fullTextSoFar = fullText_
|
||||
latestAddIdx = fullText_.length
|
||||
onText({ fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar })
|
||||
onText({ ...p, fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar })
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -314,7 +314,7 @@ export const extractReasoningOnTextWrapper = (onText: OnText, thinkTags: [string
|
|||
fullReasoningSoFar += fullText_.substring(latestAddIdx, tag2Index)
|
||||
// Update latestAddIdx to after the second tag
|
||||
latestAddIdx = tag2Index + thinkTags[1].length
|
||||
onText({ fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar })
|
||||
onText({ ...p, fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar })
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -327,7 +327,7 @@ export const extractReasoningOnTextWrapper = (onText: OnText, thinkTags: [string
|
|||
latestAddIdx = fullText_.length
|
||||
}
|
||||
|
||||
onText({ fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar })
|
||||
onText({ ...p, fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar })
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -340,7 +340,7 @@ export const extractReasoningOnTextWrapper = (onText: OnText, thinkTags: [string
|
|||
latestAddIdx = fullText_.length
|
||||
}
|
||||
|
||||
onText({ fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar })
|
||||
onText({ ...p, fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar })
|
||||
}
|
||||
|
||||
return newOnText
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ export type ToolCallType = {
|
|||
|
||||
export type AnthropicReasoning = ({ type: 'thinking'; thinking: any; signature: string; } | { type: 'redacted_thinking', data: any })
|
||||
|
||||
export type OnText = (p: { fullText: string; fullReasoning: string }) => void
|
||||
export type OnText = (p: { fullText: string; fullReasoning: string; fullToolName: string; fullToolParams: string; }) => void
|
||||
export type OnFinalMessage = (p: { fullText: string; fullReasoning: string; toolCalls?: ToolCallType[]; anthropicReasoning: AnthropicReasoning[] | null }) => void // id is tool_use_id
|
||||
export type OnError = (p: { message: string; fullError: Error | null }) => void
|
||||
export type OnAbort = () => void
|
||||
|
|
|
|||
|
|
@ -195,6 +195,10 @@ const _sendOpenAICompatibleChat = ({ messages: messages_, onText, onFinalMessage
|
|||
|
||||
let fullReasoningSoFar = ''
|
||||
let fullTextSoFar = ''
|
||||
|
||||
let fullToolName = ''
|
||||
let fullToolParams = ''
|
||||
|
||||
const toolCallOfIndex: ToolCallOfIndex = {}
|
||||
openai.chat.completions
|
||||
.create(options)
|
||||
|
|
@ -209,6 +213,9 @@ const _sendOpenAICompatibleChat = ({ messages: messages_, onText, onFinalMessage
|
|||
toolCallOfIndex[index].name += tool.function?.name ?? ''
|
||||
toolCallOfIndex[index].paramsStr += tool.function?.arguments ?? '';
|
||||
toolCallOfIndex[index].id += tool.id ?? ''
|
||||
|
||||
fullToolName += tool.function?.name ?? ''
|
||||
fullToolParams += tool.function?.arguments ?? ''
|
||||
}
|
||||
// message
|
||||
const newText = chunk.choices[0]?.delta?.content ?? ''
|
||||
|
|
@ -222,7 +229,7 @@ const _sendOpenAICompatibleChat = ({ messages: messages_, onText, onFinalMessage
|
|||
fullReasoningSoFar += newReasoning
|
||||
}
|
||||
|
||||
onText({ fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar })
|
||||
onText({ fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar, fullToolName, fullToolParams })
|
||||
}
|
||||
// on final
|
||||
const toolCalls = toolCallsFrom_OpenAICompat(toolCallOfIndex)
|
||||
|
|
@ -351,6 +358,9 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM
|
|||
let fullText = ''
|
||||
let fullReasoning = ''
|
||||
|
||||
let fullToolName = ''
|
||||
let fullToolParams = ''
|
||||
|
||||
// there are no events for tool_use, it comes in at the end
|
||||
stream.on('streamEvent', e => {
|
||||
// start block
|
||||
|
|
@ -358,18 +368,22 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM
|
|||
if (e.content_block.type === 'text') {
|
||||
if (fullText) fullText += '\n\n' // starting a 2nd text block
|
||||
fullText += e.content_block.text
|
||||
onText({ fullText, fullReasoning })
|
||||
onText({ fullText, fullReasoning, fullToolName, fullToolParams })
|
||||
}
|
||||
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 })
|
||||
onText({ fullText, fullReasoning, fullToolName, fullToolParams })
|
||||
}
|
||||
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 })
|
||||
onText({ fullText, fullReasoning, fullToolName, fullToolParams })
|
||||
}
|
||||
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, fullToolName, fullToolParams })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -377,11 +391,15 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM
|
|||
else if (e.type === 'content_block_delta') {
|
||||
if (e.delta.type === 'text_delta') {
|
||||
fullText += e.delta.text
|
||||
onText({ fullText, fullReasoning })
|
||||
onText({ fullText, fullReasoning, fullToolName, fullToolParams })
|
||||
}
|
||||
else if (e.delta.type === 'thinking_delta') {
|
||||
fullReasoning += e.delta.thinking
|
||||
onText({ fullText, fullReasoning })
|
||||
onText({ fullText, fullReasoning, fullToolName, fullToolParams })
|
||||
}
|
||||
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, fullToolName, fullToolParams })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue