mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
settings page styles
This commit is contained in:
parent
3669401819
commit
f457f006fb
8 changed files with 129 additions and 103 deletions
|
|
@ -9,25 +9,22 @@ import { IVoidSettingsService } from './voidSettingsService.js';
|
|||
import { ILLMMessageService } from './llmMessageService.js';
|
||||
import { Emitter, Event } from '../../../base/common/event.js';
|
||||
import { Disposable, IDisposable } from '../../../base/common/lifecycle.js';
|
||||
import { ProviderName, SettingsOfProvider } from './voidSettingsTypes.js';
|
||||
import { RefreshableProviderName, refreshableProviderNames, SettingsOfProvider } from './voidSettingsTypes.js';
|
||||
import { OllamaModelResponse, OpenaiCompatibleModelResponse } from './llmMessageTypes.js';
|
||||
|
||||
|
||||
export const refreshableProviderNames = ['ollama', 'openAICompatible'] satisfies ProviderName[]
|
||||
|
||||
export type RefreshableProviderName = typeof refreshableProviderNames[number]
|
||||
|
||||
|
||||
type RefreshableState = {
|
||||
type RefreshableState = ({
|
||||
state: 'init',
|
||||
timeoutId: null,
|
||||
} | {
|
||||
state: 'refreshing',
|
||||
timeoutId: NodeJS.Timeout | null,
|
||||
timeoutId: NodeJS.Timeout | null, // the timeoutId of the most recent call to refreshModels
|
||||
} | {
|
||||
state: 'success',
|
||||
timeoutId: null,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
export type RefreshModelStateOfProvider = Record<RefreshableProviderName, RefreshableState>
|
||||
|
|
@ -38,7 +35,8 @@ const refreshBasedOn: { [k in RefreshableProviderName]: (keyof SettingsOfProvide
|
|||
ollama: ['enabled', 'endpoint'],
|
||||
openAICompatible: ['enabled', 'endpoint', 'apiKey'],
|
||||
}
|
||||
const REFRESH_INTERVAL = 5000
|
||||
const REFRESH_INTERVAL = 5_000
|
||||
const COOLDOWN_TIMEOUT = 1_000
|
||||
|
||||
// element-wise equals
|
||||
function eq<T>(a: T[], b: T[]): boolean {
|
||||
|
|
@ -64,6 +62,8 @@ export class RefreshModelService extends Disposable implements IRefreshModelServ
|
|||
private readonly _onDidChangeState = new Emitter<RefreshableProviderName>();
|
||||
readonly onDidChangeState: Event<RefreshableProviderName> = this._onDidChangeState.event; // this is primarily for use in react, so react can listen + update on state changes
|
||||
|
||||
private readonly _onDidAutoEnable = new Emitter<RefreshableProviderName>();
|
||||
|
||||
constructor(
|
||||
@IVoidSettingsService private readonly voidSettingsService: IVoidSettingsService,
|
||||
@ILLMMessageService private readonly llmMessageService: ILLMMessageService,
|
||||
|
|
@ -73,8 +73,7 @@ export class RefreshModelService extends Disposable implements IRefreshModelServ
|
|||
|
||||
const disposables: Set<IDisposable> = new Set()
|
||||
|
||||
|
||||
const startRefreshing = () => {
|
||||
const initializePollingAndOnChange = () => {
|
||||
this._clearAllTimeouts()
|
||||
disposables.forEach(d => d.dispose())
|
||||
disposables.clear()
|
||||
|
|
@ -83,12 +82,8 @@ export class RefreshModelService extends Disposable implements IRefreshModelServ
|
|||
|
||||
for (const providerName of refreshableProviderNames) {
|
||||
|
||||
const refresh = () => {
|
||||
// const { enabled } = this.voidSettingsService.state.settingsOfProvider[providerName]
|
||||
this.refreshModels(providerName, { enableProviderOnSuccess: true }) // enable the provider on success
|
||||
}
|
||||
|
||||
refresh()
|
||||
const { enabled } = this.voidSettingsService.state.settingsOfProvider[providerName]
|
||||
this.refreshModels(providerName, !enabled)
|
||||
|
||||
// every time providerName.enabled changes, refresh models too, like a useEffect
|
||||
let relevantVals = () => refreshBasedOn[providerName].map(settingName => this.voidSettingsService.state.settingsOfProvider[providerName][settingName])
|
||||
|
|
@ -97,8 +92,18 @@ export class RefreshModelService extends Disposable implements IRefreshModelServ
|
|||
this.voidSettingsService.onDidChangeState(() => { // we might want to debounce this
|
||||
const newVals = relevantVals()
|
||||
if (!eq(prevVals, newVals)) {
|
||||
refresh()
|
||||
prevVals = newVals
|
||||
|
||||
const { enabled } = this.voidSettingsService.state.settingsOfProvider[providerName]
|
||||
if (enabled) {
|
||||
// if user just clicked enable, refresh
|
||||
this.refreshModels(providerName, !enabled)
|
||||
}
|
||||
else {
|
||||
// else if user just clicked disable, give 5 seconds cooldown before re-enabling (or at least re-fetching)
|
||||
const timeoutId = setTimeout(() => this.refreshModels(providerName, !enabled), COOLDOWN_TIMEOUT)
|
||||
this._setTimeoutId(providerName, timeoutId)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
|
@ -107,9 +112,9 @@ export class RefreshModelService extends Disposable implements IRefreshModelServ
|
|||
|
||||
// on mount (when get init settings state), and if a relevant feature flag changes (detected natively right now by refreshing if any flag changes), start refreshing models
|
||||
voidSettingsService.waitForInitState.then(() => {
|
||||
startRefreshing()
|
||||
initializePollingAndOnChange()
|
||||
this._register(
|
||||
voidSettingsService.onDidChangeState((type) => { if (type === 'featureFlagSettings') startRefreshing() })
|
||||
voidSettingsService.onDidChangeState((type) => { if (type === 'featureFlagSettings') initializePollingAndOnChange() })
|
||||
)
|
||||
})
|
||||
|
||||
|
|
@ -122,7 +127,7 @@ export class RefreshModelService extends Disposable implements IRefreshModelServ
|
|||
|
||||
|
||||
// start listening for models (and don't stop until success)
|
||||
async refreshModels(providerName: RefreshableProviderName, options?: { enableProviderOnSuccess?: boolean }) {
|
||||
async refreshModels(providerName: RefreshableProviderName, enableProviderOnSuccess?: boolean) {
|
||||
this._clearProviderTimeout(providerName)
|
||||
|
||||
// start loading models
|
||||
|
|
@ -140,15 +145,17 @@ export class RefreshModelService extends Disposable implements IRefreshModelServ
|
|||
else throw new Error('refreshMode fn: unknown provider', providerName)
|
||||
}))
|
||||
|
||||
if (options?.enableProviderOnSuccess)
|
||||
if (enableProviderOnSuccess) {
|
||||
this.voidSettingsService.setSettingOfProvider(providerName, 'enabled', true)
|
||||
this._onDidAutoEnable.fire(providerName)
|
||||
}
|
||||
|
||||
this._setRefreshState(providerName, 'success')
|
||||
},
|
||||
onError: ({ error }) => {
|
||||
// poll
|
||||
console.log('retrying list models:', providerName, error)
|
||||
const timeoutId = setTimeout(() => this.refreshModels(providerName, options), REFRESH_INTERVAL)
|
||||
const timeoutId = setTimeout(() => this.refreshModels(providerName, enableProviderOnSuccess), REFRESH_INTERVAL)
|
||||
this._setTimeoutId(providerName, timeoutId)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ type UnionOfKeys<T> = T extends T ? keyof T : never;
|
|||
|
||||
|
||||
|
||||
export const customProviderSettings = {
|
||||
export const defaultProviderSettings = {
|
||||
anthropic: {
|
||||
apiKey: '',
|
||||
},
|
||||
|
|
@ -122,15 +122,19 @@ export const customProviderSettings = {
|
|||
} as const
|
||||
|
||||
|
||||
export type ProviderName = keyof typeof customProviderSettings
|
||||
export const providerNames = Object.keys(customProviderSettings) as ProviderName[]
|
||||
export type ProviderName = keyof typeof defaultProviderSettings
|
||||
export const providerNames = Object.keys(defaultProviderSettings) as ProviderName[]
|
||||
|
||||
|
||||
|
||||
type CustomSettingName = UnionOfKeys<typeof customProviderSettings[ProviderName]>
|
||||
type CustomSettingName = UnionOfKeys<typeof defaultProviderSettings[ProviderName]>
|
||||
type CustomProviderSettings<providerName extends ProviderName> = {
|
||||
[k in CustomSettingName]: k extends keyof typeof customProviderSettings[providerName] ? string : undefined
|
||||
[k in CustomSettingName]: k extends keyof typeof defaultProviderSettings[providerName] ? string : undefined
|
||||
}
|
||||
export const customSettingNamesOfProvider = (providerName: ProviderName) => {
|
||||
return Object.keys(defaultProviderSettings[providerName]) as CustomSettingName[]
|
||||
}
|
||||
|
||||
|
||||
type CommonProviderSettings = {
|
||||
enabled: boolean | undefined, // undefined initially
|
||||
|
|
@ -150,11 +154,6 @@ export type SettingName = keyof SettingsForProvider<ProviderName>
|
|||
|
||||
|
||||
|
||||
export const customSettingNamesOfProvider = (providerName: ProviderName) => {
|
||||
return Object.keys(customProviderSettings[providerName]) as CustomSettingName[]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export const titleOfProviderName = (providerName: ProviderName) => {
|
||||
|
|
@ -208,11 +207,11 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName
|
|||
}
|
||||
else if (settingName === 'endpoint') {
|
||||
return {
|
||||
title: providerName === 'ollama' ? 'Your Ollama endpoint' :
|
||||
title: providerName === 'ollama' ? 'Endpoint' :
|
||||
providerName === 'openAICompatible' ? 'baseURL' // (do not include /chat/completions)
|
||||
: '(never)',
|
||||
|
||||
placeholder: providerName === 'ollama' ? customProviderSettings.ollama.endpoint
|
||||
placeholder: providerName === 'ollama' ? defaultProviderSettings.ollama.endpoint
|
||||
: providerName === 'openAICompatible' ? 'https://my-website.com/v1'
|
||||
: '(never)',
|
||||
|
||||
|
|
@ -278,42 +277,42 @@ export const defaultSettingsOfProvider: SettingsOfProvider = {
|
|||
anthropic: {
|
||||
enabled: undefined,
|
||||
...defaultCustomSettings,
|
||||
...customProviderSettings.anthropic,
|
||||
...defaultProviderSettings.anthropic,
|
||||
...voidInitModelOptions.anthropic,
|
||||
},
|
||||
openAI: {
|
||||
enabled: undefined,
|
||||
...defaultCustomSettings,
|
||||
...customProviderSettings.openAI,
|
||||
...defaultProviderSettings.openAI,
|
||||
...voidInitModelOptions.openAI,
|
||||
},
|
||||
gemini: {
|
||||
...defaultCustomSettings,
|
||||
...customProviderSettings.gemini,
|
||||
...defaultProviderSettings.gemini,
|
||||
...voidInitModelOptions.gemini,
|
||||
enabled: undefined,
|
||||
},
|
||||
groq: {
|
||||
...defaultCustomSettings,
|
||||
...customProviderSettings.groq,
|
||||
...defaultProviderSettings.groq,
|
||||
...voidInitModelOptions.groq,
|
||||
enabled: undefined,
|
||||
},
|
||||
ollama: {
|
||||
...defaultCustomSettings,
|
||||
...customProviderSettings.ollama,
|
||||
...defaultProviderSettings.ollama,
|
||||
...voidInitModelOptions.ollama,
|
||||
enabled: undefined,
|
||||
},
|
||||
openRouter: {
|
||||
...defaultCustomSettings,
|
||||
...customProviderSettings.openRouter,
|
||||
...defaultProviderSettings.openRouter,
|
||||
...voidInitModelOptions.openRouter,
|
||||
enabled: undefined,
|
||||
},
|
||||
openAICompatible: {
|
||||
...defaultCustomSettings,
|
||||
...customProviderSettings.openAICompatible,
|
||||
...defaultProviderSettings.openAICompatible,
|
||||
...voidInitModelOptions.openAICompatible,
|
||||
enabled: undefined,
|
||||
},
|
||||
|
|
@ -341,11 +340,19 @@ export const featureNames = ['Ctrl+L', 'Ctrl+K', 'Autocomplete'] as const
|
|||
|
||||
|
||||
|
||||
// the models of these can be refreshed (in theory all can, but not all should)
|
||||
export const refreshableProviderNames = ['ollama', 'openAICompatible'] satisfies ProviderName[]
|
||||
export type RefreshableProviderName = typeof refreshableProviderNames[number]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export type FeatureFlagSettings = {
|
||||
autoRefreshModels: boolean; // automatically scan for local models and enable when found
|
||||
autoRefreshModels: boolean;
|
||||
}
|
||||
export const defaultFeatureFlagSettings: FeatureFlagSettings = {
|
||||
autoRefreshModels: true,
|
||||
|
|
@ -360,7 +367,7 @@ type FeatureFlagDisplayInfo = {
|
|||
export const displayInfoOfFeatureFlag = (featureFlag: FeatureFlagName): FeatureFlagDisplayInfo => {
|
||||
if (featureFlag === 'autoRefreshModels') {
|
||||
return {
|
||||
description: 'Automatically scan for and enable local models.',
|
||||
description: `Automatically scan for and enable local models.`, // ${`refreshableProviderNames.map(providerName => titleOfProviderName(providerName)).join(', ')`}
|
||||
}
|
||||
}
|
||||
throw new Error(`featureFlagInfo: Unknown feature flag: "${featureFlag}"`)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
import { Ollama } from 'ollama';
|
||||
import { _InternalModelListFnType, _InternalSendLLMMessageFnType, OllamaModelResponse } from '../../common/llmMessageTypes.js';
|
||||
import { defaultProviderSettings } from '../../common/voidSettingsTypes.js';
|
||||
|
||||
export const ollamaList: _InternalModelListFnType<OllamaModelResponse> = async ({ onSuccess: onSuccess_, onError: onError_, settingsOfProvider }) => {
|
||||
|
||||
|
|
@ -18,6 +19,9 @@ export const ollamaList: _InternalModelListFnType<OllamaModelResponse> = async (
|
|||
|
||||
try {
|
||||
const thisConfig = settingsOfProvider.ollama
|
||||
// if endpoint is empty, normally ollama will send to 11434, but we want it to fail - the user should type it in
|
||||
if (!thisConfig.endpoint) throw new Error(`Ollama Endpoint was empty (please enter ${defaultProviderSettings.ollama.endpoint} in Void if you want the default url).`)
|
||||
|
||||
const ollama = new Ollama({ host: thisConfig.endpoint })
|
||||
ollama.list()
|
||||
.then((response) => {
|
||||
|
|
@ -38,6 +42,8 @@ export const ollamaList: _InternalModelListFnType<OllamaModelResponse> = async (
|
|||
export const sendOllamaMsg: _InternalSendLLMMessageFnType = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter }) => {
|
||||
|
||||
const thisConfig = settingsOfProvider.ollama
|
||||
// if endpoint is empty, normally ollama will send to 11434, but we want it to fail - the user should type it in
|
||||
if (!thisConfig.endpoint) throw new Error(`Ollama Endpoint was empty (please enter ${defaultProviderSettings.ollama.endpoint} if you want the default).`)
|
||||
|
||||
let fullText = ''
|
||||
|
||||
|
|
|
|||
|
|
@ -483,7 +483,7 @@ export const SidebarChat = () => {
|
|||
// .split(' ')
|
||||
// .map(style => `@@[&_div.monaco-inputbox]:!void-${style}`) // apply styles to ancestor input and textarea elements
|
||||
// .join(' ');
|
||||
`@@[&_textarea]:!void-bg-transparent @@[&_textarea]:!void-outline-none @@[&_textarea]:!void-text-vscode-input-fg @@[&_textarea]:!void-min-h-[81px] @@[&_textarea]:!void-max-h-[500px]@@[&_div.monaco-inputbox]:!void- @@[&_div.monaco-inputbox]:!void-outline-none`
|
||||
`@@[&_textarea]:!void-bg-transparent @@[&_textarea]:!void-outline-none @@[&_textarea]:!void-text-vscode-input-fg @@[&_textarea]:!void-min-h-[81px] @@[&_textarea]:!void-max-h-[500px] @@[&_div.monaco-inputbox]:!void-outline-none`
|
||||
}
|
||||
>
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
* {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* html {
|
||||
font-size: var(--vscode-font-size);
|
||||
|
|
|
|||
|
|
@ -96,15 +96,15 @@ export const VoidInputBox = ({ onChangeText, onCreateInstance, inputBoxRef, plac
|
|||
export const VoidSwitch = ({
|
||||
value,
|
||||
onChange,
|
||||
size = 'md',
|
||||
label,
|
||||
disabled = false,
|
||||
size = 'md'
|
||||
}: {
|
||||
value: boolean;
|
||||
onChange: (value: boolean) => void;
|
||||
label?: string;
|
||||
disabled?: boolean;
|
||||
size?: 'xs' | 'sm' | 'msm' | 'md';
|
||||
size?: 'xs' | 'sm' | 'sm+' | 'md';
|
||||
}) => {
|
||||
return (
|
||||
<label className="inline-flex items-center cursor-pointer">
|
||||
|
|
@ -113,10 +113,10 @@ export const VoidSwitch = ({
|
|||
className={`
|
||||
relative inline-flex items-center rounded-full transition-colors duration-200 ease-in-out
|
||||
${value ? 'bg-gray-900 dark:bg-white' : 'bg-gray-200 dark:bg-gray-700'}
|
||||
${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}
|
||||
${disabled ? 'opacity-25' : ''}
|
||||
${size === 'xs' ? 'h-4 w-7' : ''}
|
||||
${size === 'sm' ? 'h-5 w-9' : ''}
|
||||
${size === 'msm' ? 'h-5 w-10' : ''}
|
||||
${size === 'sm+' ? 'h-5 w-10' : ''}
|
||||
${size === 'md' ? 'h-6 w-11' : ''}
|
||||
`}
|
||||
>
|
||||
|
|
@ -125,11 +125,11 @@ export const VoidSwitch = ({
|
|||
inline-block transform rounded-full bg-white dark:bg-gray-900 shadow transition-transform duration-200 ease-in-out
|
||||
${size === 'xs' ? 'h-2.5 w-2.5' : ''}
|
||||
${size === 'sm' ? 'h-3 w-3' : ''}
|
||||
${size === 'msm' ? 'h-3.5 w-3.5' : ''}
|
||||
${size === 'sm+' ? 'h-3.5 w-3.5' : ''}
|
||||
${size === 'md' ? 'h-4 w-4' : ''}
|
||||
${size === 'xs' ? (value ? 'translate-x-3.5' : 'translate-x-0.5') : ''}
|
||||
${size === 'sm' ? (value ? 'translate-x-5' : 'translate-x-1') : ''}
|
||||
${size === 'msm' ? (value ? 'translate-x-6' : 'translate-x-1') : ''}
|
||||
${size === 'sm+' ? (value ? 'translate-x-6' : 'translate-x-1') : ''}
|
||||
${size === 'md' ? (value ? 'translate-x-6' : 'translate-x-1') : ''}
|
||||
`}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@
|
|||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { ThreadsState } from '../../../threadHistoryService.js'
|
||||
import { SettingsOfProvider } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
|
||||
import { RefreshableProviderName, SettingsOfProvider } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
|
||||
import { IDisposable } from '../../../../../../../base/common/lifecycle.js'
|
||||
import { ReactServicesType } from '../../../helpers/reactServicesHelper.js'
|
||||
import { VoidSidebarState } from '../../../sidebarStateService.js'
|
||||
import { VoidSettingsState } from '../../../../../../../platform/void/common/voidSettingsService.js'
|
||||
import { ColorScheme } from '../../../../../../../platform/theme/common/theme.js'
|
||||
import { RefreshableProviderName, RefreshModelStateOfProvider } from '../../../../../../../platform/void/common/refreshModelService.js'
|
||||
import { RefreshModelStateOfProvider } from '../../../../../../../platform/void/common/refreshModelService.js'
|
||||
|
||||
|
||||
// normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'
|
||||
import { ProviderName, SettingName, displayInfoOfSettingName, titleOfProviderName, providerNames, VoidModelInfo, featureFlagNames, displayInfoOfFeatureFlag, customSettingNamesOfProvider } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
|
||||
import { ProviderName, SettingName, displayInfoOfSettingName, titleOfProviderName, providerNames, VoidModelInfo, featureFlagNames, displayInfoOfFeatureFlag, customSettingNamesOfProvider, RefreshableProviderName, refreshableProviderNames } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
|
||||
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
|
||||
import { VoidCheckBox, VoidInputBox, VoidSelectBox, VoidSwitch } from '../util/inputs.js'
|
||||
import { useIsDark, useRefreshModelListener, useRefreshModelState, useService, useSettingsState } from '../util/services.js'
|
||||
import { X, RefreshCw, Loader2, Check } from 'lucide-react'
|
||||
import { RefreshableProviderName, refreshableProviderNames } from '../../../../../../../platform/void/common/refreshModelService.js'
|
||||
|
||||
|
||||
|
||||
|
|
@ -69,7 +68,7 @@ const AddModelMenu = ({ onSubmit }: { onSubmit: () => void }) => {
|
|||
const providerOptions = useMemo(() => providerNames.map(providerName => ({ text: titleOfProviderName(providerName), value: providerName })), [providerNames])
|
||||
|
||||
return <>
|
||||
<div className='flex justify-center items-center gap-4'>
|
||||
<div className='flex items-center gap-4'>
|
||||
{/* model */}
|
||||
<div className='max-w-40 w-full'>
|
||||
<VoidInputBox
|
||||
|
|
@ -89,8 +88,9 @@ const AddModelMenu = ({ onSubmit }: { onSubmit: () => void }) => {
|
|||
</div>
|
||||
|
||||
{/* button */}
|
||||
<div className='max-w-40 w-full'>
|
||||
<div className='max-w-40'>
|
||||
<button
|
||||
className='px-3 py-1 bg-black/10 dark:bg-gray-200/10 rounded-sm overflow-hidden'
|
||||
onClick={() => {
|
||||
const providerName = providerNameRef.current
|
||||
const modelName = modelNameRef.current
|
||||
|
|
@ -114,11 +114,14 @@ const AddModelMenu = ({ onSubmit }: { onSubmit: () => void }) => {
|
|||
|
||||
}}>Add model</button>
|
||||
</div>
|
||||
|
||||
{!errorString ? null : <div className='text-red-500 truncate whitespace-nowrap'>
|
||||
{errorString}
|
||||
</div>}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
{!errorString ? null : <div className='text-center text-red-500'>
|
||||
{errorString}
|
||||
</div>}
|
||||
</>
|
||||
|
||||
}
|
||||
|
|
@ -129,7 +132,10 @@ const AddModelMenuFull = () => {
|
|||
return <div className='my-2 hover:bg-black/10 dark:hover:bg-gray-200/10 py-1 px-3 rounded-sm overflow-hidden '>
|
||||
{open ?
|
||||
<AddModelMenu onSubmit={() => { setOpen(false) }} />
|
||||
: <button className='' onClick={() => setOpen(true)}>Add Model</button>
|
||||
: <button
|
||||
className='px-3 py-1 bg-black/10 dark:bg-gray-200/10 rounded-sm overflow-hidden'
|
||||
onClick={() => setOpen(true)}
|
||||
>Add Model</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
|
@ -148,24 +154,32 @@ export const ModelDump = () => {
|
|||
modelDump.push(...providerSettings.models.map(model => ({ ...model, providerName, providerEnabled: !!providerSettings.enabled })))
|
||||
}
|
||||
|
||||
// sort by hidden
|
||||
modelDump.sort((a, b) => {
|
||||
return Number(b.providerEnabled) - Number(a.providerEnabled)
|
||||
})
|
||||
|
||||
return <div className=''>
|
||||
{modelDump.map(m => {
|
||||
const { isHidden, isDefault, modelName, providerName, providerEnabled } = m
|
||||
|
||||
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'>
|
||||
<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>
|
||||
<button disabled={!providerEnabled} onClick={() => { settingsStateService.toggleModelHidden(providerName, modelName) }}>
|
||||
{!providerEnabled ? '🌑' // provider disabled
|
||||
: isHidden ? '❌' // model is disabled
|
||||
: '✅'}
|
||||
</button>
|
||||
<div className='w-5 flex items-center justify-center'>
|
||||
|
||||
<VoidSwitch
|
||||
value={!isHidden}
|
||||
onChange={() => { settingsStateService.toggleModelHidden(providerName, modelName) }}
|
||||
disabled={!providerEnabled}
|
||||
size='sm'
|
||||
/>
|
||||
|
||||
<div className={`w-5 flex items-center justify-center`}>
|
||||
{isDefault ? null : <button onClick={() => { settingsStateService.deleteModel(providerName, modelName) }}><X className='size-4' /></button>}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -180,16 +194,16 @@ export const ModelDump = () => {
|
|||
|
||||
const ProviderSetting = ({ providerName, settingName }: { providerName: ProviderName, settingName: SettingName }) => {
|
||||
|
||||
const { title, placeholder, } = displayInfoOfSettingName(providerName, settingName)
|
||||
const providerTitle = titleOfProviderName(providerName)
|
||||
const { title: settingTitle, placeholder, } = displayInfoOfSettingName(providerName, settingName)
|
||||
const voidSettingsService = useService('settingsStateService')
|
||||
|
||||
|
||||
let weChangedTextRef = false
|
||||
|
||||
return <ErrorBoundary>
|
||||
<div className='my-1'>
|
||||
<VoidInputBox
|
||||
placeholder={`Enter your ${title} here (${placeholder}).`}
|
||||
placeholder={`Enter your ${providerTitle} ${settingTitle} (${placeholder}).`}
|
||||
onChangeText={useCallback((newVal) => {
|
||||
if (weChangedTextRef) return
|
||||
voidSettingsService.setSettingOfProvider(providerName, settingName, newVal)
|
||||
|
|
@ -213,7 +227,6 @@ const ProviderSetting = ({ providerName, settingName }: { providerName: Provider
|
|||
/>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
|
||||
}
|
||||
|
||||
const SettingsForProvider = ({ providerName }: { providerName: ProviderName }) => {
|
||||
|
|
@ -223,9 +236,10 @@ const SettingsForProvider = ({ providerName }: { providerName: ProviderName }) =
|
|||
const { enabled } = voidSettingsState.settingsOfProvider[providerName]
|
||||
const settingNames = customSettingNamesOfProvider(providerName)
|
||||
|
||||
return <>
|
||||
<div className='flex items-center gap-4'>
|
||||
<h3 className='text-xl'>{titleOfProviderName(providerName)}</h3>
|
||||
return <div className='my-4'>
|
||||
<div className='flex items-center w-full gap-4'>
|
||||
<h3 className='text-xl truncate'>{titleOfProviderName(providerName)}</h3>
|
||||
{/* enable provider switch */}
|
||||
<VoidSwitch
|
||||
value={!!enabled}
|
||||
onChange={
|
||||
|
|
@ -233,28 +247,16 @@ const SettingsForProvider = ({ providerName }: { providerName: ProviderName }) =
|
|||
const enabledRef = voidSettingsService.state.settingsOfProvider[providerName].enabled
|
||||
voidSettingsService.setSettingOfProvider(providerName, 'enabled', !enabledRef)
|
||||
}, [voidSettingsService, providerName])}
|
||||
size='xs'
|
||||
disabled={false}
|
||||
label=''
|
||||
size='sm+'
|
||||
/>
|
||||
{/* <VoidCheckBox
|
||||
checked={!!enabled}
|
||||
onChange={
|
||||
useCallback(() => {
|
||||
const enabledRef = voidSettingsService.state.settingsOfProvider[providerName].enabled
|
||||
voidSettingsService.setSettingOfProvider(providerName, 'enabled', !enabledRef)
|
||||
}, [voidSettingsService, providerName])}
|
||||
/> */}
|
||||
|
||||
{/* <button className='flex items-center'
|
||||
onClick={() => { voidSettingsService.setSettingOfProvider(providerName, 'enabled', !enabled) }}
|
||||
>{enabled ? '✅' : '❌'}</button> */}
|
||||
</div>
|
||||
{/* settings besides models (e.g. api key) */}
|
||||
{settingNames.map((settingName, i) => {
|
||||
return <ProviderSetting key={settingName} providerName={providerName} settingName={settingName} />
|
||||
})}
|
||||
</>
|
||||
<div className='px-0'>
|
||||
{/* settings besides models (e.g. api key) */}
|
||||
{settingNames.map((settingName, i) => {
|
||||
return <ProviderSetting key={settingName} providerName={providerName} settingName={settingName} />
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -311,10 +313,10 @@ export const Settings = () => {
|
|||
|
||||
{/* tabs */}
|
||||
<div className='flex flex-col w-full max-w-32'>
|
||||
<button className={`text-left p-1 my-0.5 rounded-sm overflow-hidden ${tab === 'models' ? 'bg-black/10 dark:bg-gray-200/10' : ''} hover:bg-black/10 hover:dark:bg-gray-200/10 active:bg-black/10 active:dark:bg-gray-200/10 `}
|
||||
<button className={`text-left p-1 px-3 my-0.5 rounded-sm overflow-hidden ${tab === 'models' ? 'bg-black/10 dark:bg-gray-200/10' : ''} hover:bg-black/10 hover:dark:bg-gray-200/10 active:bg-black/10 active:dark:bg-gray-200/10 `}
|
||||
onClick={() => { setTab('models') }}
|
||||
>Models</button>
|
||||
<button className={`text-left p-1 my-0.5 rounded-sm overflow-hidden ${tab === 'features' ? 'bg-black/10 dark:bg-gray-200/10' : ''} hover:bg-black/10 hover:dark:bg-gray-200/10 active:bg-black/10 active:dark:bg-gray-200/10 `}
|
||||
<button className={`text-left p-1 px-3 my-0.5 rounded-sm overflow-hidden ${tab === 'features' ? 'bg-black/10 dark:bg-gray-200/10' : ''} hover:bg-black/10 hover:dark:bg-gray-200/10 active:bg-black/10 active:dark:bg-gray-200/10 `}
|
||||
onClick={() => { setTab('features') }}
|
||||
>Features</button>
|
||||
</div>
|
||||
|
|
@ -327,18 +329,17 @@ export const Settings = () => {
|
|||
<div className='w-full overflow-y-auto'>
|
||||
|
||||
<div className={`${tab !== 'models' ? 'hidden' : ''}`}>
|
||||
<h2 className={`text-3xl mb-2`}>Models</h2>
|
||||
<h2 className={`text-3xl mb-2`}>Providers</h2>
|
||||
<ErrorBoundary>
|
||||
<VoidProviderSettings />
|
||||
</ErrorBoundary>
|
||||
|
||||
<h2 className={`text-3xl mb-2 mt-4`}>Models</h2>
|
||||
<ErrorBoundary>
|
||||
<ModelDump />
|
||||
<AddModelMenuFull />
|
||||
<RefreshableModels />
|
||||
</ErrorBoundary>
|
||||
<h2 className={`text-3xl mt-4 mb-2`}>Providers</h2>
|
||||
<div className='px-3'>
|
||||
<ErrorBoundary>
|
||||
<VoidProviderSettings />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`${tab !== 'features' ? 'hidden' : ''}`}>
|
||||
|
|
|
|||
Loading…
Reference in a new issue