diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index 224a57c6..16cf18e9 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -55,6 +55,9 @@ LLM Edit x LLM Edit + +INVARIANT: +A checkpoint appears before every LLM message, and before every user message (before user really means directly after LLM is done). */ @@ -99,7 +102,7 @@ type ThreadType = { // this doesn't need to go in a state object, but feels right state: { - currCheckpointIdx: number | null; // the latest checkpoint we're at (always defined unless chat is empty so there are no checkpts) + currCheckpointIdx: number | null; // the latest checkpoint we're at (null if not at a particular checkpoint, like if the chat is streaming, or chat just finished and we haven't clicked on a checkpt) stagingSelections: StagingSelectionItem[]; focusedMessageIdx: number | undefined; // index of the user message that is being edited (undefined if none) @@ -775,8 +778,9 @@ class ChatThreadService extends Disposable implements IChatThreadService { // if awaiting user approval, keep isRunning true, else end isRunning this._setStreamState(threadId, { isRunning: isRunningWhenEnd }, 'merge') - // if successful, add checkpoint - this._addUserCheckpoint({ threadId }) + // add checkpoint before the next user message + if (!isRunningWhenEnd) + this._addUserCheckpoint({ threadId }) // capture number of messages sent this._metricsService.capture('Agent Loop Done', { nMessagesSent, chatMode }) @@ -785,31 +789,14 @@ class ChatThreadService extends Disposable implements IChatThreadService { private _addCheckpoint(threadId: string, checkpoint: CheckpointEntry) { this._addMessageToThread(threadId, checkpoint) - // update latest checkpoint idx to the one we just added - const newThread = this.state.allThreads[threadId] - if (!newThread) return // should never happen - const currCheckpointIdx = newThread.messages.length - 1 - this._setThreadState(threadId, { currCheckpointIdx }) + // // update latest checkpoint idx to the one we just added + // const newThread = this.state.allThreads[threadId] + // if (!newThread) return // should never happen + // const currCheckpointIdx = newThread.messages.length - 1 + // this._setThreadState(threadId, { currCheckpointIdx: currCheckpointIdx }) } - // merge any LLM checkpoint before this one (and after a user checkpoint if one exists), and add the checkpoint - // call this right after LLM edits a file - private _addToolEditCheckpoint({ threadId, uri, }: { threadId: string, uri: URI }) { - const thread = this.state.allThreads[threadId] - if (!thread) return - const { model } = this._voidModelService.getModel(uri) - if (!model) return // should never happen - const diffAreasSnapshot = this._editCodeService.getVoidFileSnapshot(uri) - - this._addCheckpoint(threadId, { - role: 'checkpoint', - type: 'tool_edit', - voidFileSnapshotOfURI: { [uri.fsPath]: diffAreasSnapshot }, - userModifications: { voidFileSnapshotOfURI: {} }, - }) - - } private _editMessageInThread(threadId: string, messageIdx: number, newMessage: ChatMessage,) { const { allThreads } = this.state @@ -833,17 +820,25 @@ class ChatThreadService extends Disposable implements IChatThreadService { } + private _getCheckpointInfo = (checkpointMessage: ChatMessage & { role: 'checkpoint' }, fsPath: string, opts: { includeUserModifiedChanges: boolean }) => { + const voidFileSnapshot = checkpointMessage.voidFileSnapshotOfURI ? checkpointMessage.voidFileSnapshotOfURI[fsPath] ?? null : null + if (!opts.includeUserModifiedChanges) { return { voidFileSnapshot, } } - private _computeCheckpointInfo({ threadId }: { threadId: string }) { + const userModifiedVoidFileSnapshot = fsPath in checkpointMessage.userModifications.voidFileSnapshotOfURI ? checkpointMessage.userModifications.voidFileSnapshotOfURI[fsPath] ?? null : null + return { voidFileSnapshot: userModifiedVoidFileSnapshot ?? voidFileSnapshot, } + } + + private _computeNewCheckpointInfo({ threadId }: { threadId: string }) { const thread = this.state.allThreads[threadId] if (!thread) return - const { currCheckpointIdx } = thread.state - if (currCheckpointIdx === null) return + + const lastCheckpointIdx = findLastIdx(thread.messages, (m) => m.role === 'checkpoint') ?? -1 + if (lastCheckpointIdx === -1) return const voidFileSnapshotOfURI: { [fsPath: string]: VoidFileSnapshot | undefined } = {} // add a change for all the URIs in the checkpoint history - const { lastIdxOfURI } = this._getCheckpointsBetween({ threadId, loIdx: 0, hiIdx: currCheckpointIdx, }) ?? {} + const { lastIdxOfURI } = this._getCheckpointsBetween({ threadId, loIdx: 0, hiIdx: lastCheckpointIdx, }) ?? {} for (const fsPath in lastIdxOfURI ?? {}) { const { model } = this._voidModelService.getModelFromFsPath(fsPath) if (!model) continue @@ -871,44 +866,32 @@ class ChatThreadService extends Disposable implements IChatThreadService { return { voidFileSnapshotOfURI } } - // call this right before user sends message or reverts + private _addUserCheckpoint({ threadId }: { threadId: string }) { - const { voidFileSnapshotOfURI } = this._computeCheckpointInfo({ threadId }) ?? {} + const { voidFileSnapshotOfURI } = this._computeNewCheckpointInfo({ threadId }) ?? {} this._addCheckpoint(threadId, { role: 'checkpoint', type: 'user_edit', voidFileSnapshotOfURI: voidFileSnapshotOfURI ?? {}, - userModifications: { - voidFileSnapshotOfURI: {}, - }, + userModifications: { voidFileSnapshotOfURI: {}, }, }) } - private _addUserModificationsToCurrCheckpoint({ threadId }: { threadId: string }) { - const { voidFileSnapshotOfURI } = this._computeCheckpointInfo({ threadId }) ?? {} - - const res = this._getCurrentCheckpoint(threadId) - if (!res) return - const [checkpoint, checkpointIdx] = res - this._editMessageInThread(threadId, checkpointIdx, { - ...checkpoint, - userModifications: { voidFileSnapshotOfURI: voidFileSnapshotOfURI ?? {}, }, - }) - - } - - private _getCurrentCheckpoint(threadId: string): [CheckpointEntry, number] | undefined { + // call this right after LLM edits a file + private _addToolEditCheckpoint({ threadId, uri, }: { threadId: string, uri: URI }) { const thread = this.state.allThreads[threadId] if (!thread) return - - const { currCheckpointIdx } = thread.state - if (currCheckpointIdx === null) return - - const checkpoint = thread.messages[currCheckpointIdx] - if (!checkpoint) return - if (checkpoint.role !== 'checkpoint') return - return [checkpoint, currCheckpointIdx] + const { model } = this._voidModelService.getModel(uri) + if (!model) return // should never happen + const diffAreasSnapshot = this._editCodeService.getVoidFileSnapshot(uri) + this._addCheckpoint(threadId, { + role: 'checkpoint', + type: 'tool_edit', + voidFileSnapshotOfURI: { [uri.fsPath]: diffAreasSnapshot }, + userModifications: { voidFileSnapshotOfURI: {} }, + }) } + private _getCheckpointBeforeMessage = ({ threadId, messageIdx }: { threadId: string, messageIdx: number }): [CheckpointEntry, number] | undefined => { const thread = this.state.allThreads[threadId] if (!thread) return undefined @@ -927,7 +910,7 @@ class ChatThreadService extends Disposable implements IChatThreadService { const lastIdxOfURI: { [fsPath: string]: number } = {} for (let i = loIdx; i <= hiIdx; i += 1) { const message = thread.messages[i] - if (message.role !== 'checkpoint') continue + if (message?.role !== 'checkpoint') continue for (const fsPath in message.voidFileSnapshotOfURI) { // do not include userModified.beforeStrOfURI here, jumping should not include those changes lastIdxOfURI[fsPath] = i } @@ -935,27 +918,49 @@ class ChatThreadService extends Disposable implements IChatThreadService { return { lastIdxOfURI } } - private _getCheckpointInfo = (checkpointMessage: ChatMessage & { role: 'checkpoint' }, fsPath: string, opts: { includeUserModifiedChanges: boolean }) => { - const voidFileSnapshot = checkpointMessage.voidFileSnapshotOfURI ? checkpointMessage.voidFileSnapshotOfURI[fsPath] ?? null : null - if (!opts.includeUserModifiedChanges) { return { voidFileSnapshot, } } + private _readCurrentCheckpoint(threadId: string): [CheckpointEntry, number] | undefined { + const thread = this.state.allThreads[threadId] + if (!thread) return - const userModifiedVoidFileSnapshot = fsPath in checkpointMessage.userModifications.voidFileSnapshotOfURI ? checkpointMessage.userModifications.voidFileSnapshotOfURI[fsPath] ?? null : null - return { voidFileSnapshot: userModifiedVoidFileSnapshot ?? voidFileSnapshot, } + const { currCheckpointIdx } = thread.state + if (currCheckpointIdx === null) return + + const checkpoint = thread.messages[currCheckpointIdx] + if (!checkpoint) return + if (checkpoint.role !== 'checkpoint') return + return [checkpoint, currCheckpointIdx] + } + private _addUserModificationsToCurrCheckpoint({ threadId }: { threadId: string }) { + const { voidFileSnapshotOfURI } = this._computeNewCheckpointInfo({ threadId }) ?? {} + const res = this._readCurrentCheckpoint(threadId) + if (!res) return + const [checkpoint, checkpointIdx] = res + this._editMessageInThread(threadId, checkpointIdx, { + ...checkpoint, + userModifications: { voidFileSnapshotOfURI: voidFileSnapshotOfURI ?? {}, }, + }) } - // private _writeFullFile = ({ fsPath, text }: { fsPath: string, text: string }) => { - // const { model } = this._voidModelService.getModelFromFsPath(fsPath) - // if (!model) return // should never happen - // model.applyEdits([{ - // range: { startLineNumber: 1, startColumn: 1, endLineNumber: model.getLineCount(), endColumn: Number.MAX_SAFE_INTEGER }, // whole file - // text - // }]) - // } - - jumpToCheckpointBeforeMessageIdx({ threadId, messageIdx, jumpToUserModified }: { threadId: string, messageIdx: number, jumpToUserModified: boolean }) { + private _makeUsStandOnCheckpoint({ threadId }: { threadId: string }) { const thread = this.state.allThreads[threadId] if (!thread) return + if (thread.state.currCheckpointIdx === null) { + const lastMsg = thread.messages[thread.messages.length - 1] + if (lastMsg?.role !== 'checkpoint') + this._addUserCheckpoint({ threadId }) + this._setThreadState(threadId, { currCheckpointIdx: thread.messages.length - 1 }) + } + } + + jumpToCheckpointBeforeMessageIdx({ threadId, messageIdx, jumpToUserModified }: { threadId: string, messageIdx: number, jumpToUserModified: boolean }) { + + // if null, add a new temp checkpoint so user can jump forward again + this._makeUsStandOnCheckpoint({ threadId }) + + const thread = this.state.allThreads[threadId] + if (!thread) return + if (this.streamState[threadId]?.isRunning) return const c = this._getCheckpointBeforeMessage({ threadId, messageIdx }) if (c === undefined) return // should never happen @@ -1045,7 +1050,6 @@ We only need to do it for files that were edited since `from`, ie files between } this._setThreadState(threadId, { currCheckpointIdx: toIdx }) - // TODO!!! add/merge a checkpoint modification if relevant } @@ -1090,6 +1094,12 @@ We only need to do it for files that were edited since `from`, ie files between const thread = this.state.allThreads[threadId] if (!thread) return // should never happen + + // add dummy before this message to keep checkpoint before user message idea consistent + if (thread.messages.length === 0) { + this._addUserCheckpoint({ threadId }) + } + // if the current thread is already streaming, stop it (this simply resolves the promise to free up space) const llmCancelToken = this.streamState[threadId]?.streamingToken if (llmCancelToken !== undefined) this._llmMessageService.abort(llmCancelToken) @@ -1109,6 +1119,8 @@ We only need to do it for files that were edited since `from`, ie files between const userHistoryElt: ChatMessage = { role: 'user', content: userMessageContent, displayContent: instructions, selections: currSelns, state: defaultMessageState } this._addMessageToThread(threadId, userHistoryElt) + this._setThreadState(threadId, { currCheckpointIdx: null }) // no longer at a checkpoint because started streaming + this._wrapRunAgentToNotify( this._runChatAgent({ prevSelns, currSelns, threadId, userMessageContent, ...this._currentModelSelectionProps(), }), threadId, diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx index 7dfbf73c..c2ef204e 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx @@ -191,7 +191,7 @@ export const StatusIndicatorHTML = ({ applyBoxId, uri }: { applyBoxId: string, u } -export const ApplyButtonsHTML = ({ codeStr, applyBoxId, uri }: { codeStr: string, applyBoxId: string, uri: URI | 'current' }) => { +export const ApplyButtonsHTML = ({ codeStr, applyBoxId, reapplyIcon, uri }: { codeStr: string, applyBoxId: string, reapplyIcon: boolean, uri: URI | 'current' }) => { const accessor = useAccessor() const editCodeService = accessor.get('IEditCodeService') const metricsService = accessor.get('IMetricsService') @@ -255,7 +255,7 @@ export const ApplyButtonsHTML = ({ codeStr, applyBoxId, uri }: { codeStr: string } if (currStreamState === 'idle-no-changes') { - return + return } if (currStreamState === 'idle-has-changes') { @@ -322,7 +322,7 @@ export const BlockCodeApplyWrapper = ({
{currStreamState === 'idle-no-changes' && } - +
diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 2499ae26..9c31e142 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -22,7 +22,7 @@ import { ChatMode, FeatureName, isFeatureNameDisabled } from '../../../../../../ import { WarningBox } from '../void-settings-tsx/WarningBox.js'; import { getModelCapabilities, getIsResoningEnabledState } from '../../../../common/modelCapabilities.js'; import { AlertTriangle, Ban, ChevronRight, Dot, Pencil, Undo, Undo2, X } from 'lucide-react'; -import { ChatMessage, StagingSelectionItem, ToolMessage, ToolRequestApproval } from '../../../../common/chatThreadServiceTypes.js'; +import { ChatMessage, CheckpointEntry, StagingSelectionItem, ToolMessage, ToolRequestApproval } from '../../../../common/chatThreadServiceTypes.js'; import { ToolCallParams, ToolName, toolNames, ToolNameWithApproval } from '../../../../common/toolsServiceTypes.js'; import { ApplyButtonsHTML, CopyButton, JumpToFileButton, JumpToTerminalButton, StatusIndicatorHTML, useApplyButtonState } from '../markdown/ApplyBlockHoverButtons.js'; import { IsRunningType } from '../../../chatThreadService.js'; @@ -769,7 +769,7 @@ const SimplifiedToolHeader = ({ -const UserMessageComponent = ({ chatMessage, messageIdx, isCommitted, _scrollToBottom }: { chatMessage: ChatMessage & { role: 'user' }, messageIdx: number, isCommitted: boolean, _scrollToBottom: (() => void) | null }) => { +const UserMessageComponent = ({ chatMessage, messageIdx, isCheckpointGhost, _scrollToBottom }: { chatMessage: ChatMessage & { role: 'user' }, messageIdx: number, isCheckpointGhost: boolean, _scrollToBottom: (() => void) | null }) => { const accessor = useAccessor() const chatThreadsService = accessor.get('IChatThreadService') @@ -886,7 +886,7 @@ const UserMessageComponent = ({ chatMessage, messageIdx, isCommitted, _scrollToB } } - if (!chatMessage.content && isCommitted) { // don't show if empty and not loading (if loading, want to show). + if (!chatMessage.content) { // don't show if empty and not loading (if loading, want to show). return null } @@ -929,6 +929,8 @@ const UserMessageComponent = ({ chatMessage, messageIdx, isCommitted, _scrollToB ${mode === 'edit' ? 'w-full max-w-full' : mode === 'display' ? `self-end w-fit max-w-full whitespace-pre-wrap` : '' // user words should be pre } + + ${isCheckpointGhost ? 'opacity-50 pointer-events-none' : ''} `} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} @@ -1062,12 +1064,11 @@ const ProseWrapper = ({ children }: { children: React.ReactNode }) => { {children} } -const AssistantMessageComponent = ({ chatMessage, isCommitted, messageIdx, isLast, chatIsRunning, isToolBeingWritten }: { chatMessage: ChatMessage & { role: 'assistant' }, messageIdx: number, isCommitted: boolean, isLast: boolean, chatIsRunning: IsRunningType, isToolBeingWritten: boolean }) => { +const AssistantMessageComponent = ({ chatMessage, isCheckpointGhost, isCommitted, messageIdx }: { chatMessage: ChatMessage & { role: 'assistant' }, isCheckpointGhost: boolean, messageIdx: number, isCommitted: boolean }) => { const accessor = useAccessor() const chatThreadsService = accessor.get('IChatThreadService') - const reasoningStr = chatMessage.reasoning?.trim() || null const hasReasoning = !!reasoningStr const isDoneReasoning = !!chatMessage.content @@ -1080,34 +1081,36 @@ const AssistantMessageComponent = ({ chatMessage, isCommitted, messageIdx, isLas } const isEmpty = !chatMessage.content && !chatMessage.reasoning - const isLoading = !isCommitted && !isToolBeingWritten && (chatIsRunning === 'message' || chatIsRunning === 'awaiting_user') - const isLastAndLoading = isLast && isLoading - if (isEmpty && !isLastAndLoading) return null + if (isEmpty) return null return <> {/* reasoning token */} - {hasReasoning && - - - - } + {hasReasoning && +
+ + + + + +
+ } {/* assistant message */} - - - {/* loading indicator */} - {isLoading && } - +
+ + + +
} @@ -1321,7 +1324,7 @@ const EditToolHeaderButtons = ({ applyBoxId, uri, codeStr }: { applyBoxId: strin {currStreamState === 'idle-no-changes' && } - + } @@ -1822,20 +1825,26 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent } = { }; -const Checkpoint = ({ threadId, messageIdx }: { threadId: string; messageIdx: number }) => { +const Checkpoint = ({ message, threadId, messageIdx, isCheckpointGhost, threadIsRunning }: { message: CheckpointEntry, threadId: string; messageIdx: number, isCheckpointGhost: boolean, threadIsRunning: boolean }) => { const accessor = useAccessor() const chatThreadService = accessor.get('IChatThreadService') - // const commandBarService = accessor.get('IVoidCommandBarService') + return
{ - // reject all current changes and then jump back - // commandBarService.acceptOrRejectAllFiles({ behavior: 'accept' }) - chatThreadService.jumpToCheckpointBeforeMessageIdx({ threadId, messageIdx, jumpToUserModified: true }) - }}> -
-
Checkpoint
-
+ className={` + flex items-center justify-center + px-2 text-xs text-void-fg-3 + ${isCheckpointGhost ? 'opacity-50' : ''} + `} + > +
{ + if (threadIsRunning) return + chatThreadService.jumpToCheckpointBeforeMessageIdx({ threadId, messageIdx, jumpToUserModified: true }) + }} + > + Checkpoint +
} @@ -1845,45 +1854,57 @@ type ChatBubbleProps = { chatMessage: ChatMessage, messageIdx: number, isCommitted: boolean, - isLast: boolean, // includes the streaming message (if streaming, isLast is false except for the streaming message) + canAcceptReject: boolean, chatIsRunning: IsRunningType, threadId: string, - isToolBeingWritten: boolean, + currCheckpointIdx: number, _scrollToBottom: (() => void) | null, } -const ChatBubble = ({ chatMessage, isCommitted, messageIdx, isLast, chatIsRunning, threadId, isToolBeingWritten, _scrollToBottom }: ChatBubbleProps) => { +const ChatBubble = ({ chatMessage, currCheckpointIdx, isCommitted, messageIdx, canAcceptReject, chatIsRunning, threadId, _scrollToBottom }: ChatBubbleProps) => { const role = chatMessage.role + const isCheckpointGhost = messageIdx > currCheckpointIdx && !chatIsRunning // whether to show as gray (if chat is running, for good measure just dont show any ghosts) + if (role === 'user') { return } else if (role === 'assistant') { return } else if (role === 'tool_request') { const ToolRequestWrapper = toolNameToComponent[chatMessage.name]?.requestWrapper as RequestWrapper - const toolRequestType = ( + const toolRequestState = ( chatIsRunning === 'awaiting_user' ? 'awaiting_user' : chatIsRunning === 'tool' ? 'running' - : null + : chatIsRunning === 'message' ? null + : null ) - if (ToolRequestWrapper && isLast) { // if it's the last message + if (ToolRequestWrapper && canAcceptReject) { // if it's the last message return <> - {toolRequestType !== null && } - {chatIsRunning === 'awaiting_user' && } + {toolRequestState !== null && +
+ +
} + {chatIsRunning === 'awaiting_user' && +
+ +
} } return null @@ -1891,17 +1912,26 @@ const ChatBubble = ({ chatMessage, isCommitted, messageIdx, isLast, chatIsRunnin else if (role === 'tool') { const ToolResultWrapper = toolNameToComponent[chatMessage.name]?.resultWrapper as ResultWrapper if (ToolResultWrapper) - return + return
+ +
return null } else if (role === 'checkpoint') { - return + return } - else if (role === 'checkpoint_modification') { - return - } } @@ -1974,7 +2004,7 @@ export const SidebarChat = () => { const toolNameSoFar = currThreadStreamState?.toolNameSoFar const toolParamsSoFar = currThreadStreamState?.toolParamsSoFar - const toolIsLoading = !!toolNameSoFar && toolNameSoFar === 'edit' // show loading for slow tools (right now just edit) + const toolIsGenerating = !!toolNameSoFar && toolNameSoFar === 'edit_file' // show loading for slow tools (right now just edit) // ----- SIDEBAR CHAT state (local) ----- @@ -2024,36 +2054,37 @@ export const SidebarChat = () => { scrollContainerRef.current?.scrollTo({ top: 0, left: 0 }) }, [isHistoryOpen, currentThread.id]) - const numMessages = previousMessages.length - const lastMessageIdx = previousMessages.findLastIndex(v => v.role !== 'checkpoint') - - const previousMessagesHTML = useMemo(() => { - const threadId = currentThread.id - const currCheckpointIdx = chatThreadsState.allThreads[threadId]?.state?.currCheckpointIdx ?? Infinity // if not exist, treat like checkpoint is last message (infinity) - - return previousMessages.map((message, i) => { - const isLast = i === lastMessageIdx && (isRunning === 'tool' || isRunning === 'awaiting_user') - return
- scrollToBottom(scrollContainerRef)} - /> -
- }) - }, [previousMessages, isRunning, currentThread, numMessages]) const threadId = currentThread.id + const currCheckpointIdx = chatThreadsState.allThreads[threadId]?.state?.currCheckpointIdx ?? Infinity // if not exist, treat like checkpoint is last message (infinity) + + const previousMessagesHTML = useMemo(() => { + const lastMessageIdx = previousMessages.findLastIndex(v => v.role !== 'checkpoint') + + console.log('PREVMSGS', previousMessages) + + return previousMessages.map((message, i) => { + const canAcceptReject = i === lastMessageIdx && message.role === 'tool_request' + + return scrollToBottom(scrollContainerRef)} + /> + }) + }, [previousMessages, isRunning, threadId]) + const streamingChatIdx = previousMessagesHTML.length - const currStreamingMessageHTML = !!(reasoningSoFar || messageSoFar || isRunning) ? - { anthropicReasoning: null, }} messageIdx={streamingChatIdx} - isCommitted={!isRunning} + isCommitted={false} chatIsRunning={isRunning} - isLast={true} + canAcceptReject={false} + threadId={threadId} - isToolBeingWritten={toolIsLoading} _scrollToBottom={null} /> : null - 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 ? - Generating} /> - : null - - const allMessagesHTML = [...previousMessagesHTML, currStreamingMessageHTML, currStreamingToolHTML] - - const threadSelector =
- -
+ const proposedToolTitle = toolNameSoFar && toolNames.includes(toolNameSoFar as ToolName) ? titleOfToolName[toolNameSoFar as ToolName]?.proposed : toolNameSoFar + const generatingToolTitle = typeof proposedToolTitle === 'function' ? proposedToolTitle(null) : proposedToolTitle const messagesHTML = { `} > {/* previous messages */} - {allMessagesHTML} + {previousMessagesHTML} + + + {currStreamingMessageHTML} + + + {toolIsGenerating ? + Generating} /> + : null} + + {isRunning === 'message' && !toolIsGenerating ? + {/* loading indicator */} + {} + : null} {/* error message */} @@ -2158,7 +2191,11 @@ export const SidebarChat = () => { return (
- {threadSelector} + {/* History selector */} +
+ +
+
{messagesHTML} diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx index 2e0b4bd2..7db16221 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx @@ -72,7 +72,7 @@ export const SidebarThreadSelector = () => { let firstMsg = null; // let secondMsg = null; - const firstUserMsgIdx = pastThread.messages.findIndex((msg) => msg.role !== 'tool' && msg.role !== 'tool_request'); + const firstUserMsgIdx = pastThread.messages.findIndex((msg) => msg.role === 'user'); if (firstUserMsgIdx !== -1) { // firstMsg = truncate(pastThread.messages[firstMsgIdx].displayContent ?? ''); diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index 1cfa7c4e..34410b97 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -165,7 +165,7 @@ Here's an example of a good description:\n${editToolDescription}.` export const chat_systemMessage = ({ workspaceFolders, openedURIs, activeURI, runningTerminalIds, directoryStr, chatMode: mode }: { workspaceFolders: string[], directoryStr: string, openedURIs: string[], activeURI: string | undefined, runningTerminalIds: string[], chatMode: ChatMode }) => `\ -You are an expert coding ${mode === 'agent' ? 'agent' : 'assistant'} that runs in the Void code editor. Your job is \ +You are an expert coding ${mode === 'agent' ? 'agent' : 'assistant'} that runs in the user's IDE called Void. Your job is \ ${mode === 'agent' ? `to help the user develop, run, deploy, and make changes to their codebase. You should ALWAYS bring user's task to completion to the fullest extent possible, calling tools to make all necessary changes.` : mode === 'gather' ? `to search and understand the user's codebase. You MUST use tools to read files and help the user understand the codebase, even if you were initially given files.` : mode === 'normal' ? `to assist the user with their coding tasks.` @@ -224,6 +224,7 @@ Misc: - Do not be lazy. - NEVER re-write the entire file. - Always wrap any code you produce in triple backticks, and specify a language if possible. For example, ${tripleTick[0]}typescript\n...\n${tripleTick[1]}. +- Today's date is ${new Date().toDateString()} The user's codebase is structured as follows:\n${directoryStr} \ `