From 1be8d178997ebab12aaff39fc256461902c5c959 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Wed, 5 Mar 2025 23:03:08 -0800 Subject: [PATCH] add autocomplete UI and filtering --- .../void/browser/react/src/util/inputs.tsx | 2 +- .../src/void-settings-tsx/ModelDropdown.tsx | 27 +++++--- .../react/src/void-settings-tsx/Settings.tsx | 35 ++++++++--- .../void/common/voidSettingsService.ts | 62 ++++++++++++++++--- .../contrib/void/common/voidSettingsTypes.ts | 8 ++- 5 files changed, 104 insertions(+), 30 deletions(-) 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 27a0d596..e4a2e4c6 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 @@ -257,7 +257,7 @@ export const VoidSwitch = ({ {label && ( {label} 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 7ff66b32..a4ecde94 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 @@ -10,7 +10,7 @@ import { _VoidSelectBox, VoidCustomDropdownBox } from '../util/inputs.js' import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js' import { IconWarning } from '../sidebar-tsx/SidebarChat.js' import { VOID_OPEN_SETTINGS_ACTION_ID, VOID_TOGGLE_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js' -import { ModelOption } from '../../../../../../../workbench/contrib/void/common/voidSettingsService.js' +import { modelFilterOfFeatureName, ModelOption } from '../../../../../../../workbench/contrib/void/common/voidSettingsService.js' import { WarningBox } from './WarningBox.js' const optionsEqual = (m1: ModelOption[], m2: ModelOption[]) => { @@ -38,7 +38,7 @@ const ModelSelectBox = ({ options, featureName }: { options: ModelOption[], feat onChangeOption={onChangeOption} getOptionDisplayName={(option) => option.selection.modelName} getOptionDropdownName={(option) => option.selection.modelName} - getOptionDropdownDetail={(option) => option.selection.providerName } + getOptionDropdownDetail={(option) => option.selection.providerName} getOptionsEqual={(a, b) => optionsEqual([a], [b])} className='text-xs text-void-fg-3 px-1' matchInputWidth={false} @@ -82,14 +82,21 @@ const MemoizedModelDropdown = ({ featureName }: { featureName: FeatureName }) => const oldOptionsRef = useRef([]) const [memoizedOptions, setMemoizedOptions] = useState(oldOptionsRef.current) + const { filter, emptyMessage } = modelFilterOfFeatureName[featureName] + useEffect(() => { const oldOptions = oldOptionsRef.current - const newOptions = settingsState._modelOptions + const newOptions = settingsState._modelOptions.filter((o) => filter(o.selection)) + if (!optionsEqual(oldOptions, newOptions)) { setMemoizedOptions(newOptions) } oldOptionsRef.current = newOptions - }, [settingsState._modelOptions]) + }, [settingsState._modelOptions, filter]) + + if (memoizedOptions.length === 0) { // Pretty sure this will never be reached unless filter is enabled + return + } return @@ -103,13 +110,17 @@ export const ModelDropdown = ({ featureName }: { featureName: FeatureName }) => const openSettings = () => { commandService.executeCommand(VOID_OPEN_SETTINGS_ACTION_ID); }; + + const { emptyMessage } = modelFilterOfFeatureName[featureName] + const isDisabled = isFeatureNameDisabled(featureName, settingsState) if (isDisabled) return return 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 79c8fdb8..273cfe83 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 @@ -396,6 +396,11 @@ export const AIInstructionsBox = () => { } export const FeaturesTab = () => { + const voidSettingsState = useSettingsState() + const accessor = useAccessor() + const voidSettingsService = accessor.get('IVoidSettingsService') + + return <>

Models

@@ -434,17 +439,27 @@ export const FeaturesTab = () => { -

Feature Options

+

Feature Options

- {featureNames.map(featureName => - (['Ctrl+L', 'Ctrl+K'] as FeatureName[]).includes(featureName) ? null : -
-

{displayInfoOfFeatureName(featureName)}

- +
+
+

{displayInfoOfFeatureName('Autocomplete')}

+
Experimental. Only works with FIM models.
+
+ voidSettingsService.setGlobalSetting('enableAutocomplete', newVal)} label={voidSettingsState.globalSettings.enableAutocomplete ? 'Enabled' : 'Disabled'} />
- )} + +
+ +
+
+ +
+

{displayInfoOfFeatureName('Apply')}

+ +
+
+ @@ -649,7 +664,7 @@ export const Settings = () => { {/* content */} -
+
diff --git a/src/vs/workbench/contrib/void/common/voidSettingsService.ts b/src/vs/workbench/contrib/void/common/voidSettingsService.ts index 98ab45fc..279af6e3 100644 --- a/src/vs/workbench/contrib/void/common/voidSettingsService.ts +++ b/src/vs/workbench/contrib/void/common/voidSettingsService.ts @@ -11,11 +11,18 @@ import { registerSingleton, InstantiationType } from '../../../../platform/insta import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { IMetricsService } from './metricsService.js'; -import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, VoidModelInfo, GlobalSettings, GlobalSettingName, defaultGlobalSettings, defaultProviderSettings } from './voidSettingsTypes.js'; +import { getModelCapabilities } from './modelCapabilities.js'; +import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, VoidModelInfo, GlobalSettings, GlobalSettingName, defaultGlobalSettings, defaultProviderSettings, ModelSelectionOptions, OptionsOfModelSelection } from './voidSettingsTypes.js'; const STORAGE_KEY = 'void.settingsServiceStorage' + +// name is the name in the dropdown +export type ModelOption = { name: string, selection: ModelSelection } + + + type SetSettingOfProviderFn = ( providerName: ProviderName, settingName: S, @@ -25,16 +32,17 @@ type SetSettingOfProviderFn = ( type SetModelSelectionOfFeatureFn = ( featureName: K, newVal: ModelSelectionOfFeature[K], - options?: { doNotApplyEffects?: true } ) => Promise; type SetGlobalSettingFn = (settingName: T, newVal: GlobalSettings[T]) => void; -export type ModelOption = { name: string, selection: ModelSelection } +type SetOptionsOfModelSelection = (providerName: ProviderName, modelName: string, newVal: Partial) => void + export type VoidSettingsState = { readonly settingsOfProvider: SettingsOfProvider; // optionsOfProvider readonly modelSelectionOfFeature: ModelSelectionOfFeature; // stateOfFeature + readonly optionsOfModelSelection: OptionsOfModelSelection; readonly globalSettings: GlobalSettings; readonly _modelOptions: ModelOption[] // computed based on the two above items @@ -55,6 +63,7 @@ export interface IVoidSettingsService { setSettingOfProvider: SetSettingOfProviderFn; setModelSelectionOfFeature: SetModelSelectionOfFeatureFn; + setOptionsOfModelSelection: SetOptionsOfModelSelection; setGlobalSetting: SetGlobalSettingFn; setAutodetectedModels(providerName: ProviderName, modelNames: string[], logging: object): void; @@ -88,6 +97,14 @@ const _updatedModelsAfterDefaultModelsChange = (defaultModelNames: string[], opt } +export const modelFilterOfFeatureName: { [featureName in FeatureName]: { filter: (o: ModelSelection) => boolean; emptyMessage: string | null } } = { + 'Autocomplete': { filter: o => getModelCapabilities(o.providerName, o.modelName).supportsFIM, emptyMessage: 'No models support FIM' }, + 'Ctrl+L': { filter: o => true, emptyMessage: null }, + 'Ctrl+K': { filter: o => true, emptyMessage: null }, + 'Apply': { filter: o => true, emptyMessage: null }, +} + + const _validatedState = (state: Omit) => { let newSettingsOfProvider = state.settingsOfProvider @@ -125,14 +142,17 @@ const _validatedState = (state: Omit) => { let newModelSelectionOfFeature = state.modelSelectionOfFeature for (const featureName of featureNames) { - const modelSelectionAtFeature = newModelSelectionOfFeature[featureName] - const selnIdx = modelSelectionAtFeature === null ? -1 : newModelOptions.findIndex(m => modelSelectionsEqual(m.selection, modelSelectionAtFeature)) + const { filter } = modelFilterOfFeatureName[featureName] + const modelOptionsForThisFeature = newModelOptions.filter((o) => filter(o.selection)) - if (selnIdx !== -1) continue + const modelSelectionAtFeature = newModelSelectionOfFeature[featureName] + const selnIdx = modelSelectionAtFeature === null ? -1 : modelOptionsForThisFeature.findIndex(m => modelSelectionsEqual(m.selection, modelSelectionAtFeature)) + + if (selnIdx !== -1) continue // no longer in list, so update to 1st in list or null newModelSelectionOfFeature = { ...newModelSelectionOfFeature, - [featureName]: newModelOptions.length === 0 ? null : newModelOptions[0].selection + [featureName]: modelOptionsForThisFeature.length === 0 ? null : modelOptionsForThisFeature[0].selection } } @@ -156,6 +176,7 @@ const defaultState = () => { settingsOfProvider: deepClone(defaultSettingsOfProvider), modelSelectionOfFeature: { 'Ctrl+L': null, 'Ctrl+K': null, 'Autocomplete': null, 'Apply': null }, globalSettings: deepClone(defaultGlobalSettings), + optionsOfModelSelection: {}, _modelOptions: [], // computed later } return d @@ -260,6 +281,8 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { const newModelSelectionOfFeature = this.state.modelSelectionOfFeature + const newOptionsOfModelSelection = this.state.optionsOfModelSelection + const newSettingsOfProvider: SettingsOfProvider = { ...this.state.settingsOfProvider, [providerName]: { @@ -272,6 +295,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { const newState = { modelSelectionOfFeature: newModelSelectionOfFeature, + optionsOfModelSelection: newOptionsOfModelSelection, settingsOfProvider: newSettingsOfProvider, globalSettings: newGlobalSettings, } @@ -299,7 +323,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { } - setModelSelectionOfFeature: SetModelSelectionOfFeatureFn = async (featureName, newVal, options) => { + setModelSelectionOfFeature: SetModelSelectionOfFeatureFn = async (featureName, newVal) => { const newState: VoidSettingsState = { ...this.state, modelSelectionOfFeature: { @@ -310,8 +334,26 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { this.state = newState - if (options?.doNotApplyEffects) - return + await this._storeState() + this._onDidChangeState.fire() + } + + + setOptionsOfModelSelection = async (providerName: ProviderName, modelName: string, newVal: Partial) => { + const newState: VoidSettingsState = { + ...this.state, + optionsOfModelSelection: { + ...this.state.optionsOfModelSelection, + [providerName]: { + ...this.state.optionsOfModelSelection[providerName], + [modelName]: { + ...this.state.optionsOfModelSelection[providerName]?.[modelName], + ...newVal + } + } + } + } + this.state = newState await this._storeState() this._onDidChangeState.fire() diff --git a/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts b/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts index a933fae5..3c9c2046 100644 --- a/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts +++ b/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts @@ -380,7 +380,7 @@ export const displayInfoOfFeatureName = (featureName: FeatureName) => { else if (featureName === 'Ctrl+L') return 'Chat' else if (featureName === 'Apply') - return 'Apply' + return 'Fast Apply' else throw new Error(`Feature Name ${featureName} not allowed`) } @@ -439,10 +439,12 @@ export const isFeatureNameDisabled = (featureName: FeatureName, settingsState: V export type GlobalSettings = { autoRefreshModels: boolean; aiInstructions: string; + enableAutocomplete: boolean; } export const defaultGlobalSettings: GlobalSettings = { autoRefreshModels: true, aiInstructions: '', + enableAutocomplete: false, } export type GlobalSettingName = keyof GlobalSettings @@ -459,4 +461,8 @@ export const globalSettingNames = Object.keys(defaultGlobalSettings) as GlobalSe +export type ModelSelectionOptions = { + reasoningSelection?: { budget: number } +} +export type OptionsOfModelSelection = Partial<{ [providerName in ProviderName]: { [modelName: string]: ModelSelectionOptions } }>