mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
some UI for models!
This commit is contained in:
parent
c44497510b
commit
a7aab9cf86
5 changed files with 189 additions and 89 deletions
|
|
@ -75,7 +75,7 @@ export class RefreshModelService extends Disposable implements IRefreshModelServ
|
|||
this._cancelTimeout()
|
||||
|
||||
// if ollama is disabled, obivously done
|
||||
if (this.voidSettingsService.state.settingsOfProvider.ollama.enabled !== 'true') {
|
||||
if (!this.voidSettingsService.state.settingsOfProvider.ollama.enabled) {
|
||||
this._setState('done')
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { IStorageService, StorageScope, StorageTarget } from '../../storage/comm
|
|||
import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, modelInfoOfDefaultNames, ModelInfo } from './voidSettingsTypes.js';
|
||||
|
||||
|
||||
const STORAGE_KEY = 'void.voidSettings'
|
||||
const STORAGE_KEY = 'void.voidSettingsI'
|
||||
|
||||
type SetSettingOfProviderFn = <S extends SettingName>(
|
||||
providerName: ProviderName,
|
||||
|
|
@ -60,7 +60,7 @@ let _computeModelOptions = (settingsOfProvider: SettingsOfProvider) => {
|
|||
let modelOptions: ModelOption[] = []
|
||||
for (const providerName of providerNames) {
|
||||
const providerConfig = settingsOfProvider[providerName]
|
||||
if (providerConfig.enabled !== 'true') continue
|
||||
if (!providerConfig.enabled) continue // if disabled, don't display model options
|
||||
for (const { modelName, isHidden } of providerConfig.models) {
|
||||
if (isHidden) continue
|
||||
modelOptions.push({ text: `${modelName} (${providerName})`, value: { providerName, modelName } })
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
|
||||
|
||||
export type ModelInfo = {
|
||||
modelName: string,
|
||||
isDefault: boolean, // whether or not it's a default for its provider
|
||||
|
|
@ -90,16 +92,11 @@ export const anthropicMaxPossibleTokens = (modelName: string) => {
|
|||
}
|
||||
|
||||
|
||||
// export const dummyModelData = {
|
||||
// anthropic: ['claude 3.5'],
|
||||
// openAI: ['gpt 4o'],
|
||||
// ollama: ['llama 3.2', 'codestral'],
|
||||
// openRouter: ['qwen 2.5'],
|
||||
// }
|
||||
type UnionOfKeys<T> = T extends T ? keyof T : never;
|
||||
|
||||
|
||||
|
||||
export const voidProviderDefaults = {
|
||||
export const customProviderSettingsDefaults = {
|
||||
anthropic: {
|
||||
apiKey: '',
|
||||
},
|
||||
|
|
@ -124,57 +121,30 @@ export const voidProviderDefaults = {
|
|||
}
|
||||
} as const
|
||||
|
||||
export type ProviderName = keyof typeof customProviderSettingsDefaults
|
||||
export const providerNames = Object.keys(customProviderSettingsDefaults) as ProviderName[]
|
||||
|
||||
export const voidInitModelOptions = {
|
||||
anthropic: {
|
||||
models: defaultAnthropicModels,
|
||||
},
|
||||
openAI: {
|
||||
models: defaultOpenAIModels,
|
||||
},
|
||||
ollama: {
|
||||
models: [],
|
||||
},
|
||||
openRouter: {
|
||||
models: [], // any string
|
||||
},
|
||||
openAICompatible: {
|
||||
models: [],
|
||||
},
|
||||
gemini: {
|
||||
models: defaultGeminiModels,
|
||||
},
|
||||
groq: {
|
||||
models: defaultGroqModels,
|
||||
},
|
||||
|
||||
type CustomSettingName = UnionOfKeys<typeof customProviderSettingsDefaults[ProviderName]>
|
||||
|
||||
type CustomProviderSettings<providerName extends ProviderName> = {
|
||||
[k in CustomSettingName]: k extends keyof typeof customProviderSettingsDefaults[providerName] ? string : undefined
|
||||
}
|
||||
|
||||
type CommonProviderSettings = {
|
||||
enabled: boolean,
|
||||
models: ModelInfo[], // if null, user can type in any string as a model
|
||||
}
|
||||
|
||||
type SettingsForProvider<providerName extends ProviderName> = CustomProviderSettings<providerName> & CommonProviderSettings
|
||||
|
||||
export type ProviderName = keyof typeof voidProviderDefaults
|
||||
export const providerNames = Object.keys(voidProviderDefaults) as ProviderName[]
|
||||
|
||||
|
||||
|
||||
// state
|
||||
// part of state
|
||||
export type SettingsOfProvider = {
|
||||
[providerName in ProviderName]: (
|
||||
{
|
||||
[optionName in keyof typeof voidProviderDefaults[providerName]]: string
|
||||
}
|
||||
&
|
||||
{
|
||||
enabled: string, // 'true' | 'false'
|
||||
|
||||
models: ModelInfo[], // if null, user can type in any string as a model
|
||||
})
|
||||
[providerName in ProviderName]: SettingsForProvider<providerName>
|
||||
}
|
||||
|
||||
|
||||
type UnionOfKeys<T> = T extends T ? keyof T : never;
|
||||
|
||||
export type SettingName = UnionOfKeys<SettingsOfProvider[ProviderName]>
|
||||
|
||||
export type SettingName = keyof SettingsForProvider<ProviderName>
|
||||
|
||||
|
||||
|
||||
|
|
@ -219,7 +189,7 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName
|
|||
title: providerName === 'ollama' ? 'Your Ollama endpoint' :
|
||||
providerName === 'openAICompatible' ? 'baseURL' // (do not include /chat/completions)
|
||||
: '(never)',
|
||||
placeholder: providerName === 'ollama' ? voidProviderDefaults.ollama.endpoint
|
||||
placeholder: providerName === 'ollama' ? customProviderSettingsDefaults.ollama.endpoint
|
||||
: providerName === 'openAICompatible' ? 'https://my-website.com/v1'
|
||||
: '(never)',
|
||||
}
|
||||
|
|
@ -242,42 +212,81 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const defaultCustomSettings: Record<CustomSettingName, undefined> = {
|
||||
apiKey: undefined,
|
||||
endpoint: undefined,
|
||||
}
|
||||
|
||||
export const voidInitModelOptions = {
|
||||
anthropic: {
|
||||
models: defaultAnthropicModels,
|
||||
},
|
||||
openAI: {
|
||||
models: defaultOpenAIModels,
|
||||
},
|
||||
ollama: {
|
||||
models: [],
|
||||
},
|
||||
openRouter: {
|
||||
models: [], // any string
|
||||
},
|
||||
openAICompatible: {
|
||||
models: [],
|
||||
},
|
||||
gemini: {
|
||||
models: defaultGeminiModels,
|
||||
},
|
||||
groq: {
|
||||
models: defaultGroqModels,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
// used when waiting and for a type reference
|
||||
export const defaultSettingsOfProvider: SettingsOfProvider = {
|
||||
anthropic: {
|
||||
...voidProviderDefaults.anthropic,
|
||||
...defaultCustomSettings,
|
||||
...customProviderSettingsDefaults.anthropic,
|
||||
...voidInitModelOptions.anthropic,
|
||||
enabled: 'false',
|
||||
enabled: false,
|
||||
},
|
||||
openAI: {
|
||||
...voidProviderDefaults.openAI,
|
||||
...defaultCustomSettings,
|
||||
...customProviderSettingsDefaults.openAI,
|
||||
...voidInitModelOptions.openAI,
|
||||
enabled: 'false',
|
||||
enabled: false,
|
||||
},
|
||||
gemini: {
|
||||
...voidProviderDefaults.gemini,
|
||||
...defaultCustomSettings,
|
||||
...customProviderSettingsDefaults.gemini,
|
||||
...voidInitModelOptions.gemini,
|
||||
enabled: 'false',
|
||||
enabled: false,
|
||||
},
|
||||
groq: {
|
||||
...voidProviderDefaults.groq,
|
||||
...defaultCustomSettings,
|
||||
...customProviderSettingsDefaults.groq,
|
||||
...voidInitModelOptions.groq,
|
||||
enabled: 'false',
|
||||
enabled: false,
|
||||
},
|
||||
ollama: {
|
||||
...voidProviderDefaults.ollama,
|
||||
...defaultCustomSettings,
|
||||
...customProviderSettingsDefaults.ollama,
|
||||
...voidInitModelOptions.ollama,
|
||||
enabled: 'false',
|
||||
enabled: false,
|
||||
},
|
||||
openRouter: {
|
||||
...voidProviderDefaults.openRouter,
|
||||
...defaultCustomSettings,
|
||||
...customProviderSettingsDefaults.openRouter,
|
||||
...voidInitModelOptions.openRouter,
|
||||
enabled: 'false',
|
||||
enabled: false,
|
||||
},
|
||||
openAICompatible: {
|
||||
...voidProviderDefaults.openAICompatible,
|
||||
...defaultCustomSettings,
|
||||
...customProviderSettingsDefaults.openAICompatible,
|
||||
...voidInitModelOptions.openAICompatible,
|
||||
enabled: 'false',
|
||||
enabled: false,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ const DummySelectBox = () => {
|
|||
return <VoidSelectBox
|
||||
options={[{ text: 'Please add a model!', value: null }]}
|
||||
onChangeSelection={() => { }}
|
||||
onCreateInstance={() => { }}
|
||||
/>
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import React, { useCallback } from 'react'
|
||||
import React, { useCallback, useRef, useState } from 'react'
|
||||
import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'
|
||||
import { ProviderName, SettingName, displayInfoOfSettingName, titleOfProviderName, providerNames, ModelInfo } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
|
||||
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
|
||||
import { VoidInputBox } from '../util/inputs.js'
|
||||
import { VoidInputBox, VoidSelectBox } from '../util/inputs.js'
|
||||
import { useIsDark, useRefreshModelState, useService, useSettingsState } from '../util/services.js'
|
||||
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ const RefreshableModels = () => {
|
|||
const refreshModelState = useRefreshModelState()
|
||||
const refreshModelService = useService('refreshModelService')
|
||||
|
||||
if (settingsState.settingsOfProvider.ollama.enabled !== 'true')
|
||||
if (!settingsState.settingsOfProvider.ollama.enabled)
|
||||
return null
|
||||
|
||||
return <>
|
||||
|
|
@ -26,8 +26,82 @@ const RefreshableModels = () => {
|
|||
|
||||
|
||||
|
||||
const AddModelMenu = ({ onSubmit }: { onSubmit: () => void }) => {
|
||||
const settingsStateService = useService('settingsStateService')
|
||||
const settingsState = useSettingsState()
|
||||
|
||||
export const ModelMenu = () => {
|
||||
const providerNameRef = useRef<ProviderName | null>(null)
|
||||
const modelNameRef = useRef<string | null>(null)
|
||||
|
||||
const [errorString, setErrorString] = useState('')
|
||||
|
||||
const providerOptions = providerNames.map(providerName => ({ text: titleOfProviderName(providerName), value: providerName }))
|
||||
|
||||
return <div className='flex justify-center items-center gap-20'>
|
||||
{/* model */}
|
||||
<div className='max-w-xl w-full'>
|
||||
<VoidInputBox
|
||||
placeholder='Model Name'
|
||||
onChangeText={(modelName) => { modelNameRef.current = modelName }}
|
||||
multiline={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* provider */}
|
||||
<div className='w-fit'>
|
||||
<VoidSelectBox
|
||||
onCreateInstance={(instance) => { providerNameRef.current = providerOptions[0].value }} // initialize state
|
||||
onChangeSelection={(providerName: ProviderName) => { console.log('selecting provider', providerName); providerNameRef.current = providerName }}
|
||||
options={providerOptions}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* button */}
|
||||
<div className='w-80'>
|
||||
<button onClick={() => {
|
||||
const providerName = providerNameRef.current
|
||||
const modelName = modelNameRef.current
|
||||
|
||||
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}.`)
|
||||
return
|
||||
}
|
||||
|
||||
settingsStateService.addModel(providerName, modelName)
|
||||
onSubmit()
|
||||
|
||||
}}>Add model</button>
|
||||
</div>
|
||||
|
||||
{!errorString ? null : <>
|
||||
{errorString}
|
||||
</>}
|
||||
</div>
|
||||
|
||||
}
|
||||
|
||||
const AddModelButton = () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
return <>
|
||||
{open ?
|
||||
<AddModelMenu onSubmit={() => { setOpen(false) }} />
|
||||
: <button onClick={() => setOpen(true)}>Add Model</button>
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
export const ModelDump = () => {
|
||||
|
||||
const settingsStateService = useService('settingsStateService')
|
||||
const settingsState = useSettingsState()
|
||||
|
|
@ -36,21 +110,30 @@ export const ModelMenu = () => {
|
|||
const modelDump: (ModelInfo & { providerName: ProviderName })[] = []
|
||||
for (let providerName of providerNames) {
|
||||
const providerSettings = settingsState.settingsOfProvider[providerName]
|
||||
if (providerSettings.enabled !== 'true') continue
|
||||
if (!providerSettings.enabled) continue
|
||||
modelDump.push(...providerSettings.models.map(model => ({ ...model, providerName })))
|
||||
}
|
||||
|
||||
return <>
|
||||
return <div className='max-h-80 overflow-y-auto'>
|
||||
{modelDump.map(m => {
|
||||
const { isHidden, isDefault, modelName, providerName } = m
|
||||
|
||||
return <div key={`${modelName}${providerName}`} className='flex items-center justify-between gap-4 hover:bg-black/10 dark:hover:bg-white/10'>
|
||||
<span>{modelName} {isDefault ? '' : '(custom)'}</span>
|
||||
<span>{providerName}</span>
|
||||
<span onClick={() => { settingsStateService.toggleModelHidden(providerName, modelName) }}>{isHidden ? 'hidden' : '✅'}</span>
|
||||
return <div key={`${modelName}${providerName}`} className='flex items-center justify-between gap-4 hover:bg-black/10 dark:hover:bg-gray-200/10 py-1 px-3 rounded-sm overflow-hidden cursor-default'>
|
||||
{/* left part is width:full */}
|
||||
<div className='w-full flex items-center gap-4'>
|
||||
<span>{`${modelName} (${providerName})`}</span>
|
||||
</div>
|
||||
{/* right part is anything that fits */}
|
||||
<div className='w-fit flex items-center gap-4'>
|
||||
<span className='opacity-50 whitespace-nowrap'>{isDefault ? '' : '(custom model)'}</span>
|
||||
<div onClick={() => { settingsStateService.toggleModelHidden(providerName, modelName) }}>{isHidden ? '❌' : '✅'}</div>
|
||||
<div className='w-10'>
|
||||
{isDefault ? null : <button onClick={() => { settingsStateService.deleteModel(providerName, modelName) }}>x</button>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
})}
|
||||
</>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -78,7 +161,7 @@ const ProviderSetting = ({ providerName, settingName }: { providerName: Provider
|
|||
onCreateInstance={useCallback((instance: InputBox) => {
|
||||
const syncInstance = () => {
|
||||
const settingsAtProvider = voidSettingsService.state.settingsOfProvider[providerName];
|
||||
const stateVal = settingsAtProvider[settingName as keyof typeof settingsAtProvider]
|
||||
const stateVal = settingsAtProvider[settingName as SettingName]
|
||||
// console.log('SYNCING TO', providerName, settingName, stateVal)
|
||||
weChangedTextRef = true
|
||||
instance.value = stateVal as string
|
||||
|
|
@ -96,10 +179,16 @@ const ProviderSetting = ({ providerName, settingName }: { providerName: Provider
|
|||
|
||||
const SettingsForProvider = ({ providerName }: { providerName: ProviderName }) => {
|
||||
const voidSettingsState = useSettingsState()
|
||||
const { models, ...others } = voidSettingsState.settingsOfProvider[providerName]
|
||||
const voidSettingsService = useService('settingsStateService')
|
||||
|
||||
const { models, enabled, ...others } = voidSettingsState.settingsOfProvider[providerName]
|
||||
|
||||
return <>
|
||||
<h1 className='text-xl'>{titleOfProviderName(providerName)}</h1>
|
||||
|
||||
<div className='flex items-center gap-4'>
|
||||
<h3 className='text-xl'>{titleOfProviderName(providerName)}</h3>
|
||||
<span onClick={() => { voidSettingsService.setSettingOfProvider(providerName, 'enabled', !enabled) }}>{enabled ? '✅' : '❌'}</span>
|
||||
</div>
|
||||
{/* settings besides models (e.g. api key) */}
|
||||
{Object.keys(others).map((sName, i) => {
|
||||
const settingName = sName as keyof typeof others
|
||||
|
|
@ -123,19 +212,22 @@ export const VoidProviderSettings = () => {
|
|||
|
||||
export const Settings = () => {
|
||||
const isDark = useIsDark()
|
||||
return <div className={`@@void-scope ${isDark ? 'dark' : ''} px-2 lg:px-10`}>
|
||||
<div className='w-full h-full'>
|
||||
return <div className={`@@void-scope ${isDark ? 'dark' : ''}`}>
|
||||
<div className='w-full h-full px-10 py-10'>
|
||||
|
||||
<div className='max-w-3xl mx-auto'>
|
||||
<h2 className='text-3xl'>Models</h2>
|
||||
<ErrorBoundary>
|
||||
<ModelMenu />
|
||||
<ModelDump />
|
||||
<AddModelButton />
|
||||
<RefreshableModels />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
<ErrorBoundary>
|
||||
<VoidProviderSettings />
|
||||
</ErrorBoundary>
|
||||
<h2 className='text-3xl'>Providers</h2>
|
||||
<ErrorBoundary>
|
||||
<VoidProviderSettings />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue