From 39ea63b20773c52ddc8fab081b36ae5630e1086c Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Sun, 20 Apr 2025 06:43:49 -0700 Subject: [PATCH] add download/upload/reset --- .../contrib/void/browser/chatThreadService.ts | 19 +- .../react/src/sidebar-tsx/ErrorBoundary.tsx | 1 - .../react/src/sidebar-tsx/SidebarChat.tsx | 53 ++-- .../src/void-onboarding/VoidOnboarding.tsx | 237 ++++++++++-------- .../react/src/void-settings-tsx/Settings.tsx | 229 ++++++++++++----- .../src/void-settings-tsx/WarningBox.tsx | 2 +- .../contrib/void/browser/toolsService.ts | 18 +- .../contrib/void/common/prompt/prompts.ts | 18 +- .../contrib/void/common/toolsServiceTypes.ts | 14 +- .../void/common/voidSettingsService.ts | 19 ++ 10 files changed, 387 insertions(+), 223 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index 3265e43b..4ee81654 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -202,6 +202,10 @@ export interface IChatThreadService { getCurrentFocusedMessageIdx(): number | undefined; isCurrentlyFocusingMessage(): boolean; setCurrentlyFocusedMessageIdx(messageIdx: number | undefined): void; + + dangerousSetState: (newState: ThreadsState) => void; + resetState: () => void; + // // current thread's staging selections // closeCurrentStagingSelectionsInMessage(opts: { messageIdx: number }): void; // closeCurrentStagingSelectionsInThread(): void; @@ -293,6 +297,16 @@ class ChatThreadService extends Disposable implements IChatThreadService { } + dangerousSetState = (newState: ThreadsState) => { + this.state = newState + this._onDidChangeCurrentThread.fire() + } + resetState = () => { + this.state = { allThreads: {}, currentThreadId: null as unknown as string } // see constructor + this.openNewThread() + this._onDidChangeCurrentThread.fire() + } + // !!! this is important for properly restoring URIs from storage // should probably re-use code from void/src/vs/base/common/marshalling.ts instead. but this is simple enough private _convertThreadDataFromStorage(threadsStr: string): ChatThreads { @@ -499,7 +513,7 @@ class ChatThreadService extends Disposable implements IChatThreadService { if (approvalType) { const autoApprove = this._settingsService.state.globalSettings.autoApprove[approvalType] // add a tool_request because we use it for UI if a tool is loading (this should be improved in the future) - this._addMessageToThread(threadId, { role: 'tool', type: 'tool_request', content: '(never)', result: null, name: toolName, params: toolParams, id: toolId, rawParams: opts.unvalidatedToolParams }) + this._addMessageToThread(threadId, { role: 'tool', type: 'tool_request', content: '(Awaiting user permission...)', result: null, name: toolName, params: toolParams, id: toolId, rawParams: opts.unvalidatedToolParams }) if (!autoApprove) { return { awaitingUserApproval: true } } @@ -679,8 +693,10 @@ class ChatThreadService extends Disposable implements IChatThreadService { // stop if interrupted. we don't have to do this for llmMessage because we have a stream token for it and onAbort gets called, but we don't have the equivalent for tools. // just detect tool interruption which is the same as chat interruption right now if (interrupted) { return } + if (aborted) { return } if (awaitingUserApproval) { + console.log('awaiting...') isRunningWhenEnd = 'awaiting_user' } else { @@ -691,7 +707,6 @@ class ChatThreadService extends Disposable implements IChatThreadService { } // end while (attempts) } // end while (send message) - // if awaiting user approval, keep isRunning true, else end isRunning this._setStreamState(threadId, { isRunning: isRunningWhenEnd }, 'merge') diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorBoundary.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorBoundary.tsx index 3720d1a6..9e882240 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorBoundary.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorBoundary.tsx @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------*/ import React, { Component, ErrorInfo, ReactNode } from 'react'; -import { ErrorDisplay } from './ErrorDisplay.js'; import { WarningBox } from '../void-settings-tsx/WarningBox.js'; interface Props { 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 0ad54462..5f8c84f6 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 @@ -1228,9 +1228,9 @@ const titleOfToolName = { 'create_file_or_folder': { done: `Created`, proposed: `Create`, running: loadingTitleWrapper(`Creating`) }, 'delete_file_or_folder': { done: `Deleted`, proposed: `Delete`, running: loadingTitleWrapper(`Deleting`) }, 'edit_file': { done: `Edited file`, proposed: 'Edit file', running: loadingTitleWrapper('Editing file') }, - 'run_terminal': { done: `Ran terminal`, proposed: 'Run terminal', running: loadingTitleWrapper('Running terminal') }, - 'open_bg_terminal': { done: `Opened terminal`, proposed: 'Open terminal', running: loadingTitleWrapper('Opening terminal') }, - 'kill_bg_terminal': { done: `Killed terminal`, proposed: 'Kill terminal', running: loadingTitleWrapper('Killing terminal') }, + 'run_command': { done: `Ran terminal`, proposed: 'Run terminal', running: loadingTitleWrapper('Running terminal') }, + 'open_persistent_terminal': { done: `Opened terminal`, proposed: 'Open terminal', running: loadingTitleWrapper('Opening terminal') }, + 'kill_persistent_terminal': { done: `Killed terminal`, proposed: 'Kill terminal', running: loadingTitleWrapper('Killing terminal') }, 'read_lint_errors': { done: `Read lint errors`, proposed: 'Read lint errors', running: loadingTitleWrapper('Reading lint errors') }, 'search_in_file': { done: 'Searched in file', proposed: 'Search in file', running: loadingTitleWrapper('Searching in file') }, } as const satisfies Record @@ -1310,19 +1310,19 @@ const toolNameToDesc = (toolName: ToolName, _toolParams: ToolCallParams[ToolName desc1Info: getRelative(toolParams.uri, accessor), } }, - 'run_terminal': () => { - const toolParams = _toolParams as ToolCallParams['run_terminal'] + 'run_command': () => { + const toolParams = _toolParams as ToolCallParams['run_command'] return { desc1: `"${toolParams.command}"`, desc1Info: toolParams.bgTerminalId } }, - 'open_bg_terminal': () => { - const toolParams = _toolParams as ToolCallParams['open_bg_terminal'] + 'open_persistent_terminal': () => { + const toolParams = _toolParams as ToolCallParams['open_persistent_terminal'] return { desc1: '' } }, - 'kill_bg_terminal': () => { - const toolParams = _toolParams as ToolCallParams['kill_bg_terminal'] + 'kill_persistent_terminal': () => { + const toolParams = _toolParams as ToolCallParams['kill_persistent_terminal'] return { desc1: toolParams.terminalId } }, 'get_dir_tree': () => { @@ -2045,7 +2045,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, // --- - 'run_terminal': { + 'run_command': { resultWrapper: ({ toolMessage }) => { const accessor = useAccessor() const commandService = accessor.get('ICommandService') @@ -2091,36 +2091,27 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, componentParams.desc2 = `(terminal ${params.bgTerminalId})` } - else if (toolMessage.type === 'rejected' || toolMessage.type === 'tool_error') { - if (params) { - const { bgTerminalId, command } = params - if (bgTerminalId) { - componentParams.desc2 = '(persistent terminal)' - if (terminalToolsService.terminalExists(bgTerminalId)) - componentParams.onClick = () => terminalToolsService.focusTerminal(bgTerminalId) - } - } - if (toolMessage.type === 'tool_error') { - const { result } = toolMessage - componentParams.children = {result} - } - } - else if (toolMessage.type === 'running_now' || toolMessage.type === 'tool_request') { - const { bgTerminalId } = toolMessage.params + else if (toolMessage.type === 'rejected' || toolMessage.type === 'tool_error' || toolMessage.type === 'running_now' || toolMessage.type === 'tool_request') { + const { bgTerminalId, command } = params if (bgTerminalId) { componentParams.desc2 = '(persistent terminal)' if (terminalToolsService.terminalExists(bgTerminalId)) componentParams.onClick = () => terminalToolsService.focusTerminal(bgTerminalId) } + if (toolMessage.type === 'tool_error') { + const { result } = toolMessage + componentParams.children = {result} + } } return } }, - 'open_bg_terminal': { + 'open_persistent_terminal': { resultWrapper: ({ toolMessage }) => { const accessor = useAccessor() + const terminalToolsService = accessor.get('ITerminalToolService') const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor) const title = getTitle(toolMessage) @@ -2136,6 +2127,12 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, if (toolMessage.type === 'success') { const { result } = toolMessage + const { terminalId } = result + if (terminalId) { + componentParams.desc2 = `(terminal ${terminalId})` + if (terminalToolsService.terminalExists(terminalId)) + componentParams.onClick = () => terminalToolsService.focusTerminal(terminalId) + } } else if (toolMessage.type === 'tool_error') { const { result } = toolMessage @@ -2149,7 +2146,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, return }, }, - 'kill_bg_terminal': { + 'kill_persistent_terminal': { resultWrapper: ({ toolMessage }) => { const accessor = useAccessor() const commandService = accessor.get('ICommandService') 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 1feed1e0..87eafad0 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 @@ -9,8 +9,9 @@ import { Brain, Check, ChevronRight, DollarSign, ExternalLink, Lock, X } from 'l import { displayInfoOfProviderName, ProviderName, providerNames, refreshableProviderNames } from '../../../../common/voidSettingsTypes.js'; import { getModelCapabilities, ollamaRecommendedModels } from '../../../../common/modelCapabilities.js'; import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js'; -import { AddModelInputBox, AnimatedCheckmarkButton, ollamaSetupInstructions, OneClickSwitchButton, SettingsForProvider } from '../void-settings-tsx/Settings.js'; +import { AddModelInputBox, AnimatedCheckmarkButton, OllamaSetupInstructions, OneClickSwitchButton, SettingsForProvider } from '../void-settings-tsx/Settings.js'; import { ColorScheme } from '../../../../../../../platform/theme/common/theme.js'; +import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'; const OVERRIDE_VALUE = false @@ -29,7 +30,9 @@ export const VoidOnboarding = () => { transition-all duration-1000 ${isOnboardingComplete ? 'opacity-0 pointer-events-none' : 'opacity-100 pointer-events-auto'} `} > - + + + ) @@ -303,11 +306,11 @@ const TableOfModelsForProvider = ({ providerName }: { providerName: ProviderName // info used to show the table - const infoOfModelName: Record = {} + const infoOfModelName: Record = {} voidSettingsState.settingsOfProvider[providerName].models.forEach(m => { infoOfModelName[m.modelName] = { - showAsDefault: m.type === 'default', + showAsDefault: m.type !== 'custom', isDownloaded: true } }) @@ -317,7 +320,7 @@ const TableOfModelsForProvider = ({ providerName }: { providerName: ProviderName for (const modelName of ollamaRecommendedModels) { if (modelName in infoOfModelName) continue infoOfModelName[modelName] = { - ...infoOfModelName[modelName], + isDownloaded: infoOfModelName[modelName]?.isDownloaded ?? false, showAsDefault: true, } } @@ -339,7 +342,7 @@ const TableOfModelsForProvider = ({ providerName }: { providerName: ProviderName {Object.keys(infoOfModelName).map(modelName => { - const { showAsDefault, isDownloaded } = infoOfModelName[modelName] + const { showAsDefault, isDownloaded } = infoOfModelName[modelName] ?? {} const capabilities = getModelCapabilities(providerName, modelName) @@ -380,7 +383,7 @@ const TableOfModelsForProvider = ({ providerName }: { providerName: ProviderName {/* */} {isDetectableLocally && {!!isDownloaded ? : <>}} {providerName === 'ollama' && - + } @@ -388,10 +391,13 @@ const TableOfModelsForProvider = ({ providerName }: { providerName: ProviderName })} - + + + @@ -672,19 +678,22 @@ const VoidOnboardingContent = () => { { id: 'cheap', label: 'Affordable' }, { id: 'all', label: 'All' } ].map(option => ( - + + ))} @@ -693,108 +702,129 @@ const VoidOnboardingContent = () => { {/* Provider Buttons - Modified to use separate components for each tab */}
{/* Intelligent tab */} -
- {providerNamesOfWantToUseOption['smart'].map((providerName) => { - const isSelected = selectedIntelligentProvider === providerName; - return ( - - ); - })} -
+ > + {displayInfoOfProviderName(providerName).title} + + ); + })} +
+ + {/* Private tab */} -
- {providerNamesOfWantToUseOption['private'].map((providerName) => { - const isSelected = selectedPrivateProvider === providerName; - return ( - - ); - })} -
+ > + {displayInfoOfProviderName(providerName).title} + + ); + })} + + + {/* Affordable tab */} -
- {providerNamesOfWantToUseOption['cheap'].map((providerName) => { - const isSelected = selectedAffordableProvider === providerName; - return ( - - ); - })} -
+ > + {displayInfoOfProviderName(providerName).title} + + ); + })} + + + {/* All tab */} -
- {providerNames.map((providerName) => { - const isSelected = selectedAllProvider === providerName; - return ( - - ); - })} -
+ > + {displayInfoOfProviderName(providerName).title} + + ); + })} + + {/* Description */} -
- -
+ +
+ +
+
{/* ModelsTable and ProviderFields */} {selectedProviderName &&
- - {/* Models Table */} - + + + {/* Add provider section - simplified styling */} +
-
- Add {displayInfoOfProviderName(selectedProviderName).title} + +
+ Add {displayInfoOfProviderName(selectedProviderName).title} + +
+ {selectedProviderName === 'ollama' ? : ''} +
-
- {selectedProviderName === 'ollama' ? ollamaSetupInstructions : ''}
+ -
- - {selectedProviderName && - - } + + {selectedProviderName && + + } + {/* Button and status indicators */} - {!didFillInProviderSettings ?

Please fill in all fields to continue

- : !isAtLeastOneModel ?

Please add a model to continue

- : !isApiKeyLongEnoughIfApiKeyExists ?

Please enter a valid API key

- : } + + {!didFillInProviderSettings ?

Please fill in all fields to continue

+ : !isAtLeastOneModel ?

Please add a model to continue

+ : !isApiKeyLongEnoughIfApiKeyExists ?

Please enter a valid API key

+ : } +
} @@ -802,10 +832,11 @@ const VoidOnboardingContent = () => { } bottom={ - - {prevAndNextButtons} - - + + + {prevAndNextButtons} + + } />, @@ -864,7 +895,9 @@ const VoidOnboardingContent = () => { return
- {contentOfIdx[pageIndex]} + + {contentOfIdx[pageIndex]} +
} diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx index 606ee858..a0e69fd8 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx @@ -3,7 +3,7 @@ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------*/ -import React, { useCallback, useEffect, useMemo, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react' import { ProviderName, SettingName, displayInfoOfSettingName, providerNames, VoidStatefulModelInfo, customSettingNamesOfProvider, RefreshableProviderName, refreshableProviderNames, displayInfoOfProviderName, nonlocalProviderNames, localProviderNames, GlobalSettingName, featureNames, displayInfoOfFeatureName, isProviderNameDisabled, FeatureName, hasDownloadButtonsOnModelsProviderNames, subTextMdOfProviderName } from '../../../../common/voidSettingsTypes.js' import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js' import { VoidButtonBgDarken, VoidCustomDropdownBox, VoidInputBox2, VoidSimpleInputBox, VoidSwitch } from '../util/inputs.js' @@ -17,7 +17,7 @@ import { WarningBox } from './WarningBox.js' import { os } from '../../../../common/helpers/systemInfo.js' import { IconLoading } from '../sidebar-tsx/SidebarChat.js' import { ToolApprovalType, toolApprovalTypes } from '../../../../common/toolsServiceTypes.js' - +import Severity from '../../../../../../../base/common/severity.js' const ButtonLeftTextRightOption = ({ text, leftButton }: { text: string, leftButton?: React.ReactNode }) => { @@ -166,14 +166,14 @@ export const AddModelInputBox = ({ providerName: permanentProviderName, classNam const [showCheckmark, setShowCheckmark] = useState(false) // const providerNameRef = useRef(null) - const [userChosenProviderName, setUserChosenProviderName] = useState('anthropic') + const [userChosenProviderName, setUserChosenProviderName] = useState(null) const providerName = permanentProviderName ?? userChosenProviderName; const [modelName, setModelName] = useState('') const [errorString, setErrorString] = useState('') - const numModels = settingsState.settingsOfProvider[providerName].models.length + const numModels = providerName === null ? 0 : settingsState.settingsOfProvider[providerName].models.length if (showCheckmark) { return @@ -199,59 +199,66 @@ export const AddModelInputBox = ({ providerName: permanentProviderName, classNam */} {/* provider input */} - {!permanentProviderName && - setUserChosenProviderName(pn)} - getOptionDisplayName={(pn) => pn ? displayInfoOfProviderName(pn).title : 'Provider Name'} - getOptionDropdownName={(pn) => pn ? displayInfoOfProviderName(pn).title : 'Provider Name'} - getOptionsEqual={(a, b) => a === b} - // className={`max-w-44 w-full border border-void-border-2 bg-void-bg-1 text-void-fg-3 text-root py-[4px] px-[6px]`} - className={`max-w-32 mx-2 w-full resize-none bg-void-bg-1 text-void-fg-1 placeholder:text-void-fg-3 border border-void-border-2 focus:border-void-border-1 py-1 px-2 rounded`} - arrowTouchesText={false} - /> - } + + {!permanentProviderName && + setUserChosenProviderName(pn)} + getOptionDisplayName={(pn) => pn ? displayInfoOfProviderName(pn).title : 'Provider Name'} + getOptionDropdownName={(pn) => pn ? displayInfoOfProviderName(pn).title : 'Provider Name'} + getOptionsEqual={(a, b) => a === b} + // className={`max-w-44 w-full border border-void-border-2 bg-void-bg-1 text-void-fg-3 text-root py-[4px] px-[6px]`} + className={`max-w-32 mx-2 w-full resize-none bg-void-bg-1 text-void-fg-1 placeholder:text-void-fg-3 border border-void-border-2 focus:border-void-border-1 py-1 px-2 rounded`} + arrowTouchesText={false} + /> + } + + {/* model input */} - + + + {/* add button */} - { - if (providerName === null) { - setErrorString('Please select a provider.') - return - } - if (!modelName) { - setErrorString('Please enter a model name.') - return - } - // if model already exists here - if (settingsState.settingsOfProvider[providerName].models.find(m => m.modelName === modelName)) { - // setErrorString(`This model already exists under ${providerName}.`) - setErrorString(`This model already exists.`) - return - } + + { + if (providerName === null) { + setErrorString('Please select a provider.') + return + } + if (!modelName) { + setErrorString('Please enter a model name.') + return + } + // if model already exists here + if (settingsState.settingsOfProvider[providerName].models.find(m => m.modelName === modelName)) { + // setErrorString(`This model already exists under ${providerName}.`) + setErrorString(`This model already exists.`) + return + } - settingsStateService.addModel(providerName, modelName) - setShowCheckmark(true) - setTimeout(() => { - setShowCheckmark(false) - setIsOpen(false) - }, 1500) - setErrorString('') - setModelName('') - }} - /> + settingsStateService.addModel(providerName, modelName) + setShowCheckmark(true) + setTimeout(() => { + setShowCheckmark(false) + setIsOpen(false) + }, 1500) + setErrorString('') + setModelName('') + }} + /> + @@ -552,18 +559,20 @@ const FastApplyMethodDropdown = () => { } -export const ollamaSetupInstructions =
-
-
-
-
- +export const OllamaSetupInstructions = () => { + return
+
+
+
+
+ +
+
-
-
+} const RedoOnboardingButton = ({ className }: { className?: string }) => { @@ -823,6 +832,70 @@ export const Settings = () => { const nativeHostService = accessor.get('INativeHostService') const settingsState = useSettingsState() const voidSettingsService = accessor.get('IVoidSettingsService') + const chatThreadsService = accessor.get('IChatThreadService') + const notificationService = accessor.get('INotificationService') + + const onDownload = (t: 'Chats' | 'Settings') => { + let dataStr: string + let downloadName: string + if (t === 'Chats') { + dataStr = JSON.stringify(voidSettingsService.state, null, 2) + downloadName = 'void-chats.json' + } + else if (t === 'Settings') { + dataStr = JSON.stringify(chatThreadsService.state, null, 2) + downloadName = 'void-settings.json' + } + else { + dataStr = '' + downloadName = '' + } + + const blob = new Blob([dataStr], { type: 'application/json' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = downloadName + a.click() + URL.revokeObjectURL(url) + } + + + // Add file input refs + const fileInputSettingsRef = useRef(null) + const fileInputChatsRef = useRef(null) + + const [s, ss] = useState(0) + + const handleUpload = (t: 'Chats' | 'Settings') => (e: React.ChangeEvent,) => { + const files = e.target.files + if (!files) return; + const file = files[0] + if (!file) return + + const reader = new FileReader(); + reader.onload = () => { + try { + const json = JSON.parse(reader.result as string); + + if (t === 'Chats') { + chatThreadsService.dangerousSetState(json as any) + } + else if (t === 'Settings') { + voidSettingsService.dangerousSetState(json as any) + } + + notificationService.info(`${t} imported successfully!`) + } catch (err) { + notificationService.notify({ message: `Failed to import ${t}`, source: err + '', severity: Severity.Error, }) + } + }; + reader.readAsText(file); + e.target.value = ''; + + ss(s => s + 1) + } + return
@@ -834,6 +907,34 @@ export const Settings = () => { {/* separator */}
+ {/* Download & Upload Settings and Chats */} +
+ + { fileInputSettingsRef.current?.click() }}> + Upload Settings + + onDownload('Settings')}> + Download Settings + + + + + { fileInputChatsRef.current?.click() }}> + Upload Chats + + onDownload('Chats')}> + Download Chats + + + + { voidSettingsService.resetState() }}> + Reset Settings + + { chatThreadsService.resetState() }}> + Reset Chats + +
+ {/* Models section (formerly FeaturesTab) */}

Models

@@ -849,7 +950,7 @@ export const Settings = () => {

{`Void can access any model that you host locally. We automatically detect your local models by default.`}

- {ollamaSetupInstructions} +
diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/WarningBox.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/WarningBox.tsx index 43faedd8..97e0e4c3 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/WarningBox.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/WarningBox.tsx @@ -15,7 +15,7 @@ export const WarningBox = ({ text, onClick, className }: { text: string; onClick > {text}
diff --git a/src/vs/workbench/contrib/void/browser/toolsService.ts b/src/vs/workbench/contrib/void/browser/toolsService.ts index fe88dbe6..b6dcf819 100644 --- a/src/vs/workbench/contrib/void/browser/toolsService.ts +++ b/src/vs/workbench/contrib/void/browser/toolsService.ts @@ -250,17 +250,17 @@ export class ToolsService implements IToolsService { // --- - run_terminal: (params: RawToolParamsObj) => { + run_command: (params: RawToolParamsObj) => { const { command: commandUnknown, terminal_id: terminalIdUnknown } = params; const command = validateStr('command', commandUnknown); const proposedTerminalId = terminalIdUnknown ? validateProposedTerminalId(terminalIdUnknown) : null; return { command, bgTerminalId: proposedTerminalId }; }, - open_bg_terminal: (_params: RawToolParamsObj) => { + open_persistent_terminal: (_params: RawToolParamsObj) => { // No parameters needed; will open a new background terminal return {}; }, - kill_bg_terminal: (params: RawToolParamsObj) => { + kill_persistent_terminal: (params: RawToolParamsObj) => { const { terminal_id: terminalIdUnknown } = params; const terminalId = validateProposedTerminalId(terminalIdUnknown); return { terminalId }; @@ -414,19 +414,19 @@ export class ToolsService implements IToolsService { return { result: lintErrorsPromise, interruptTool } }, // --- - run_terminal: async ({ command, bgTerminalId }) => { + run_command: async ({ command, bgTerminalId }) => { const { terminalId, resPromise } = await this.terminalToolService.runCommand(command, bgTerminalId) const interruptTool = () => { this.terminalToolService.killTerminal(terminalId) } return { result: resPromise, interruptTool } }, - open_bg_terminal: async () => { + open_persistent_terminal: async () => { // Open a new background terminal without waiting for completion const terminalId = await this.terminalToolService.createTerminal() return { result: { terminalId } } }, - kill_bg_terminal: async ({ terminalId }) => { + kill_persistent_terminal: async ({ terminalId }) => { // Close the background terminal by sending exit await this.terminalToolService.killTerminal(terminalId) return { result: {} } @@ -492,7 +492,7 @@ export class ToolsService implements IToolsService { return `Change successfully made to ${params.uri.fsPath}.${lintErrsString}` }, - run_terminal: (params, result) => { + run_command: (params, result) => { const { resolveReason, result: result_, @@ -520,11 +520,11 @@ export class ToolsService implements IToolsService { throw new Error(`Unexpected internal error: Terminal command did not resolve with a valid reason.`) }, - open_bg_terminal: (_params, result) => { + open_persistent_terminal: (_params, result) => { const { terminalId } = result; return `Successfully created background terminal with ID ${terminalId}`; }, - kill_bg_terminal: (params, _result) => { + kill_persistent_terminal: (params, _result) => { return `Successfully closed terminal ${params.terminalId}.`; }, diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index ffe9a8d9..ab88d57b 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -214,22 +214,22 @@ Here's an example of a good description:\n${editToolDescriptionExample}` }, }, - run_terminal: { - name: 'run_terminal', + run_command: { + name: 'run_command', description: `Runs a terminal command and waits for the result (times out after ${MAX_TERMINAL_INACTIVE_TIME}s of inactivity). You can use this tool to run any command: sed, grep, etc. Do not edit any files with this tool; use edit_file instead. When working with git and other tools that open an editor like vim, you might need to pipe to cat to get all results.`, params: { command: { description: 'The terminal command to run.' }, - bg_terminal_id: { description: 'Optional. This only applies to terminals that have been opened with open_bg_terminal. Runs the command in the terminal with the specified ID.' }, + bg_terminal_id: { description: 'Optional. This only applies to terminals that have been opened with open_persistent_terminal. Runs the command in the terminal with the specified ID.' }, }, }, - open_bg_terminal: { - name: 'open_bg_terminal', + open_persistent_terminal: { + name: 'open_persistent_terminal', description: `Use this tool when you want to run a terminal command indefinitely, like a dev server (eg \`npm run dev\`), a background listener, etc. Opens a new terminal in the user's environment which will not awaited for or killed.`, params: {} }, - kill_bg_terminal: { - name: 'kill_bg_terminal', + kill_persistent_terminal: { + name: 'kill_persistent_terminal', description: `Closes a BG terminal with the given ID.`, params: { terminal_id: { description: `The terminal ID to interrupt and close.` } } } @@ -518,9 +518,9 @@ ${DIVIDER} ${FINAL} ${tripleTick[1]} -1. Every single item written in \`CHANGE\` should show up in the final result, except for comments explicitly saying things like "// ... existing code". Make sure to include ALL other comments (even descriptive ones), code, whitespace, etc. in the final result. +1. Your SEARCH/REPLACE block(s) must implement the change EXACTLY. -2. Your SEARCH/REPLACE block(s) must implement the change EXACTLY. You should use comments like "// ... existing code" as reference points, and everything else in the change should be written verbatim. +2. Assume any comments in the diff are PART OF THE CHANGE. Include them in the output. 3. You are allowed to output multiple SEARCH/REPLACE blocks. diff --git a/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts b/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts index 54151ff0..56bbf6fa 100644 --- a/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts +++ b/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts @@ -20,7 +20,7 @@ export const approvalTypeOfToolName: Partial<{ [T in ToolName]?: 'edits' | 'term 'create_file_or_folder': 'edits', 'delete_file_or_folder': 'edits', 'edit_file': 'edits', - 'run_terminal': 'terminal', + 'run_command': 'terminal', } @@ -46,9 +46,9 @@ export type ToolCallParams = { 'create_file_or_folder': { uri: URI, isFolder: boolean }, 'delete_file_or_folder': { uri: URI, isRecursive: boolean, isFolder: boolean }, // --- - 'run_terminal': { command: string; bgTerminalId: string | null }, - 'open_bg_terminal': {}, - 'kill_bg_terminal': { terminalId: string }, + 'run_command': { command: string; bgTerminalId: string | null }, + 'open_persistent_terminal': {}, + 'kill_persistent_terminal': { terminalId: string }, } // RESULT OF TOOL CALL @@ -65,8 +65,8 @@ export type ToolResultType = { 'create_file_or_folder': {}, 'delete_file_or_folder': {}, // --- - 'run_terminal': { result: string; resolveReason: TerminalResolveReason; }, - 'open_bg_terminal': { terminalId: string }, - 'kill_bg_terminal': {}, + 'run_command': { result: string; resolveReason: TerminalResolveReason; }, + 'open_persistent_terminal': { terminalId: string }, + 'kill_persistent_terminal': {}, } diff --git a/src/vs/workbench/contrib/void/common/voidSettingsService.ts b/src/vs/workbench/contrib/void/common/voidSettingsService.ts index 2cb033ca..ad7c6a7f 100644 --- a/src/vs/workbench/contrib/void/common/voidSettingsService.ts +++ b/src/vs/workbench/contrib/void/common/voidSettingsService.ts @@ -62,6 +62,9 @@ export interface IVoidSettingsService { setOptionsOfModelSelection: SetOptionsOfModelSelection; setGlobalSetting: SetGlobalSettingFn; + dangerousSetState(newState: VoidSettingsState): Promise; + resetState(): Promise; + setAutodetectedModels(providerName: ProviderName, modelNames: string[], logging: object): void; toggleModelHidden(providerName: ProviderName, modelName: string): void; addModel(providerName: ProviderName, modelName: string): void; @@ -231,6 +234,22 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { this.readAndInitializeState() } + + + + dangerousSetState = async (newState: VoidSettingsState) => { + this.state = _validatedModelState(newState) + await this._storeState() + this._onDidChangeState.fire() + this._onUpdate_syncApplyToChat() + } + async resetState() { + await this.dangerousSetState(defaultState()) + } + + + + async readAndInitializeState() { let readS: VoidSettingsState try {