mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
add autocomplete UI and filtering
This commit is contained in:
parent
46e8fb1ea5
commit
1be8d17899
5 changed files with 104 additions and 30 deletions
|
|
@ -257,7 +257,7 @@ export const VoidSwitch = ({
|
|||
</div>
|
||||
{label && (
|
||||
<span className={`
|
||||
ml-3 font-medium text-gray-900 dark:text-gray-100
|
||||
ml-3 text-gray-900 dark:text-gray-100
|
||||
${size === 'xs' ? 'text-xs' : 'text-sm'}
|
||||
`}>
|
||||
{label}
|
||||
|
|
|
|||
|
|
@ -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<ModelOption[]>([])
|
||||
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 <WarningBox text={emptyMessage || 'No models available'} />
|
||||
}
|
||||
|
||||
return <ModelSelectBox featureName={featureName} options={memoizedOptions} />
|
||||
|
||||
|
|
@ -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 <WarningBox onClick={openSettings} text={
|
||||
isDisabled === 'needToEnableModel' ? 'Enable a model'
|
||||
: isDisabled === 'addModel' ? 'Add a model'
|
||||
: (isDisabled === 'addProvider' || isDisabled === 'notFilledIn' || isDisabled === 'providerNotAutoDetected') ? 'Provider required'
|
||||
: 'Provider required'
|
||||
emptyMessage ? emptyMessage :
|
||||
isDisabled === 'needToEnableModel' ? 'Enable a model'
|
||||
: isDisabled === 'addModel' ? 'Add a model'
|
||||
: (isDisabled === 'addProvider' || isDisabled === 'notFilledIn' || isDisabled === 'providerNotAutoDetected') ? 'Provider required'
|
||||
: 'Provider required'
|
||||
} />
|
||||
|
||||
return <MemoizedModelDropdown featureName={featureName} />
|
||||
|
|
|
|||
|
|
@ -396,6 +396,11 @@ export const AIInstructionsBox = () => {
|
|||
}
|
||||
|
||||
export const FeaturesTab = () => {
|
||||
const voidSettingsState = useSettingsState()
|
||||
const accessor = useAccessor()
|
||||
const voidSettingsService = accessor.get('IVoidSettingsService')
|
||||
|
||||
|
||||
return <>
|
||||
<h2 className={`text-3xl mb-2`}>Models</h2>
|
||||
<ErrorBoundary>
|
||||
|
|
@ -434,17 +439,27 @@ export const FeaturesTab = () => {
|
|||
|
||||
|
||||
|
||||
<h2 className={`text-3xl mb-2 mt-12`}>Feature Options</h2>
|
||||
<h2 className={`text-3xl mt-12`}>Feature Options</h2>
|
||||
<ErrorBoundary>
|
||||
{featureNames.map(featureName =>
|
||||
(['Ctrl+L', 'Ctrl+K'] as FeatureName[]).includes(featureName) ? null :
|
||||
<div key={featureName}
|
||||
className='mb-2'
|
||||
>
|
||||
<h4 className={`text-void-fg-3`}>{displayInfoOfFeatureName(featureName)}</h4>
|
||||
<ModelDropdown featureName={featureName} />
|
||||
<div className='flex gap-x-4 items-start justify-around mt-4 mb-16'>
|
||||
<div>
|
||||
<h4 className={`text-base`}>{displayInfoOfFeatureName('Autocomplete')}</h4>
|
||||
<div className='text-sm italic text-void-fg-3 my-1'>Experimental. Only works with FIM models.</div>
|
||||
<div className='flex items-center gap-x-2'>
|
||||
<VoidSwitch size='xs' value={voidSettingsState.globalSettings.enableAutocomplete} onChange={(newVal) => voidSettingsService.setGlobalSetting('enableAutocomplete', newVal)} label={voidSettingsState.globalSettings.enableAutocomplete ? 'Enabled' : 'Disabled'} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={!voidSettingsState.globalSettings.enableAutocomplete ? 'hidden' : ''}>
|
||||
<ModelDropdown featureName={'Autocomplete'} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className={`text-base`}>{displayInfoOfFeatureName('Apply')}</h4>
|
||||
<ModelDropdown featureName={'Apply'} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ErrorBoundary>
|
||||
|
||||
</>
|
||||
|
|
@ -649,7 +664,7 @@ export const Settings = () => {
|
|||
|
||||
|
||||
{/* content */}
|
||||
<div className='w-full min-w-[550px] overflow-auto'>
|
||||
<div className='w-full min-w-[550px]'>
|
||||
|
||||
<div className={`${tab !== 'models' ? 'hidden' : ''}`}>
|
||||
<FeaturesTab />
|
||||
|
|
|
|||
|
|
@ -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 = <S extends SettingName>(
|
||||
providerName: ProviderName,
|
||||
settingName: S,
|
||||
|
|
@ -25,16 +32,17 @@ type SetSettingOfProviderFn = <S extends SettingName>(
|
|||
type SetModelSelectionOfFeatureFn = <K extends FeatureName>(
|
||||
featureName: K,
|
||||
newVal: ModelSelectionOfFeature[K],
|
||||
options?: { doNotApplyEffects?: true }
|
||||
) => Promise<void>;
|
||||
|
||||
type SetGlobalSettingFn = <T extends GlobalSettingName>(settingName: T, newVal: GlobalSettings[T]) => void;
|
||||
|
||||
export type ModelOption = { name: string, selection: ModelSelection }
|
||||
type SetOptionsOfModelSelection = (providerName: ProviderName, modelName: string, newVal: Partial<ModelSelectionOptions>) => 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<VoidSettingsState, '_modelOptions'>) => {
|
||||
|
||||
let newSettingsOfProvider = state.settingsOfProvider
|
||||
|
|
@ -125,14 +142,17 @@ const _validatedState = (state: Omit<VoidSettingsState, '_modelOptions'>) => {
|
|||
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<ModelSelectionOptions>) => {
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -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 } }>
|
||||
|
|
|
|||
Loading…
Reference in a new issue