mirror of
https://github.com/voideditor/void
synced 2026-05-24 01:48:25 +00:00
misc fixes + (empty message) improvements
This commit is contained in:
parent
8b45aea8a8
commit
7748dc4f2d
5 changed files with 65 additions and 47 deletions
|
|
@ -12,7 +12,7 @@ import { URI } from '../../../../base/common/uri.js';
|
|||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { ILLMMessageService } from '../common/sendLLMMessageService.js';
|
||||
import { chat_userMessageContent, ToolName, } from '../common/prompt/prompts.js';
|
||||
import { getErrorMessage, RawToolCallObj, RawToolParamsObj } from '../common/sendLLMMessageTypes.js';
|
||||
import { AnthropicReasoning, getErrorMessage, RawToolCallObj, RawToolParamsObj } from '../common/sendLLMMessageTypes.js';
|
||||
import { generateUuid } from '../../../../base/common/uuid.js';
|
||||
import { FeatureName, ModelSelection, ModelSelectionOptions } from '../common/voidSettingsTypes.js';
|
||||
import { IVoidSettingsService } from '../common/voidSettingsService.js';
|
||||
|
|
@ -540,7 +540,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
|
||||
const { name, id, rawParams } = lastMsg
|
||||
|
||||
const errorMessage = this.errMsgs.rejected
|
||||
const errorMessage = this.toolErrMsgs.rejected
|
||||
this._updateLatestTool(threadId, { role: 'tool', type: 'rejected', params: params, name: name, content: errorMessage, result: null, id, rawParams })
|
||||
this._setStreamState(threadId, undefined)
|
||||
}
|
||||
|
|
@ -557,7 +557,8 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
}
|
||||
// add tool that's running
|
||||
else if (this.streamState[threadId]?.isRunning === 'tool') {
|
||||
const { toolName, toolParams, id, content, rawParams } = this.streamState[threadId].toolInfo
|
||||
const { toolName, toolParams, id, content: content_, rawParams } = this.streamState[threadId].toolInfo
|
||||
const content = content_ || this.toolErrMsgs.interrupted
|
||||
this._updateLatestTool(threadId, { role: 'tool', name: toolName, params: toolParams, id, content, rawParams, type: 'rejected', result: null })
|
||||
}
|
||||
// reject the tool for the user if relevant
|
||||
|
|
@ -581,8 +582,9 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
|
||||
|
||||
|
||||
private readonly errMsgs = {
|
||||
private readonly toolErrMsgs = {
|
||||
rejected: 'Tool call was rejected by the user.',
|
||||
interrupted: 'Tool call was interrupted by the user.',
|
||||
errWhenStringifying: (error: any) => `Tool call succeeded, but there was an error stringifying the output.\n${getErrorMessage(error)}`
|
||||
}
|
||||
|
||||
|
|
@ -671,7 +673,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
try {
|
||||
toolResultStr = this._toolsService.stringOfResult[toolName](toolParams as any, toolResult as any)
|
||||
} catch (error) {
|
||||
const errorMessage = this.errMsgs.errWhenStringifying(error)
|
||||
const errorMessage = this.toolErrMsgs.errWhenStringifying(error)
|
||||
this._updateLatestTool(threadId, { role: 'tool', type: 'tool_error', params: toolParams, result: errorMessage, name: toolName, content: errorMessage, id: toolId, rawParams: opts.unvalidatedToolParams })
|
||||
return {}
|
||||
}
|
||||
|
|
@ -749,8 +751,13 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
shouldRetryLLM = false
|
||||
nAttempts += 1
|
||||
|
||||
let resMessageIsDonePromise: (res: { type: 'llmDone', toolCall?: RawToolCallObj } | { type: 'llmError', error?: { message: string; fullError: Error | null; } } | { type: 'llmAborted' }) => void // resolves when user approves this tool use (or if tool doesn't require approval)
|
||||
const messageIsDonePromise = new Promise<{ type: 'llmDone', toolCall?: RawToolCallObj } | { type: 'llmError', error?: { message: string; fullError: Error | null; } } | { type: 'llmAborted' }>((res, rej) => { resMessageIsDonePromise = res })
|
||||
type ResTypes =
|
||||
| { type: 'llmDone', toolCall?: RawToolCallObj, info: { fullText: string, fullReasoning: string, anthropicReasoning: AnthropicReasoning[] | null } }
|
||||
| { type: 'llmError', error?: { message: string; fullError: Error | null; } }
|
||||
| { type: 'llmAborted' }
|
||||
|
||||
let resMessageIsDonePromise: (res: ResTypes) => void // resolves when user approves this tool use (or if tool doesn't require approval)
|
||||
const messageIsDonePromise = new Promise<ResTypes>((res, rej) => { resMessageIsDonePromise = res })
|
||||
|
||||
const llmCancelToken = this._llmMessageService.sendLLMMessage({
|
||||
messagesType: 'chatMessages',
|
||||
|
|
@ -765,9 +772,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
this._setStreamState(threadId, { isRunning: 'LLM', llmInfo: { displayContentSoFar: fullText, reasoningSoFar: fullReasoning, toolCallSoFar: toolCall ?? null }, interrupt: Promise.resolve(() => { if (llmCancelToken) this._llmMessageService.abort(llmCancelToken) }) })
|
||||
},
|
||||
onFinalMessage: async ({ fullText, fullReasoning, toolCall, anthropicReasoning, }) => {
|
||||
this._addMessageToThread(threadId, { role: 'assistant', displayContent: fullText, reasoning: fullReasoning, anthropicReasoning })
|
||||
resMessageIsDonePromise({ type: 'llmDone', toolCall }) // resolve with tool calls
|
||||
|
||||
resMessageIsDonePromise({ type: 'llmDone', toolCall, info: { fullText, fullReasoning, anthropicReasoning } }) // resolve with tool calls
|
||||
},
|
||||
onError: async (error) => {
|
||||
resMessageIsDonePromise({ type: 'llmError', error: error })
|
||||
|
|
@ -826,11 +831,12 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// llm res success
|
||||
const { toolCall } = llmRes
|
||||
this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) // just decorative, for clarity
|
||||
const { toolCall, info } = llmRes
|
||||
|
||||
this._addMessageToThread(threadId, { role: 'assistant', displayContent: info.fullText, reasoning: info.fullReasoning, anthropicReasoning: info.anthropicReasoning })
|
||||
|
||||
this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) // just decorative for clarity
|
||||
|
||||
// call tool if there is one
|
||||
if (toolCall) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { IVoidModelService } from '../common/voidModelService.js';
|
|||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { EndOfLinePreference } from '../../../../editor/common/model.js';
|
||||
|
||||
export const EMPTY_MESSAGE = '(empty message)'
|
||||
|
||||
|
||||
|
||||
|
|
@ -36,7 +37,6 @@ type SimpleLLMMessage = {
|
|||
}
|
||||
|
||||
|
||||
const EMPTY_MESSAGE = '(empty message)'
|
||||
|
||||
const CHARS_PER_TOKEN = 4 // assume abysmal chars per token
|
||||
const TRIM_TO_LEN = 120
|
||||
|
|
@ -405,14 +405,24 @@ const prepareOpenAIOrAnthropicMessages = ({
|
|||
|
||||
|
||||
// ================ no empty message ================
|
||||
for (const currMsg of llmMessages) {
|
||||
for (let i = 0; i < llmMessages.length; i += 1) {
|
||||
const currMsg: AnthropicOrOpenAILLMMessage = llmMessages[i]
|
||||
const nextMsg: AnthropicOrOpenAILLMMessage | undefined = llmMessages[i + 1]
|
||||
|
||||
if (currMsg.role === 'tool') continue
|
||||
|
||||
// if content is a string, replace string with empty msg
|
||||
if (typeof currMsg.content === 'string')
|
||||
if (typeof currMsg.content === 'string') {
|
||||
currMsg.content = currMsg.content || EMPTY_MESSAGE
|
||||
}
|
||||
else {
|
||||
// if content is an array, replace any empty text entries with empty msg, and make sure there's at least 1 entry
|
||||
// allowed to be empty if has a tool in it or following it
|
||||
if (currMsg.content.find(c => c.type === 'tool_result' || c.type === 'tool_use')) {
|
||||
continue
|
||||
}
|
||||
if (nextMsg?.role === 'tool') continue
|
||||
|
||||
// replace any empty text entries with empty msg, and make sure there's at least 1 entry
|
||||
for (const c of currMsg.content) {
|
||||
if (c.type === 'text') c.text = c.text || EMPTY_MESSAGE
|
||||
}
|
||||
|
|
|
|||
|
|
@ -311,7 +311,6 @@ export const ApplyButtonsHTML = ({
|
|||
|
||||
|
||||
const currStreamState = currStreamStateRef.current
|
||||
console.log('currStreamState...', currStreamState)
|
||||
|
||||
if (currStreamState === 'streaming') {
|
||||
return <IconShell1
|
||||
|
|
|
|||
|
|
@ -120,31 +120,34 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string
|
|||
const [didComputeCodespanLink, setDidComputeCodespanLink] = useState<boolean>(false)
|
||||
|
||||
let link: CodespanLocationLink | undefined = undefined
|
||||
|
||||
if (!rawText.endsWith('`')) return null
|
||||
|
||||
|
||||
// get link from cache
|
||||
link = chatThreadService.getCodespanLink({ codespanStr: text, messageIdx, threadId })
|
||||
|
||||
if (link === undefined) {
|
||||
// if no link, generate link and add to cache
|
||||
chatThreadService.generateCodespanLink({ codespanStr: text, threadId })
|
||||
.then(link => {
|
||||
chatThreadService.addCodespanLink({ newLinkText: text, newLinkLocation: link, messageIdx, threadId })
|
||||
setDidComputeCodespanLink(true) // rerender
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// If it's a file path, shorten it and add tooltip (whether or not it's a link)
|
||||
let displayText = link?.displayText || text
|
||||
let tooltip: string | undefined = undefined
|
||||
if (isValidUri(displayText)) {
|
||||
tooltip = getRelative(URI.file(displayText), accessor) // Full path as tooltip
|
||||
displayText = getBasename(displayText)
|
||||
let displayText = text
|
||||
|
||||
|
||||
if (rawText.endsWith('`')) {
|
||||
// get link from cache
|
||||
link = chatThreadService.getCodespanLink({ codespanStr: text, messageIdx, threadId })
|
||||
|
||||
if (link === undefined) {
|
||||
// if no link, generate link and add to cache
|
||||
chatThreadService.generateCodespanLink({ codespanStr: text, threadId })
|
||||
.then(link => {
|
||||
chatThreadService.addCodespanLink({ newLinkText: text, newLinkLocation: link, messageIdx, threadId })
|
||||
setDidComputeCodespanLink(true) // rerender
|
||||
})
|
||||
}
|
||||
|
||||
if (link?.displayText) {
|
||||
displayText = link.displayText
|
||||
}
|
||||
|
||||
if (isValidUri(displayText)) {
|
||||
tooltip = getRelative(URI.file(displayText), accessor) // Full path as tooltip
|
||||
displayText = getBasename(displayText)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const onClick = () => {
|
||||
if (!link) return;
|
||||
// Use the updated voidOpenFileFn to open the file and handle selection
|
||||
|
|
|
|||
|
|
@ -500,7 +500,7 @@ export const getRelative = (uri: URI, accessor: ReturnType<typeof useAccessor>)
|
|||
let path: string
|
||||
const isInside = workspaceContextService.isInsideWorkspace(uri)
|
||||
if (isInside) {
|
||||
const f = workspaceContextService.getWorkspace().folders.find(f => uri.fsPath.startsWith(f.uri.fsPath))
|
||||
const f = workspaceContextService.getWorkspace().folders.find(f => uri.fsPath?.startsWith(f.uri.fsPath))
|
||||
if (f) { path = uri.fsPath.replace(f.uri.fsPath, '') }
|
||||
else { path = uri.fsPath }
|
||||
}
|
||||
|
|
@ -1651,8 +1651,8 @@ const LintErrorChildren = ({ lintErrors }: { lintErrors: LintErrorItem[] }) => {
|
|||
}
|
||||
|
||||
const BottomChildren = ({ children, title }: { children: React.ReactNode, title: string }) => {
|
||||
if (!children) return null;
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
if (!children) return null;
|
||||
return (
|
||||
<div className="w-full px-2 mt-0.5">
|
||||
<div
|
||||
|
|
@ -1905,7 +1905,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
</SmallProseWrapper>
|
||||
</ToolChildrenWrapper>
|
||||
}
|
||||
else {
|
||||
else if (toolMessage.type === 'tool_error') {
|
||||
const { result } = toolMessage
|
||||
componentParams.bottomChildren = <BottomChildren title='Error'>
|
||||
<CodeChildren>
|
||||
|
|
@ -1960,7 +1960,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
}
|
||||
</ToolChildrenWrapper>
|
||||
}
|
||||
else {
|
||||
else if (toolMessage.type === 'tool_error') {
|
||||
const { result } = toolMessage
|
||||
componentParams.bottomChildren = <BottomChildren title='Error'>
|
||||
<CodeChildren>
|
||||
|
|
@ -2009,7 +2009,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
|
||||
</ToolChildrenWrapper>
|
||||
}
|
||||
else {
|
||||
else if (toolMessage.type === 'tool_error') {
|
||||
const { result } = toolMessage
|
||||
componentParams.bottomChildren = <BottomChildren title='Error'>
|
||||
<CodeChildren>
|
||||
|
|
@ -2064,7 +2064,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
|
||||
</ToolChildrenWrapper>
|
||||
}
|
||||
else {
|
||||
else if (toolMessage.type === 'tool_error') {
|
||||
const { result } = toolMessage
|
||||
componentParams.bottomChildren = <BottomChildren title='Error'>
|
||||
<CodeChildren>
|
||||
|
|
|
|||
Loading…
Reference in a new issue