mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
new model dropdown draft
This commit is contained in:
parent
ec65903692
commit
865c34f282
5 changed files with 149 additions and 38 deletions
|
|
@ -29,7 +29,7 @@ type SetModelSelectionOfFeatureFn = <K extends FeatureName>(
|
|||
|
||||
type SetFeatureFlagFn = (flagName: FeatureFlagName, newVal: boolean) => void;
|
||||
|
||||
export type ModelOption = { text: string, value: ModelSelection }
|
||||
export type ModelOption = { name: string, selection: ModelSelection }
|
||||
|
||||
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ let _computeModelOptions = (settingsOfProvider: SettingsOfProvider) => {
|
|||
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 } })
|
||||
modelOptions.push({ name: `${modelName} (${providerName})`, selection: { providerName, modelName } })
|
||||
}
|
||||
}
|
||||
return modelOptions
|
||||
|
|
@ -169,11 +169,11 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
|
|||
for (const featureName of featureNames) {
|
||||
|
||||
const currentSelection = newModelSelectionOfFeature[featureName]
|
||||
const selnIdx = currentSelection === null ? -1 : newModelsList.findIndex(m => modelSelectionsEqual(m.value, currentSelection))
|
||||
const selnIdx = currentSelection === null ? -1 : newModelsList.findIndex(m => modelSelectionsEqual(m.selection, currentSelection))
|
||||
|
||||
if (selnIdx === -1) {
|
||||
if (newModelsList.length !== 0)
|
||||
this.setModelSelectionOfFeature(featureName, newModelsList[0].value, { doNotApplyEffects: true })
|
||||
this.setModelSelectionOfFeature(featureName, newModelsList[0].selection, { doNotApplyEffects: true })
|
||||
else
|
||||
this.setModelSelectionOfFeature(featureName, null, { doNotApplyEffects: true })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -628,7 +628,7 @@ export const SidebarChat = () => {
|
|||
transition-all duration-200
|
||||
rounded-md
|
||||
bg-vscode-input-bg
|
||||
max-h-[60vh] overflow-y-auto
|
||||
max-h-[80vh] overflow-y-auto
|
||||
border border-vscode-commandcenter-inactive-border focus-within:border-vscode-commandcenter-active-border hover:border-vscode-commandcenter-active-border
|
||||
`}
|
||||
onKeyDown={(e) => {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* Void Editor additions licensed under the AGPL 3.0 License.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { IInputBoxStyles, InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
|
||||
import { defaultCheckboxStyles, defaultInputBoxStyles, defaultSelectBoxStyles } from '../../../../../../../platform/theme/browser/defaultStyles.js';
|
||||
import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js';
|
||||
|
|
@ -13,6 +13,7 @@ import { Checkbox } from '../../../../../../../base/browser/ui/toggle/toggle.js'
|
|||
import { CodeEditorWidget } from '../../../../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'
|
||||
import { useAccessor } from './services.js';
|
||||
import { ScrollableElement } from '../../../../../../../base/browser/ui/scrollbar/scrollableElement.js';
|
||||
import { ModelOption } from '../../../../../../../platform/void/common/voidSettingsService.js';
|
||||
|
||||
|
||||
// type guard
|
||||
|
|
@ -198,7 +199,95 @@ export const VoidCheckBox = ({ label, value, onClick, className }: { label: stri
|
|||
}
|
||||
|
||||
|
||||
export const VoidSelectBox = <T,>({ onChangeSelection, onCreateInstance, selectBoxRef, options, className }: {
|
||||
export const VoidCustomSelectBox = <T extends any>({ options, selectedOption, onChangeOption, getOptionName, getOptionsEqual }: { options: T[], selectedOption?: T, onChangeOption: (newValue: T) => void, getOptionName: (option: T) => string, getOptionsEqual: (a: T, b: T) => boolean }) => {
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const dropdownRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
if (!selectedOption) {
|
||||
selectedOption = options[0]
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<div className="relative inline-block" ref={dropdownRef}>
|
||||
{/* Select Button */}
|
||||
<button
|
||||
style={{ fontSize: '6px' }}
|
||||
className="flex items-center gap-1 px-2 h-4 bg-white hover:bg-gray-100 active:bg-gray-200 whitespace-nowrap"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
<span>{getOptionName(selectedOption)}</span>
|
||||
|
||||
{/* Check Mark */}
|
||||
<svg className="w-2 h-2 flex-shrink-0" viewBox="0 0 12 12" fill="none">
|
||||
<path
|
||||
d="M2.5 4.5L6 8L9.5 4.5"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Dropdown Menu */}
|
||||
{isOpen && (
|
||||
<div className="absolute z-10 py-1 mt-1 bg-white border rounded shadow-lg">
|
||||
{options.map((option) => {
|
||||
|
||||
const thisOptionIsSelected = getOptionsEqual(option, selectedOption)
|
||||
const optionName = getOptionName(option)
|
||||
|
||||
return (
|
||||
<div
|
||||
key={optionName}
|
||||
className={`flex items-center h-4 px-2 cursor-pointer whitespace-nowrap text-[6px]
|
||||
transition-colors duration-100
|
||||
hover:bg-gray-100 active:bg-gray-200
|
||||
${thisOptionIsSelected ? 'bg-gray-50' : ''}
|
||||
`}
|
||||
onClick={() => {
|
||||
onChangeOption(option);
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
<div className="w-4 flex justify-center flex-shrink-0">
|
||||
{thisOptionIsSelected && (
|
||||
<svg className="w-2 h-2" viewBox="0 0 12 12" fill="none">
|
||||
<path
|
||||
d="M10 3L4.5 8.5L2 6"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
<span>{optionName}</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const _VoidSelectBox = <T,>({ onChangeSelection, onCreateInstance, selectBoxRef, options, className }: {
|
||||
onChangeSelection: (value: T) => void;
|
||||
onCreateInstance?: ((instance: SelectBox) => void | IDisposable[]);
|
||||
selectBoxRef?: React.MutableRefObject<SelectBox | null>;
|
||||
|
|
@ -211,9 +300,13 @@ export const VoidSelectBox = <T,>({ onChangeSelection, onCreateInstance, selectB
|
|||
let containerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
return <WidgetComponent
|
||||
className={`@@select-child-restyle
|
||||
className={`
|
||||
@@select-child-restyle
|
||||
@@[&_select]:!void-text-void-fg-3
|
||||
${className ?? ''}`}
|
||||
@@[&_select]:!void-text-xs
|
||||
!text-void-fg-3
|
||||
${className ?? ''}
|
||||
`}
|
||||
ctor={SelectBox}
|
||||
propsFn={useCallback((container) => {
|
||||
containerRef.current = container
|
||||
|
|
@ -222,7 +315,7 @@ export const VoidSelectBox = <T,>({ onChangeSelection, onCreateInstance, selectB
|
|||
options.map(opt => ({ text: opt.text })),
|
||||
defaultIndex,
|
||||
contextViewProvider,
|
||||
defaultSelectBoxStyles
|
||||
defaultSelectBoxStyles,
|
||||
] as const;
|
||||
}, [containerRef, options, contextViewProvider])}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { FeatureName, featureNames, ModelSelection, modelSelectionsEqual, ProviderName, providerNames } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
|
||||
import { useSettingsState, useRefreshModelState, useAccessor } from '../util/services.js'
|
||||
import { VoidSelectBox } from '../util/inputs.js'
|
||||
import { _VoidSelectBox, VoidCustomSelectBox } 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 } from '../../../voidSettingsPane.js'
|
||||
|
|
@ -17,43 +17,61 @@ import { ModelOption } from '../../../../../../../platform/void/common/voidSetti
|
|||
const optionsEqual = (m1: ModelOption[], m2: ModelOption[]) => {
|
||||
if (m1.length !== m2.length) return false
|
||||
for (let i = 0; i < m1.length; i++) {
|
||||
if (!modelSelectionsEqual(m1[i].value, m2[i].value)) return false
|
||||
if (!modelSelectionsEqual(m1[i].selection, m2[i].selection)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
|
||||
const ModelSelectBox = ({ options, featureName }: { options: ModelOption[], featureName: FeatureName }) => {
|
||||
const accessor = useAccessor()
|
||||
|
||||
const voidSettingsService = accessor.get('IVoidSettingsService')
|
||||
|
||||
let weChangedText = false
|
||||
const selection = voidSettingsService.state.modelSelectionOfFeature[featureName]
|
||||
const selectedOption = selection ? voidSettingsService.state._modelOptions.find(v => modelSelectionsEqual(v.selection, selection)) : options[0]
|
||||
|
||||
return <VoidSelectBox
|
||||
className='@@[&_select]:!void-text-xs text-void-fg-3'
|
||||
const onChangeOption = useCallback((newOption: ModelOption) => {
|
||||
voidSettingsService.setModelSelectionOfFeature(featureName, newOption.selection)
|
||||
}, [voidSettingsService, featureName])
|
||||
|
||||
return <VoidCustomSelectBox
|
||||
options={options}
|
||||
onChangeSelection={useCallback((newVal: ModelSelection) => {
|
||||
if (weChangedText) return
|
||||
voidSettingsService.setModelSelectionOfFeature(featureName, newVal)
|
||||
}, [voidSettingsService, 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._modelOptions // as a ref
|
||||
const settingsAtProvider = voidSettingsService.state.modelSelectionOfFeature[featureName]
|
||||
const selectionIdx = settingsAtProvider === null ? -1 : modelsListRef.findIndex(v => modelSelectionsEqual(v.value, settingsAtProvider))
|
||||
weChangedText = true
|
||||
instance.select(selectionIdx === -1 ? 0 : selectionIdx)
|
||||
weChangedText = false
|
||||
}
|
||||
syncInstance()
|
||||
const disposable = voidSettingsService.onDidChangeState(syncInstance)
|
||||
return [disposable]
|
||||
}, [voidSettingsService, featureName])}
|
||||
selectedOption={selectedOption}
|
||||
onChangeOption={onChangeOption}
|
||||
getOptionName={(option) => option.name}
|
||||
getOptionsEqual={(a, b) => optionsEqual([a], [b])}
|
||||
|
||||
/>
|
||||
}
|
||||
// const ModelSelectBox = ({ options, featureName }: { options: ModelOption[], featureName: FeatureName }) => {
|
||||
// const accessor = useAccessor()
|
||||
|
||||
// const voidSettingsService = accessor.get('IVoidSettingsService')
|
||||
|
||||
// let weChangedText = false
|
||||
|
||||
// return <VoidSelectBox
|
||||
// className='@@[&_select]:!void-text-xs text-void-fg-3'
|
||||
// options={options}
|
||||
// onChangeSelection={useCallback((newVal: ModelSelection) => {
|
||||
// if (weChangedText) return
|
||||
// voidSettingsService.setModelSelectionOfFeature(featureName, newVal)
|
||||
// }, [voidSettingsService, 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._modelOptions // as a ref
|
||||
// const settingsAtProvider = voidSettingsService.state.modelSelectionOfFeature[featureName]
|
||||
// const selectionIdx = settingsAtProvider === null ? -1 : modelsListRef.findIndex(v => modelSelectionsEqual(v.value, settingsAtProvider))
|
||||
// weChangedText = true
|
||||
// instance.select(selectionIdx === -1 ? 0 : selectionIdx)
|
||||
// weChangedText = false
|
||||
// }
|
||||
// syncInstance()
|
||||
// const disposable = voidSettingsService.onDidChangeState(syncInstance)
|
||||
// return [disposable]
|
||||
// }, [voidSettingsService, featureName])}
|
||||
// />
|
||||
// }
|
||||
|
||||
const MemoizedModelSelectBox = ({ featureName }: { featureName: FeatureName }) => {
|
||||
const settingsState = useSettingsState()
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|||
import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'
|
||||
import { ProviderName, SettingName, displayInfoOfSettingName, providerNames, VoidModelInfo, featureFlagNames, displayInfoOfFeatureFlag, customSettingNamesOfProvider, RefreshableProviderName, refreshableProviderNames, displayInfoOfProviderName, defaultProviderSettings, nonlocalProviderNames, localProviderNames } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
|
||||
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
|
||||
import { VoidCheckBox, VoidInputBox, VoidSelectBox, VoidSwitch } from '../util/inputs.js'
|
||||
import { VoidCheckBox, VoidInputBox, _VoidSelectBox, VoidSwitch } from '../util/inputs.js'
|
||||
import { useAccessor, useIsDark, useRefreshModelListener, useRefreshModelState, useSettingsState } from '../util/services.js'
|
||||
import { X, RefreshCw, Loader2, Check, MoveRight } from 'lucide-react'
|
||||
import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js'
|
||||
|
|
@ -98,7 +98,7 @@ const AddModelMenu = ({ onSubmit }: { onSubmit: () => void }) => {
|
|||
|
||||
{/* provider */}
|
||||
<div className='max-w-40 w-full border border-vscode-editorwidget-border'>
|
||||
<VoidSelectBox
|
||||
<_VoidSelectBox
|
||||
onCreateInstance={useCallback(() => { providerNameRef.current = providerOptions[0].value }, [providerOptions])} // initialize state
|
||||
onChangeSelection={useCallback((providerName: ProviderName) => { providerNameRef.current = providerName }, [])}
|
||||
options={providerOptions}
|
||||
|
|
|
|||
Loading…
Reference in a new issue