diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index 8afa92dd..7a206021 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -349,36 +349,6 @@ class ChatThreadService extends Disposable implements IChatThreadService { - editUserMessageAndStreamResponse: IChatThreadService['editUserMessageAndStreamResponse'] = async ({ userMessage, messageIdx, threadId }) => { - - const thread = this.state.allThreads[threadId] - if (!thread) return // should never happen - - if (thread.messages?.[messageIdx]?.role !== 'user') { - throw new Error(`Error: editing a message with role !=='user'`) - } - - // get prev and curr selections before clearing the message - const currSelns = thread.messages[messageIdx].state.stagingSelections || [] // staging selections for the edited message - - // clear messages up to the index - const slicedMessages = thread.messages.slice(0, messageIdx) - this._setState({ - allThreads: { - ...this.state.allThreads, - [thread.id]: { - ...thread, - messages: slicedMessages - } - } - }, true) - - // re-add the message and stream it - this.addUserMessageAndStreamResponse({ userMessage, _chatSelections: currSelns, threadId }) - - } - - private _currentModelSelectionProps = () => { // these settings should not change throughout the loop (eg anthropic breaks if you change its thinking mode and it's using tools) const featureName: FeatureName = 'Chat' @@ -883,7 +853,7 @@ class ChatThreadService extends Disposable implements IChatThreadService { const [_, toIdx] = c if (toIdx === fromIdx) return - // console.log(`going from ${fromIdx} to ${toIdx}`) + console.log(`going from ${fromIdx} to ${toIdx}`) // update the user's checkpoint this._addUserModificationsToCurrCheckpoint({ threadId }) @@ -895,15 +865,14 @@ A,B,C are all files. x means a checkpoint where the file changed. A B C D E F G H I -x x x x x x x x x -| | | | | | | | | -x | | | | | | | x ----x-|-|-|-x-|-x-|----- <-- to - x | | | | | x - | | x x | - | | | | --------x-|---x-x------- <-- from - x + x x x x x x <-- you can't always go up to find the "before" version; sometimes you need to go down + | | | | | | x +--x-|-|-|-x---x-|----- <-- to + | | | | x x + | | x x | + | | | | +----x-|---x-x------- <-- from + x We need to revert anything that happened between to+1 and from. **We do this by finding the last x from 0...`to` for each file and applying those contents.** @@ -911,9 +880,19 @@ We only need to do it for files that were edited since `to`, ie files between to */ if (toIdx < fromIdx) { const { lastIdxOfURI } = this._getCheckpointsBetween({ threadId, loIdx: toIdx + 1, hiIdx: fromIdx }) + + const idxes = function* () { + for (let k = toIdx; k >= 0; k -= 1) { // first go up + yield k + } + for (let k = toIdx + 1; k < thread.messages.length; k += 1) { // then go down + yield k + } + } + for (const fsPath in lastIdxOfURI) { - // apply lowest down content for each uri (or original if not found) - for (let k = toIdx; k >= 0; k -= 1) { + // find the first instance of this file starting at toIdx (go up to latest file; if there is none, go down) + for (const k of idxes()) { const message = thread.messages[k] if (message.role !== 'checkpoint') continue const res = this._getCheckpointInfo(message, fsPath, { includeUserModifiedChanges: jumpToUserModified }) @@ -929,16 +908,16 @@ We only need to do it for files that were edited since `to`, ie files between to /* if redoing -A B C D E F G H I -x x x x x x x x x -| | | | | | | | | -x | | | | | | | x ----x-|-|-|-x-|-x-|----- <-- from - x | | | | | x - | | x x | - | | | | --------x-|---x-x------- <-- to - x +A B C D E F G H I J + x x x x x x x + | | | | | | x x x +--x-|-|-|-x---x-|-|--- <-- from + | | | | x x + | | x x | + | | | | +----x-|---x-x-----|--- <-- to + x x + We need to apply latest change for anything that happened between from+1 and to. We only need to do it for files that were edited since `from`, ie files between from+1...to. @@ -954,7 +933,6 @@ We only need to do it for files that were edited since `from`, ie files between if (!res) continue const { voidFileSnapshot } = res if (!voidFileSnapshot) continue - this._editCodeService.restoreVoidFileSnapshot(URI.file(fsPath), voidFileSnapshot) break } @@ -1003,11 +981,15 @@ We only need to do it for files that were edited since `from`, ie files between }) } - async addUserMessageAndStreamResponse({ userMessage, _chatSelections, threadId }: { userMessage: string, _chatSelections?: StagingSelectionItem[], threadId: string }) { + dismissStreamError(threadId: string): void { + this._setStreamState(threadId, { error: undefined }, 'merge') + } + + + private async _addUserMessageAndStreamResponse({ userMessage, _chatSelections, threadId }: { userMessage: string, _chatSelections?: StagingSelectionItem[], threadId: string }) { const thread = this.state.allThreads[threadId] if (!thread) return // should never happen - const llmCancelToken = this.streamState[threadId]?.streamingToken // currently streaming LLM on this thread if (llmCancelToken === undefined && this.streamState[threadId]?.isRunning === 'LLM') { // if about to call the other LLM, just wait for it by stopping right now @@ -1016,14 +998,11 @@ We only need to do it for files that were edited since `from`, ie files between // stop it (this simply resolves the promise to free up space) if (llmCancelToken !== undefined) this._llmMessageService.abort(llmCancelToken) - - // add dummy before this message to keep checkpoint before user message idea consistent if (thread.messages.length === 0) { this._addUserCheckpoint({ threadId }) } - const { chatMode } = this._settingsService.state.globalSettings // add user's message to chat history @@ -1043,11 +1022,61 @@ We only need to do it for files that were edited since `from`, ie files between ) } - dismissStreamError(threadId: string): void { - this._setStreamState(threadId, { error: undefined }, 'merge') + + async addUserMessageAndStreamResponse({ userMessage, _chatSelections, threadId }: { userMessage: string, _chatSelections?: StagingSelectionItem[], threadId: string }) { + const thread = this.state.allThreads[threadId]; + if (!thread) return + + // if there's a current checkpoint, delete all messages after it + if (thread.state.currCheckpointIdx !== null) { + const checkpointIdx = thread.state.currCheckpointIdx; + const newMessages = thread.messages.slice(0, checkpointIdx + 1); + + // Update the thread with truncated messages + const newThreads = { + ...this.state.allThreads, + [threadId]: { + ...thread, + lastModified: new Date().toISOString(), + messages: newMessages, + } + }; + this._storeAllThreads(newThreads); + this._setState({ allThreads: newThreads }, true); + } + + // Now call the original method to add the user message and stream the response + await this._addUserMessageAndStreamResponse({ userMessage, _chatSelections, threadId }); + } + editUserMessageAndStreamResponse: IChatThreadService['editUserMessageAndStreamResponse'] = async ({ userMessage, messageIdx, threadId }) => { + const thread = this.state.allThreads[threadId] + if (!thread) return // should never happen + + if (thread.messages?.[messageIdx]?.role !== 'user') { + throw new Error(`Error: editing a message with role !=='user'`) + } + + // get prev and curr selections before clearing the message + const currSelns = thread.messages[messageIdx].state.stagingSelections || [] // staging selections for the edited message + + // clear messages up to the index + const slicedMessages = thread.messages.slice(0, messageIdx) + this._setState({ + allThreads: { + ...this.state.allThreads, + [thread.id]: { + ...thread, + messages: slicedMessages + } + } + }, true) + + // re-add the message and stream it + this._addUserMessageAndStreamResponse({ userMessage, _chatSelections: currSelns, threadId }) + } // ---------- the rest ---------- diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index 78fafa7c..72d90ce7 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -258,6 +258,22 @@ class EditCodeService extends Disposable implements IEditCodeService { this._realignAllDiffAreasLines(uri, change.text, change.range) } this._refreshStylesAndDiffsInURI(uri) + + // if diffarea has no diffs after a user edit, delete it + const diffAreasToDelete: DiffZone[] = [] + for (const diffareaid of this.diffAreasOfURI[uri.fsPath] ?? []) { + const diffArea = this.diffAreaOfId[diffareaid] ?? null + const shouldDelete = diffArea?.type === 'DiffZone' && Object.keys(diffArea._diffOfId).length === 0 + if (shouldDelete) { + diffAreasToDelete.push(diffArea) + } + } + if (diffAreasToDelete.length !== 0) { + const { onFinishEdit } = this._addToHistory(uri) + diffAreasToDelete.forEach(da => this._deleteDiffZone(da)) + onFinishEdit() + } + } 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 ab7bca52..97ffa768 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 @@ -1949,10 +1949,14 @@ const Checkpoint = ({ message, threadId, messageIdx, isCheckpointGhost, threadIs style={{ position: 'relative', display: 'inline-block' }} // allow absolute icon onClick={() => { if (threadIsRunning) return - chatThreadService.jumpToCheckpointBeforeMessageIdx({ threadId, messageIdx, jumpToUserModified: true }) + chatThreadService.jumpToCheckpointBeforeMessageIdx({ + threadId, + messageIdx, + jumpToUserModified: messageIdx === (chatThreadService.state.allThreads[threadId]?.messages.length ?? 0) - 1 + }) }} > - Checkpoint + Checkpoint } diff --git a/src/vs/workbench/contrib/void/browser/react/src/styles.css b/src/vs/workbench/contrib/void/browser/react/src/styles.css index e7a3da15..13257709 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/styles.css +++ b/src/vs/workbench/contrib/void/browser/react/src/styles.css @@ -11,7 +11,7 @@ --void-bg-1: var(--vscode-input-background); --void-bg-1-alt: var(--vscode-badge-background); --void-bg-2: var(--vscode-sideBar-background); - --void-bg-2-alt: color-mix(in srgb, var(--vscode-sideBar-background) 30%, var(--vscode-editor-background) 70%); + --void-bg-2-alt: color-mix(in srgb, var(--vscode-editor-background) 30%, var(--vscode-sideBar-background) 70%); --void-bg-3: var(--vscode-editor-background); --void-fg-0: color-mix(in srgb, var(--vscode-tab-activeForeground) 90%, black 10%); diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx index b7e31757..c0f00c53 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx @@ -664,57 +664,60 @@ export const VoidCustomDropdownBox = >({ {isOpen && (
- {options.map((option) => { - const thisOptionIsSelected = getOptionsEqual(option, selectedOption); - const optionName = getOptionDropdownName(option); - const optionDetail = getOptionDropdownDetail?.(option) || ''; + onWheel={(e) => e.stopPropagation()} + >
- return ( -
{ + const thisOptionIsSelected = getOptionsEqual(option, selectedOption); + const optionName = getOptionDropdownName(option); + const optionDetail = getOptionDropdownDetail?.(option) || ''; + + return ( +
{ - onChangeOption(option); - setIsOpen(false); - }} - > -
- {thisOptionIsSelected && ( - - - - )} + onClick={() => { + onChangeOption(option); + setIsOpen(false); + }} + > +
+ {thisOptionIsSelected && ( + + + + )} +
+ + {optionName} + {optionDetail} +
- - {optionName} - {optionDetail} - -
- ); - })} + ); + })} +
+
)}
diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx index 3a885c6c..47b3ce1c 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx @@ -510,7 +510,7 @@ const VoidOnboardingContent = () => { content={
{/*
AI Preferences
*/} -
What are you looking for in an AI model?
+
Model Preferences
@@ -520,7 +520,7 @@ const VoidOnboardingContent = () => { >
-
Intelligence
+
Intelligent
{basicDescOfWantToUseOption['smart']}
@@ -531,7 +531,7 @@ const VoidOnboardingContent = () => { >
-
Privacy
+
Private
{basicDescOfWantToUseOption['private']}
@@ -542,7 +542,7 @@ const VoidOnboardingContent = () => { >
-
Affordability
+
Affordable
{basicDescOfWantToUseOption['cheap']}
diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx index 813968a4..a445e3bf 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx @@ -44,37 +44,6 @@ const ModelSelectBox = ({ options, featureName, className }: { options: ModelOpt matchInputWidth={false} /> } -// const ModelSelectBox = ({ options, featureName }: { options: ModelOption[], featureName: FeatureName }) => { -// const accessor = useAccessor() - -// const voidSettingsService = accessor.get('IVoidSettingsService') - -// let weChangedText = false - -// return { -// if (weChangedText) return -// voidSettingsService.setModelSelectionOfFeature(featureName, newVal) -// }, [voidSettingsService, featureName])} -// // we are responsible for setting the initial state here. always sync instance when state changes. -// onCreateInstance={useCallback((instance: SelectBox) => { -// const syncInstance = () => { -// const modelsListRef = voidSettingsService.state._modelOptions // as a ref -// const settingsAtProvider = voidSettingsService.state.modelSelectionOfFeature[featureName] -// const selectionIdx = settingsAtProvider === null ? -1 : modelsListRef.findIndex(v => modelSelectionsEqual(v.value, settingsAtProvider)) -// weChangedText = true -// instance.select(selectionIdx === -1 ? 0 : selectionIdx) -// weChangedText = false -// } -// syncInstance() -// const disposable = voidSettingsService.onDidChangeState(syncInstance) -// return [disposable] -// }, [voidSettingsService, featureName])} -// /> -// } - const MemoizedModelDropdown = ({ featureName, className }: { featureName: FeatureName, className: string }) => {