fix streaming issue, cleanup chat error styles

This commit is contained in:
Andrew Pareles 2025-01-17 17:30:52 -08:00
parent 268d2ae99a
commit 6f1905a962
3 changed files with 84 additions and 75 deletions

View file

@ -65,7 +65,7 @@ export class LLMMessageService extends Disposable implements ILLMMessageService
this._onRequestIdDone(e.requestId)
}))
this._register((this.channel.listen('onError_llm') satisfies Event<EventLLMMessageOnErrorParams>)(e => {
console.log('Error in LLMMessageService:', JSON.stringify(e))
console.error('Error in LLMMessageService:', JSON.stringify(e))
this.onErrorHooks_llm[e.requestId]?.(e)
this._onRequestIdDone(e.requestId)
}))

View file

@ -74,9 +74,9 @@ export type ThreadsState = {
export type ThreadStreamState = {
[threadId: string]: undefined | {
streamingToken?: string;
error?: { message: string, fullError: Error | null };
messageSoFar?: string;
streamingToken?: string;
}
}
@ -177,6 +177,13 @@ class ChatThreadService extends Disposable implements IChatThreadService {
// ---------- streaming ----------
finishStreaming = (threadId: string, content: string, error?: { message: string, fullError: Error | null }) => {
// add assistant's message to chat history, and clear selection
const assistantHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content || null }
this._addMessageToThread(threadId, assistantHistoryElt)
this._setStreamState(threadId, { messageSoFar: undefined, streamingToken: undefined, error })
}
async addUserMessageAndStreamResponse(userMessage: string) {
const threadId = this.getCurrentThread().id
@ -192,12 +199,6 @@ class ChatThreadService extends Disposable implements IChatThreadService {
const userHistoryElt: ChatMessage = { role: 'user', content: chat_prompt(instructions, selections), displayContent: instructions, selections: selections }
this._addMessageToThread(threadId, userHistoryElt)
const onDone = (content: string, error?: { message: string, fullError: Error | null }) => {
// add assistant's message to chat history, and clear selection
const assistantHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content || null }
this._addMessageToThread(threadId, assistantHistoryElt)
this._setStreamState(threadId, { messageSoFar: undefined, streamingToken: undefined, error })
}
this._setStreamState(threadId, { error: undefined })
@ -211,11 +212,10 @@ class ChatThreadService extends Disposable implements IChatThreadService {
this._setStreamState(threadId, { messageSoFar: fullText })
},
onFinalMessage: ({ fullText: content }) => {
onDone(content)
this.finishStreaming(threadId, content)
},
onError: (error) => {
console.log('Void Chat Error:', error)
onDone(this.streamState[threadId]?.messageSoFar ?? '', error)
this.finishStreaming(threadId, this.streamState[threadId]?.messageSoFar ?? '', error)
},
useProviderFor: 'Ctrl+L',
@ -227,8 +227,8 @@ class ChatThreadService extends Disposable implements IChatThreadService {
cancelStreaming(threadId: string) {
const llmCancelToken = this.streamState[threadId]?.streamingToken
if (llmCancelToken) this._llmMessageService.abort(llmCancelToken)
this._setStreamState(threadId, { streamingToken: undefined })
if (llmCancelToken !== undefined) this._llmMessageService.abort(llmCancelToken)
this.finishStreaming(threadId, this.streamState[threadId]?.messageSoFar ?? '')
}
dismissStreamError(threadId: string): void {

View file

@ -18,7 +18,7 @@ import { ErrorDisplay } from './ErrorDisplay.js';
import { OnError, ServiceSendLLMMessageParams } from '../../../../../../../platform/void/common/llmMessageTypes.js';
import { HistoryInputBox, InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
import { TextAreaFns, VoidCodeEditorProps, VoidInputBox2 } from '../util/inputs.js';
import { ModelDropdown } from '../void-settings-tsx/ModelDropdown.js';
import { ModelDropdown, WarningBox } from '../void-settings-tsx/ModelDropdown.js';
import { chat_systemMessage, chat_prompt } from '../../../prompt/prompts.js';
import { ISidebarStateService } from '../../../sidebarStateService.js';
import { ILLMMessageService } from '../../../../../../../platform/void/common/llmMessageService.js';
@ -29,6 +29,7 @@ import { VOID_CTRL_L_ACTION_ID } from '../../../actionIDs.js';
import { ArrowBigLeftDash, CopyX, Delete, FileX2, SquareX, X } from 'lucide-react';
import { filenameToVscodeLanguage } from '../../../helpers/detectLanguage.js';
import { Pencil } from 'lucide-react'
import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js';
export const IconX = ({ size, className = '', ...props }: { size: number, className?: string } & React.SVGProps<SVGSVGElement>) => {
@ -438,10 +439,47 @@ export const SelectedFiles = (
const ChatBubble = ({ chatMessage, isLoading }: {
chatMessage: ChatMessage,
isLoading?: boolean,
}) => {
const ChatBubble_ = ({ isEditMode, isLoading, children, role }: { role: ChatMessage['role'], children: React.ReactNode, isLoading: boolean, isEditMode: boolean }) => {
return <div
// align chatbubble accoridng to role
className={`
relative
${isEditMode ? 'px-2 w-full max-w-full'
: role === 'user' ? `px-2 self-end w-fit max-w-full`
: role === 'assistant' ? `px-2 self-start w-full max-w-full` : ''
}
`}
>
<div
// style chatbubble according to role
className={`
text-left space-y-2 rounded-lg
overflow-x-auto max-w-full
${role === 'user' ? 'p-2 bg-void-bg-1 text-void-fg-1' : 'px-2'}
`}
>
{children}
{isLoading && <IconLoading className='opacity-50 text-sm' />}
</div>
{/* edit button */}
{/* {role === 'user' &&
<Pencil
size={16}
className={`
absolute top-0 right-2
translate-x-0 -translate-y-0
cursor-pointer z-1
`}
onClick={() => { setIsEditMode(v => !v); }}
/>
} */}
</div>
}
const ChatBubble = ({ chatMessage, isLoading }: { chatMessage: ChatMessage, isLoading?: boolean, }) => {
const role = chatMessage.role
@ -480,41 +518,9 @@ const ChatBubble = ({ chatMessage, isLoading }: {
chatbubbleContents = <ChatMarkdownRender string={chatMessage.displayContent ?? ''} />
}
return <div
// align chatbubble accoridng to role
className={`
relative
${isEditMode ? 'px-2 w-full max-w-full'
: role === 'user' ? `px-2 self-end w-fit max-w-full`
: role === 'assistant' ? `px-2 self-start w-full max-w-full` : ''
}
`}
>
<div
// style chatbubble according to role
className={`
p-2 text-left space-y-2 rounded-lg
overflow-x-auto max-w-full
${role === 'user' ? 'bg-void-bg-1 text-void-fg-1' : ''}
`}
>
{chatbubbleContents}
{isLoading && <IconLoading className='opacity-50 text-sm' />}
</div>
{/* edit button */}
{/* {role === 'user' &&
<Pencil
size={16}
className={`
absolute top-0 right-2
translate-x-0 -translate-y-0
cursor-pointer z-1
`}
onClick={() => { setIsEditMode(v => !v); }}
/>
} */}
</div>
return <ChatBubble_ role={role} isEditMode={isEditMode} isLoading={!!isLoading}>
{chatbubbleContents}
</ChatBubble_>
}
@ -524,7 +530,8 @@ export const SidebarChat = () => {
const textAreaFnsRef = useRef<TextAreaFns | null>(null)
const accessor = useAccessor()
const modelService = accessor.get('IModelService')
// const modelService = accessor.get('IModelService')
const commandService = accessor.get('ICommandService')
// ----- HIGHER STATE -----
// sidebar state
@ -549,11 +556,10 @@ export const SidebarChat = () => {
const selections = chatThreadsState.currentStagingSelections
// stream state
const chatThreadsStreamState = useChatThreadsStreamState(chatThreadsState.currentThreadId)
const streamingToken = chatThreadsStreamState?.streamingToken
const isStreaming = !!streamingToken
const latestError = chatThreadsStreamState?.error
const messageSoFar = chatThreadsStreamState?.messageSoFar
const currThreadStreamState = useChatThreadsStreamState(chatThreadsState.currentThreadId)
const isStreaming = !!currThreadStreamState?.streamingToken
const latestError = currThreadStreamState?.error
const messageSoFar = currThreadStreamState?.messageSoFar
// ----- SIDEBAR CHAT state (local) -----
@ -585,9 +591,8 @@ export const SidebarChat = () => {
}
const onAbort = () => {
// this assumes an instant cancellation doesn't happen, since streamingToken state must have updated by this time
if (!streamingToken) return
chatThreadsService.cancelStreaming(streamingToken)
const threadId = currentThread.id
chatThreadsService.cancelStreaming(threadId)
}
// const [_test_messages, _set_test_messages] = useState<string[]>([])
@ -617,7 +622,7 @@ export const SidebarChat = () => {
scrollContainerRef={scrollContainerRef}
className={`
w-full h-auto
flex flex-col gap-0
flex flex-col gap-1
overflow-x-hidden
overflow-y-auto
`}
@ -631,6 +636,21 @@ export const SidebarChat = () => {
{/* message stream */}
<ChatBubble chatMessage={{ role: 'assistant', content: messageSoFar ?? '', displayContent: messageSoFar || null }} isLoading={isStreaming} />
{/* error message */}
{latestError === undefined ? null :
<div className='px-2'>
<ErrorDisplay
message={latestError.message}
fullError={latestError.fullError}
onDismiss={() => { chatThreadsService.dismissStreamError(currentThread.id) }}
showDismiss={true}
/>
<WarningBox className='text-sm my-2 pl-4' onClick={() => { commandService.executeCommand(VOID_OPEN_SETTINGS_ACTION_ID) }} text='Open settings' />
</div>
}
</ScrollToBottomContainer>
@ -655,18 +675,7 @@ export const SidebarChat = () => {
{/* top row */}
<>
{/* selections */}
<SelectedFiles type='staging' selections={selections || []} setSelections={chatThreadsService.setStaging.bind(chatThreadsService)} showProspectiveSelections={previousMessages.length === 0} />
{/* error message */}
{latestError === undefined ? null :
<ErrorDisplay
message={latestError.message}
fullError={latestError.fullError}
onDismiss={() => { chatThreadsService.dismissStreamError(currentThread.id) }}
showDismiss={true}
/>
}
<SelectedFiles type='staging' selections={selections || []} setSelections={chatThreadsService.setStaging.bind(chatThreadsService)} />
</>
{/* middle row */}