checkpoint progress

This commit is contained in:
Andrew Pareles 2025-04-02 19:13:51 -07:00
parent 7e8af9c2ef
commit 3ac9dcf0c0
2 changed files with 52 additions and 35 deletions

View file

@ -104,7 +104,7 @@ type ThreadType = {
// this doesn't need to go in a state object, but feels right
state: {
latestCheckpointIdx: number | null; // the latest checkpoint we're standing at or null
currCheckpointIdx: number | null; // the latest checkpoint we're standing at or null
stagingSelections: StagingSelectionItem[];
focusedMessageIdx: number | undefined; // index of the user message that is being edited (undefined if none)
@ -122,7 +122,7 @@ type ChatThreads = {
}
export const defaultThreadState: ThreadType['state'] = {
latestCheckpointIdx: null,
currCheckpointIdx: null,
stagingSelections: [],
focusedMessageIdx: undefined,
linksOfMessageIdx: {},
@ -629,8 +629,6 @@ class ChatThreadService extends Disposable implements IChatThreadService {
delete this._currentlyRunningToolInterruptor[threadId];
}
toolResult = await result // ts is bad... await is needed
if (toolName === 'edit') { this._addOrUpdateToolEditCheckpoint({ threadId, uri: (toolParams as ToolCallParams['edit']).uri }) }
}
catch (error) {
if (interrupted) {
@ -654,6 +652,9 @@ class ChatThreadService extends Disposable implements IChatThreadService {
// 5. add to history and keep going
this._addMessageToThread(threadId, { role: 'tool', name: toolName, paramsStr: toolParamsStr, id: toolId, content: toolResultStr, result: { type: 'success', params: toolParams, value: toolResult }, })
// 6. add a checkpoint
if (toolName === 'edit') { this._addToolEditCheckpoint({ threadId, uri: (toolParams as ToolCallParams['edit']).uri }) }
return {}
};
@ -778,35 +779,41 @@ class ChatThreadService extends Disposable implements IChatThreadService {
const newThread = this.state.allThreads[threadId]
if (!newThread) return // should never happen
const latestCheckpointIdx = newThread.messages.length - 1
this._setThreadState(threadId, { latestCheckpointIdx })
this._setThreadState(threadId, { currCheckpointIdx: latestCheckpointIdx })
}
// 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 _addOrUpdateToolEditCheckpoint({ threadId, uri, }: { threadId: string, uri: URI }) {
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 lastUserCheckpointIdx = findLastIdx(thread.messages, (m) => m.role === 'checkpoint' && m.type === 'after_user_edits')
const prevLLMCheckpointIdx = thread.messages.findIndex((m, i) => i > lastUserCheckpointIdx && m.role === 'checkpoint' && m.type === 'after_tool_edits')
const afterStr = model.getValue() // afterStr = the value of the file right after the edit
let prevLLMCheckpoint: LLMCheckpoint | undefined = undefined
if (prevLLMCheckpointIdx !== -1) {
prevLLMCheckpoint = thread.messages[prevLLMCheckpointIdx] as ChatMessage & { role: 'checkpoint', type: 'after_tool_edits' }
this._removeMessageFromThread(threadId, prevLLMCheckpointIdx)
}
const newLLMCheckpoint: LLMCheckpoint = {
role: 'checkpoint',
type: 'after_tool_edits',
afterStrOfURI: {
...prevLLMCheckpoint?.afterStrOfURI,
[uri.fsPath]: afterStr,
},
}
// remove and merge
// const lastUserCheckpointIdx = findLastIdx(thread.messages, (m) => m.role === 'checkpoint' && m.type === 'after_user_edits')
// const prevLLMCheckpointIdx = thread.messages.findIndex((m, i) => i > lastUserCheckpointIdx && m.role === 'checkpoint' && m.type === 'after_tool_edits')
// let prevLLMCheckpoint: LLMCheckpoint | undefined = undefined
// if (prevLLMCheckpointIdx !== -1) {
// prevLLMCheckpoint = thread.messages[prevLLMCheckpointIdx] as ChatMessage & { role: 'checkpoint', type: 'after_tool_edits' }
// this._removeMessageFromThread(threadId, prevLLMCheckpointIdx)
// newLLMCheckpoint.afterStrOfURI = {
// ...newLLMCheckpoint.afterStrOfURI,
// ...prevLLMCheckpoint?.afterStrOfURI,
// }
// }
this._addCheckpoint(threadId, newLLMCheckpoint)
}
@ -894,7 +901,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
const c = this._getCheckpointAfter({ threadId, messageIdx })
if (c === undefined) return // should never happen
const fromIdx = thread.state.latestCheckpointIdx
const fromIdx = thread.state.currCheckpointIdx
if (fromIdx === null) return // should never happen
// TODO!!! change toIdx if there's a checkpointModification on the To, and add a checkpoint modification on the from
@ -990,7 +997,7 @@ We only need to do it for files that were edited since `from`, ie files between
}
}
this._setThreadState(threadId, { latestCheckpointIdx: toIdx })
this._setThreadState(threadId, { currCheckpointIdx: toIdx })
// TODO!!! add/merge a checkpoint modification if relevant
}

