diff --git a/src/vs/platform/void/common/refreshModelService.ts b/src/vs/platform/void/common/refreshModelService.ts index f1f0c575..b9b44e83 100644 --- a/src/vs/platform/void/common/refreshModelService.ts +++ b/src/vs/platform/void/common/refreshModelService.ts @@ -85,7 +85,7 @@ export class RefreshModelService extends Disposable implements IRefreshModelServ this.llmMessageService.ollamaList({ onSuccess: ({ models }) => { - this.voidSettingsService.setSettingOfProvider('ollama', 'models', models.map(model => model.name)) + this.voidSettingsService.setDefaultModels('ollama', models.map(model => model.name)) this._setState('done') }, onError: ({ error }) => { diff --git a/src/vs/platform/void/common/voidSettingsService.ts b/src/vs/platform/void/common/voidSettingsService.ts index dbaf0837..a11e3f60 100644 --- a/src/vs/platform/void/common/voidSettingsService.ts +++ b/src/vs/platform/void/common/voidSettingsService.ts @@ -10,10 +10,10 @@ import { IEncryptionService } from '../../encryption/common/encryptionService.js import { registerSingleton, InstantiationType } from '../../instantiation/common/extensions.js'; import { createDecorator } from '../../instantiation/common/instantiation.js'; import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js'; -import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames } from './voidSettingsTypes.js'; +import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, modelInfoOfDefaultNames, ModelInfo } from './voidSettingsTypes.js'; -const STORAGE_KEY = 'void.voidConfigStateIV' +const STORAGE_KEY = 'void.voidSettings' type SetSettingOfProviderFn = ( providerName: ProviderName, @@ -37,7 +37,7 @@ export type VoidSettingsState = { readonly settingsOfProvider: SettingsOfProvider; // optionsOfProvider readonly modelSelectionOfFeature: ModelSelectionOfFeature; // stateOfFeature - readonly _modelsList: ModelOption[] // computed based on the two above items + readonly _modelOptions: ModelOption[] // computed based on the two above items } @@ -48,19 +48,25 @@ export interface IVoidSettingsService { onDidChangeState: Event; setSettingOfProvider: SetSettingOfProviderFn; setModelSelectionOfFeature: SetModelSelectionOfFeature; + + setDefaultModels(providerName: ProviderName, modelNames: string[]): void; + toggleModelHidden(providerName: ProviderName, modelName: string): void; + addModel(providerName: ProviderName, modelName: string): void; + deleteModel(providerName: ProviderName, modelName: string): boolean; } -let _computeModelsList = (settingsOfProvider: SettingsOfProvider) => { - let modelsList: ModelOption[] = [] +let _computeModelOptions = (settingsOfProvider: SettingsOfProvider) => { + let modelOptions: ModelOption[] = [] for (const providerName of providerNames) { const providerConfig = settingsOfProvider[providerName] if (providerConfig.enabled !== 'true') continue - providerConfig.models?.forEach(modelName => { - modelsList.push({ text: `${modelName} (${providerName})`, value: { providerName, modelName } }) - }) + for (const { modelName, isHidden } of providerConfig.models) { + if (isHidden) continue + modelOptions.push({ text: `${modelName} (${providerName})`, value: { providerName, modelName } }) + } } - return modelsList + return modelOptions } @@ -68,7 +74,7 @@ const defaultState = () => { const d: VoidSettingsState = { settingsOfProvider: deepClone(defaultSettingsOfProvider), modelSelectionOfFeature: { 'Ctrl+L': null, 'Ctrl+K': null, 'Autocomplete': null }, - _modelsList: _computeModelsList(defaultSettingsOfProvider), + _modelOptions: _computeModelOptions(defaultSettingsOfProvider), // computed } return d } @@ -132,12 +138,12 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { // if changed models or enabled a provider, recompute models list const modelsListChanged = settingName === 'models' || settingName === 'enabled' - const newModelsList = modelsListChanged ? _computeModelsList(newSettingsOfProvider) : this.state._modelsList + const newModelsList = modelsListChanged ? _computeModelOptions(newSettingsOfProvider) : this.state._modelOptions const newState: VoidSettingsState = { modelSelectionOfFeature: newModelSelectionOfFeature, settingsOfProvider: newSettingsOfProvider, - _modelsList: newModelsList, + _modelOptions: newModelsList, } // this must go above this.setanythingelse() @@ -177,7 +183,6 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { if (options?.doNotApplyEffects) return - console.log('NEW STATE II', JSON.stringify(newState, null, 2)) await this._storeState() this._onDidChangeState.fire() @@ -185,6 +190,48 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { + setDefaultModels(providerName: ProviderName, newDefaultModelNames: string[]) { + const { models } = this.state.settingsOfProvider[providerName] + const newDefaultModels = modelInfoOfDefaultNames(newDefaultModelNames) + const newModels = [ + ...newDefaultModels, + ...models.filter(m => !m.isDefault), // keep any non-default models + ] + this.setSettingOfProvider(providerName, 'models', newModels) + } + toggleModelHidden(providerName: ProviderName, modelName: string) { + const { models } = this.state.settingsOfProvider[providerName] + const modelIdx = models.findIndex(m => m.modelName === modelName) + if (modelIdx === -1) return + const newModels: ModelInfo[] = [ + ...models.slice(0, modelIdx), + { ...models[modelIdx], isHidden: !models[modelIdx].isHidden }, + ...models.slice(modelIdx + 1, Infinity) + ] + this.setSettingOfProvider(providerName, 'models', newModels) + } + addModel(providerName: ProviderName, modelName: string) { + const { models } = this.state.settingsOfProvider[providerName] + const existingIdx = models.findIndex(m => m.modelName === modelName) + if (existingIdx !== -1) return // if exists, do nothing + const newModels = [ + ...models, + { modelName, isDefault: false, isHidden: false } + ] + this.setSettingOfProvider(providerName, 'models', newModels) + } + deleteModel(providerName: ProviderName, modelName: string): boolean { + const { models } = this.state.settingsOfProvider[providerName] + const delIdx = models.findIndex(m => m.modelName === modelName) + if (delIdx === -1) return false + const newModels = [ + ...models.slice(0, delIdx), // delete the idx + ...models.slice(delIdx + 1, Infinity) + ] + this.setSettingOfProvider(providerName, 'models', newModels) + return true + } + } diff --git a/src/vs/platform/void/common/voidSettingsTypes.ts b/src/vs/platform/void/common/voidSettingsTypes.ts index 810e9651..7c8b99c8 100644 --- a/src/vs/platform/void/common/voidSettingsTypes.ts +++ b/src/vs/platform/void/common/voidSettingsTypes.ts @@ -5,17 +5,30 @@ *--------------------------------------------------------------------------------------------*/ +export type ModelInfo = { + modelName: string, + isDefault: boolean, // whether or not it's a default for its provider + isHidden: boolean, // whether or not the user is hiding it +} + + +export const modelInfoOfDefaultNames = (modelNames: string[]): ModelInfo[] => { + const isHidden = modelNames.length >= 10 // hide all models if there are a ton of them, and make user enable them individually + return modelNames.map((modelName, i) => ({ modelName, isDefault: true, isHidden })) +} + // https://docs.anthropic.com/en/docs/about-claude/models -export const defaultAnthropicModels = [ +export const defaultAnthropicModels = modelInfoOfDefaultNames([ 'claude-3-5-sonnet-20241022', 'claude-3-5-haiku-20241022', 'claude-3-opus-20240229', 'claude-3-sonnet-20240229', // 'claude-3-haiku-20240307', -] +]) -export const defaultOpenAIModels = [ +// https://platform.openai.com/docs/models/gp +export const defaultOpenAIModels = modelInfoOfDefaultNames([ 'o1-preview', 'o1-mini', 'gpt-4o', @@ -33,23 +46,24 @@ export const defaultOpenAIModels = [ // 'gpt-3.5-turbo-0125', // 'gpt-3.5-turbo', // 'gpt-3.5-turbo-1106', -] +]) -export const defaultGroqModels = [ +// https://console.groq.com/docs/models +export const defaultGroqModels = modelInfoOfDefaultNames([ "mixtral-8x7b-32768", "llama2-70b-4096", "gemma-7b-it" -] +]) -export const defaultGeminiModels = [ +export const defaultGeminiModels = modelInfoOfDefaultNames([ 'gemini-1.5-flash', 'gemini-1.5-pro', 'gemini-1.5-flash-8b', 'gemini-1.0-pro' -] +]) @@ -152,7 +166,7 @@ export type SettingsOfProvider = { { enabled: string, // 'true' | 'false' - models: string[], // if null, user can type in any string as a model + models: ModelInfo[], // if null, user can type in any string as a model }) } @@ -245,6 +259,16 @@ export const defaultSettingsOfProvider: SettingsOfProvider = { ...voidInitModelOptions.openAI, enabled: 'false', }, + gemini: { + ...voidProviderDefaults.gemini, + ...voidInitModelOptions.gemini, + enabled: 'false', + }, + groq: { + ...voidProviderDefaults.groq, + ...voidInitModelOptions.groq, + enabled: 'false', + }, ollama: { ...voidProviderDefaults.ollama, ...voidInitModelOptions.ollama, @@ -260,16 +284,6 @@ export const defaultSettingsOfProvider: SettingsOfProvider = { ...voidInitModelOptions.openAICompatible, enabled: 'false', }, - gemini: { - ...voidProviderDefaults.gemini, - ...voidInitModelOptions.gemini, - enabled: 'false', - }, - groq: { - ...voidProviderDefaults.groq, - ...voidInitModelOptions.groq, - enabled: 'false', - } } diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx index 399feff8..4d177c88 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx @@ -15,8 +15,6 @@ import { useSidebarState } from '../util/services.js'; import '../styles.css' import { SidebarThreadSelector } from './SidebarThreadSelector.js'; import { SidebarChat } from './SidebarChat.js'; -import { ModelSelectionSettings } from '../void-settings-tsx/ModelSelectionSettings.js'; -import { VoidProviderSettings } from '../void-settings-tsx/VoidProviderSettings.js'; import ErrorBoundary from './ErrorBoundary.js'; export const Sidebar = () => { 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 abb47d23..9180640a 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 @@ -22,7 +22,7 @@ import { OnError, ServiceSendLLMMessageParams } from '../../../../../../../platf import { getCmdKey } from '../../../helpers/getCmdKey.js' import { HistoryInputBox, InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'; import { VoidInputBox } from './inputs.js'; -import { ModelSelectionOfFeature } from '../void-settings-tsx/ModelSelectionSettings.js'; +import { ModelDropdown } from '../void-settings-tsx/ModelDropdown.js'; const IconX = ({ size, className = '' }: { size: number, className?: string }) => { @@ -487,7 +487,7 @@ export const SidebarChat = () => {
{/* submit options */}
- +
{/* submit / stop button */} diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelSelectionSettings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx similarity index 67% rename from src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelSelectionSettings.tsx rename to src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx index 6c8c53c1..71c84014 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelSelectionSettings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx @@ -10,14 +10,14 @@ import { VoidSelectBox } from '../sidebar-tsx/inputs.js' import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js' -const SelectBoxOfFeature = ({ featureName }: { featureName: FeatureName }) => { +const ModelSelectBox = ({ featureName }: { featureName: FeatureName }) => { const voidSettingsService = useService('settingsStateService') const settingsState = useSettingsState() let weChangedText = false return { if (weChangedText) return voidSettingsService.setModelSelectionOfFeature(featureName, newVal) @@ -25,7 +25,7 @@ const SelectBoxOfFeature = ({ featureName }: { featureName: 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._modelsList // as a ref + 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 @@ -39,34 +39,17 @@ const SelectBoxOfFeature = ({ featureName }: { featureName: FeatureName }) => { /> } +const DummySelectBox = () => { + return { }} + onCreateInstance={() => { }} + /> +} -export const ModelSelectionOfFeature = ({ featureName }: { featureName: FeatureName }) => { +export const ModelDropdown = ({ featureName }: { featureName: FeatureName }) => { const settingsState = useSettingsState() return <> - {settingsState._modelsList.length === 0 ? 'Please add a provider!' : } + {settingsState._modelOptions.length === 0 ? : } } - -const RefreshModels = () => { - const refreshModelState = useRefreshModelState() - const refreshModelService = useService('refreshModelService') - - return <> - - {refreshModelState === 'loading' ? 'loading...' : '✅'} - -} - -export const ModelSelectionSettings = () => { - return <> - {featureNames.map(featureName => )} - - - -} - diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelSettings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelSettings.tsx new file mode 100644 index 00000000..efcce3c0 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelSettings.tsx @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Glass Devtools, Inc. All rights reserved. + * Void Editor additions licensed under the AGPL 3.0 License. + *--------------------------------------------------------------------------------------------*/ + +import { ModelInfo, ProviderName, providerNames } from '../../../../../../../platform/void/common/voidSettingsTypes.js' +import { useRefreshModelState, useService, useSettingsState } from '../util/services.js' + + + + + + +const Refreshables = () => { + const settingsState = useSettingsState() + + const refreshModelState = useRefreshModelState() + const refreshModelService = useService('refreshModelService') + + if (settingsState.settingsOfProvider.ollama.enabled !== 'true') + return null + + return <> + + {refreshModelState === 'loading' ? 'loading...' : 'good!'} + +} + + + + +export const ModelMenu = () => { + + const settingsStateService = useService('settingsStateService') + const settingsState = useSettingsState() + + // a dump of all the enabled providers' models + const models: (ModelInfo & { providerName: ProviderName })[] = [] + for (let providerName of providerNames) { + const providerSettings = settingsState.settingsOfProvider[providerName] + if (providerSettings.enabled !== 'true') continue + models.push(...providerSettings.models.map(model => ({ ...model, providerName }))) + } + + return <> + {models.map(m => { + const { isHidden, isDefault, modelName, providerName } = m + + return
+ {modelName} {isDefault ? '' : '(custom)'} + {providerName} + { settingsStateService.toggleModelHidden(providerName, modelName) }}>{isHidden ? 'hidden' : '✅'} +
+ })} + + + +} diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/VoidProviderSettings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ProviderSettings.tsx similarity index 100% rename from src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/VoidProviderSettings.tsx rename to src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ProviderSettings.tsx 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 new file mode 100644 index 00000000..4a2ac4e0 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx @@ -0,0 +1,17 @@ +import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js' +import { ModelMenu } from './ModelSettings.js' +import { VoidProviderSettings } from './ProviderSettings.js' + + +export const Settings = () => { + return
+ + + + + + + + +
+} diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/VoidSettings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/VoidSettings.tsx deleted file mode 100644 index ecf60499..00000000 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/VoidSettings.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js' -import { VoidProviderSettings } from './VoidProviderSettings.js' - - -export const VoidSettings = () => { - return <> - - - - -} - - diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/index.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/index.tsx index ee89ffab..ff596b24 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/index.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/index.tsx @@ -1,6 +1,6 @@ import { mountFnGenerator } from '../util/mountFnGenerator.js' -import { VoidSettings } from './VoidSettings.js' +import { Settings } from './Settings.js' -export const mountVoidSettings = mountFnGenerator(VoidSettings) +export const mountVoidSettings = mountFnGenerator(Settings) diff --git a/src/vs/workbench/contrib/void/browser/voidSettingsPane.ts b/src/vs/workbench/contrib/void/browser/voidSettingsPane.ts index f09b5e63..2d638bf5 100644 --- a/src/vs/workbench/contrib/void/browser/voidSettingsPane.ts +++ b/src/vs/workbench/contrib/void/browser/voidSettingsPane.ts @@ -26,11 +26,12 @@ import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextke import { mountVoidSettings } from './react/out/void-settings-tsx/index.js' import { getReactServices } from './helpers/reactServicesHelper.js'; import { Codicon } from '../../../../base/common/codicons.js'; +import { IDisposable } from '../../../../base/common/lifecycle.js'; // refer to preferences.contribution.ts keybindings editor -class VoidEditorInput extends EditorInput { +class VoidSettingsInput extends EditorInput { static readonly ID: string = 'workbench.input.void.settings'; @@ -44,7 +45,7 @@ class VoidEditorInput extends EditorInput { } override get typeId(): string { - return VoidEditorInput.ID; + return VoidSettingsInput.ID; } override getName(): string { @@ -54,7 +55,7 @@ class VoidEditorInput extends EditorInput { } -class MyCustomPane extends EditorPane { +class VoidSettingsPane extends EditorPane { static readonly ID = 'workbench.test.myCustomPane'; constructor( @@ -64,14 +65,18 @@ class MyCustomPane extends EditorPane { @IStorageService storageService: IStorageService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { - super(MyCustomPane.ID, group, telemetryService, themeService, storageService); + super(VoidSettingsPane.ID, group, telemetryService, themeService, storageService); } - protected createEditor(container: HTMLElement): void { + protected createEditor(parent: HTMLElement): void { + parent.style.overflow = 'auto' + parent.style.userSelect = 'text' + // gets set immediately this.instantiationService.invokeFunction(accessor => { const services = getReactServices(accessor) - mountVoidSettings(container, services); + const disposables: IDisposable[] | undefined = mountVoidSettings(parent, services); + disposables?.forEach(d => this._register(d)) }) } @@ -87,8 +92,8 @@ class MyCustomPane extends EditorPane { Registry.as(EditorExtensions.EditorPane).registerEditorPane( - EditorPaneDescriptor.create(MyCustomPane, MyCustomPane.ID, nls.localize('MyCustomPane', "CustomPane")), - [new SyncDescriptor(VoidEditorInput)] + EditorPaneDescriptor.create(VoidSettingsPane, VoidSettingsPane.ID, nls.localize('VoidSettingsPane', "Void Settings Pane")), + [new SyncDescriptor(VoidSettingsInput)] ); @@ -117,7 +122,7 @@ registerAction2(class extends Action2 { async run(accessor: ServicesAccessor): Promise { const editorService = accessor.get(IEditorService); const instantiationService = accessor.get(IInstantiationService); - const input = instantiationService.createInstance(VoidEditorInput); + const input = instantiationService.createInstance(VoidSettingsInput); await editorService.openEditor(input); } })