From 865c34f2822909dfdd0912bac9ae74430ab4a2f1 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Wed, 8 Jan 2025 22:06:58 -0800 Subject: [PATCH] new model dropdown draft --- .../void/common/voidSettingsService.ts | 8 +- .../react/src/sidebar-tsx/SidebarChat.tsx | 2 +- .../void/browser/react/src/util/inputs.tsx | 103 +++++++++++++++++- .../src/void-settings-tsx/ModelDropdown.tsx | 70 +++++++----- .../react/src/void-settings-tsx/Settings.tsx | 4 +- 5 files changed, 149 insertions(+), 38 deletions(-) diff --git a/src/vs/platform/void/common/voidSettingsService.ts b/src/vs/platform/void/common/voidSettingsService.ts index 895d3f6b..34a4ce80 100644 --- a/src/vs/platform/void/common/voidSettingsService.ts +++ b/src/vs/platform/void/common/voidSettingsService.ts @@ -29,7 +29,7 @@ type SetModelSelectionOfFeatureFn = ( 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 }) } 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 220d4197..31c9bafe 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 @@ -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) => { diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx index 0d895d67..09df6ef8 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx @@ -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 = ({ onChangeSelection, onCreateInstance, selectBoxRef, options, className }: { +export const VoidCustomSelectBox = ({ 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(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 ( +
+ {/* Select Button */} + + + {/* Dropdown Menu */} + {isOpen && ( +
+ {options.map((option) => { + + const thisOptionIsSelected = getOptionsEqual(option, selectedOption) + const optionName = getOptionName(option) + + return ( +
{ + onChangeOption(option); + setIsOpen(false); + }} + > +
+ {thisOptionIsSelected && ( + + + + )} +
+ {optionName} +
+ ) + })} +
+ )} +
+ ); +}; + + + +export const _VoidSelectBox = ({ onChangeSelection, onCreateInstance, selectBoxRef, options, className }: { onChangeSelection: (value: T) => void; onCreateInstance?: ((instance: SelectBox) => void | IDisposable[]); selectBoxRef?: React.MutableRefObject; @@ -211,9 +300,13 @@ export const VoidSelectBox = ({ onChangeSelection, onCreateInstance, selectB let containerRef = useRef(null); return { containerRef.current = container @@ -222,7 +315,7 @@ export const VoidSelectBox = ({ onChangeSelection, onCreateInstance, selectB options.map(opt => ({ text: opt.text })), defaultIndex, contextViewProvider, - defaultSelectBoxStyles + defaultSelectBoxStyles, ] as const; }, [containerRef, options, contextViewProvider])} diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx index c42c8334..709e9e1a 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx @@ -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 { + voidSettingsService.setModelSelectionOfFeature(featureName, newOption.selection) + }, [voidSettingsService, featureName]) + + return { - 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 { +// 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() 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 index af6edb50..627d7cc7 100644 --- 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 @@ -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 */}
- { providerNameRef.current = providerOptions[0].value }, [providerOptions])} // initialize state onChangeSelection={useCallback((providerName: ProviderName) => { providerNameRef.current = providerName }, [])} options={providerOptions}