View file

@ -25,7 +25,7 @@ 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, getIsResoningEnabledState } from '../../../../common/modelCapabilities.js';
import { AlertTriangle, Ban, ChevronRight, Dot, Pencil, X } from 'lucide-react';
import { AlertTriangle, Ban, ChevronRight, Dot, Pencil, Undo, Undo2, X } from 'lucide-react';
import { ChatMessage, 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';
@ -988,7 +988,7 @@ const UserMessageComponent = ({ chatMessage, messageIdx, isCommitted, _scrollToB
</div>
{<EditSymbol
<EditSymbol
size={18}
className={`
absolute -top-1 -right-1
@ -1006,7 +1006,7 @@ const UserMessageComponent = ({ chatMessage, messageIdx, isCommitted, _scrollToB
onCloseEdit()
}
}}
/>}
/>
</div>
@ -1821,12 +1821,18 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent<T> } = {
const Checkpoint = ({ threadId, messageIdx }: { threadId: string; messageIdx: number }) => {
const accessor = useAccessor()
const chatThreadService = accessor.get('IChatThreadService')
return <button onClick={() => {
chatThreadService.jumpToCheckpointAfterMessageIdx({ threadId, messageIdx })
}}>
jump
</button>
const commandBarService = accessor.get('IVoidCommandBarService')
return <div
className='pointer-events-auto cursor-pointer select-none hover:brightness-125 flex items-center justify-center'
onClick={() => {
// reject all current changes and then jump back
commandBarService.acceptOrRejectAllFiles({ behavior: 'reject' })
chatThreadService.jumpToCheckpointAfterMessageIdx({ threadId, messageIdx })
}}>
<div className='bg-void-border-1 h-[1px] flex-grow'></div>
<div className='px-2'>Checkpoint</div>
<div className='bg-void-border-1 h-[1px] flex-grow'></div>
</div>
}
@ -2021,18 +2027,22 @@ export const SidebarChat = () => {
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 === numMessages - 1 && (isRunning === 'tool' || isRunning === 'awaiting_user')
return <ChatBubble key={getChatBubbleId(currentThread.id, i)}
chatMessage={message}
messageIdx={i}
isCommitted={true}
chatIsRunning={isRunning}
isLast={isLast}
threadId={threadId}
isToolBeingWritten={toolIsLoading}
_scrollToBottom={() => scrollToBottom(scrollContainerRef)}
/>
return <div className={`${currCheckpointIdx < i ? 'opacity-50 pointer-events-none select-none' : ''}`}>
<ChatBubble key={getChatBubbleId(currentThread.id, i)}
chatMessage={message}
messageIdx={i}
isCommitted={true}
chatIsRunning={isRunning}
isLast={isLast}
threadId={threadId}
isToolBeingWritten={toolIsLoading}
_scrollToBottom={() => scrollToBottom(scrollContainerRef)}
/>
</div>
})
}, [previousMessages, isRunning, currentThread, numMessages])