thinking slider UI

This commit is contained in:
Andrew Pareles 2025-03-06 02:51:48 -08:00
parent e1a1ca6275
commit 6b5792aaf7
3 changed files with 96 additions and 94 deletions

View file

@ -165,34 +165,42 @@ const ReasoningOptionDropdown = () => {
let toggleButton: React.ReactNode = null
if (canToggleReasoning) {
toggleButton = <VoidSwitch
size='xs'
value={isEnabled}
onChange={(newVal) => { voidSettingsService.setOptionsOfModelSelection(modelSelection.providerName, modelSelection.modelName, { reasoningEnabled: newVal }) }}
/>
toggleButton =
<div className='flex items-center gap-x-3'>
<span className='text-void-fg-3 text-xs pointer-events-none inline-block w-10'>{isEnabled ? 'Thinking' : 'Thinking'}</span>
<VoidSwitch
size='xs'
value={isEnabled}
onChange={(newVal) => { voidSettingsService.setOptionsOfModelSelection(modelSelection.providerName, modelSelection.modelName, { reasoningEnabled: newVal }) }}
/>
</div>
}
let slider: React.ReactNode = null
if (isEnabled && reasoningBudgetOptions?.type === 'slider') {
const { min, max, default: defaultVal } = reasoningBudgetOptions
const value = voidSettingsState.optionsOfModelSelection[modelSelection.providerName]?.[modelSelection.modelName]?.reasoningBudget ?? defaultVal
slider = <VoidSlider
width={50}
size='xs'
min={min}
max={max}
step={(max - min) / 8}
value={value}
onChange={(newVal) => { voidSettingsService.setOptionsOfModelSelection(modelSelection.providerName, modelSelection.modelName, { reasoningBudget: newVal }) }}
label={value + ''}
/>
slider = <div className='flex items-center gap-x-3'>
<span className='text-void-fg-3 text-xs pointer-events-none inline-block w-10'>Budget</span>
<VoidSlider
width={50}
size='xs'
min={min}
max={max}
step={(max - min) / 8}
value={value}
onChange={(newVal) => { voidSettingsService.setOptionsOfModelSelection(modelSelection.providerName, modelSelection.modelName, { reasoningBudget: newVal }) }}
/>
<span className='text-void-fg-3 text-xs pointer-events-none'>{`${value} tokens`}</span>
</div>
}
return <>
{toggleButton}
{slider}
</>
}
@ -294,14 +302,12 @@ export const VoidChatArea: React.FC<VoidChatAreaProps> = ({
{/* Bottom row */}
<div className='flex flex-row justify-between items-end gap-1'>
{showModelDropdown && (
<div className='max-w-[150px] @@[&_select]:!void-border-none @@[&_select]:!void-outline-none flex-grow'
onClick={(e) => { e.preventDefault(); e.stopPropagation() }}>
<div className='max-w-[200px] flex-grow'>
<ReasoningOptionDropdown />
<ModelDropdown featureName={featureName} />
</div>
)}
<ReasoningOptionDropdown />
{isStreaming ? (
<ButtonStop onClick={onAbort} />
) : (

View file

@ -169,7 +169,7 @@ export const VoidInputBox = ({ onChangeText, onCreateInstance, inputBoxRef, plac
ctor={InputBox}
className='
bg-void-bg-1
@@[&_::placeholder]:!void-text-void-fg-3
placeholder:!void-text-void-fg-3
'
propsFn={useCallback((container) => [
container,
@ -213,13 +213,10 @@ export const VoidInputBox = ({ onChangeText, onCreateInstance, inputBoxRef, plac
export const VoidSlider = ({
value,
onChange,
size = 'md',
label,
disabled = false,
min = 0,
max = 7,
@ -229,7 +226,6 @@ export const VoidSlider = ({
}: {
value: number;
onChange: (value: number) => void;
label?: string;
disabled?: boolean;
size?: 'xs' | 'sm' | 'sm+' | 'md';
min?: number;
@ -241,6 +237,12 @@ export const VoidSlider = ({
// Calculate percentage for position
const percentage = ((value - min) / (max - min)) * 100;
// Get numeric thumb size for padding calculation
const thumbSizePx =
size === 'xs' ? 2.5 * 4 : // Convert rem to px (approx)
size === 'sm' ? 3 * 4 :
size === 'sm+' ? 3.5 * 4 : 4 * 4; // md size
// Handle track click
const handleTrackClick = (e: React.MouseEvent<HTMLDivElement>) => {
if (disabled) return;
@ -293,90 +295,89 @@ export const VoidSlider = ({
return (
<div className={`inline-flex items-center flex-shrink-0 ${className}`}>
<div className={`relative flex-shrink-0 ${disabled ? 'opacity-25' : ''}`} style={{ width }}>
{/* Track */}
<div
className={`relative ${size === 'xs' ? 'h-1' :
{/* Outer container with padding to account for thumb overhang */}
<div className={`relative flex-shrink-0 ${disabled ? 'opacity-25' : ''}`}
style={{
width,
// Add horizontal padding equal to half the thumb width
paddingLeft: thumbSizePx / 2,
paddingRight: thumbSizePx / 2
}}>
{/* Track container with adjusted width */}
<div className="relative w-full">
{/* Track */}
<div
className={`relative ${size === 'xs' ? 'h-1' :
size === 'sm' ? 'h-1.5' :
size === 'sm+' ? 'h-2' : 'h-2.5'
} bg-gray-200 dark:bg-gray-700 rounded-full cursor-pointer`}
onClick={handleTrackClick}
>
{/* Filled part of track */}
<div
className={`absolute left-0 ${size === 'xs' ? 'h-1' :
} bg-gray-200 dark:bg-gray-700 rounded-full cursor-pointer`}
onClick={handleTrackClick}
>
{/* Filled part of track */}
<div
className={`absolute left-0 ${size === 'xs' ? 'h-1' :
size === 'sm' ? 'h-1.5' :
size === 'sm+' ? 'h-2' : 'h-2.5'
} bg-gray-900 dark:bg-white rounded-full`}
style={{ width: `${percentage}%` }}
} bg-gray-900 dark:bg-white rounded-full`}
style={{ width: `${percentage}%` }}
/>
</div>
{/* Thumb with sizes matching VoidSwitch */}
<div
className={`absolute top-1/2 transform -translate-x-1/2 -translate-y-1/2
${size === 'xs' ? 'h-2.5 w-2.5' : size === 'sm' ? 'h-3 w-3' : size === 'sm+' ? 'h-3.5 w-3.5' : 'h-4 w-4'}
bg-white dark:bg-gray-900 rounded-full shadow-md ${disabled ? 'cursor-not-allowed' : 'cursor-grab active:cursor-grabbing'}`}
style={{ left: `${percentage}%` }}
onMouseDown={(e) => {
if (disabled) return;
const track = e.currentTarget.previousElementSibling;
const handleMouseMove = (moveEvent: MouseEvent) => {
handleThumbDrag(moveEvent, track as Element);
};
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
document.body.style.cursor = '';
document.body.style.userSelect = '';
};
document.body.style.userSelect = 'none';
document.body.style.cursor = 'grabbing';
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
e.preventDefault();
}}
/>
</div>
{/* Thumb */}
<div
className={`absolute top-1/2 transform -translate-x-1/2 -translate-y-1/2 ${size === 'xs' ? 'h-3 w-3' :
size === 'sm' ? 'h-4 w-4' :
size === 'sm+' ? 'h-5 w-5' : 'h-6 w-6'
} bg-white dark:bg-gray-900 rounded-full shadow-md ${disabled ? 'cursor-not-allowed' : 'cursor-grab active:cursor-grabbing'}`}
style={{ left: `${percentage}%` }}
onMouseDown={(e) => {
if (disabled) return;
const track = e.currentTarget.previousElementSibling;
const handleMouseMove = (moveEvent: MouseEvent) => {
handleThumbDrag(moveEvent, track as Element);
};
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
document.body.style.cursor = '';
document.body.style.userSelect = '';
};
document.body.style.userSelect = 'none';
document.body.style.cursor = 'grabbing';
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
e.preventDefault();
}}
/>
</div>
{label && (
<span className={`ml-3 text-gray-900 dark:text-gray-100 ${size === 'xs' ? 'text-xs' : 'text-sm'
}`}>
{label}
</span>
)}
</div>
);
};
export const VoidSwitch = ({
value,
onChange,
size = 'md',
label,
disabled = false,
}: {
value: boolean;
onChange: (value: boolean) => void;
label?: string;
disabled?: boolean;
size?: 'xs' | 'sm' | 'sm+' | 'md';
}) => {
return (
<label className="inline-flex items-center cursor-pointer">
<label className="inline-flex items-center">
<div
onClick={() => !disabled && onChange(!value)}
className={`
cursor-pointer
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-25' : ''}
@ -400,14 +401,6 @@ export const VoidSwitch = ({
`}
/>
</div>
{label && (
<span className={`
ml-3 text-gray-900 dark:text-gray-100
${size === 'xs' ? 'text-xs' : 'text-sm'}
`}>
{label}
</span>
)}
</label>
);
};

View file

@ -230,9 +230,7 @@ export const ModelDump = () => {
<VoidSwitch
value={disabled ? false : !isHidden}
onChange={() => {
settingsStateService.toggleModelHidden(providerName, modelName)
}}
onChange={() => { settingsStateService.toggleModelHidden(providerName, modelName) }}
disabled={disabled}
size='sm'
/>
@ -445,8 +443,13 @@ export const FeaturesTab = () => {
<div className='w-full'>
<h4 className={`text-base`}>{displayInfoOfFeatureName('Autocomplete')}</h4>
<div className='text-sm italic text-void-fg-3 my-1'>Experimental. Only works with models that support FIM.</div>
<div className='flex items-center gap-x-2'>
<VoidSwitch size='xs' value={voidSettingsState.globalSettings.enableAutocomplete} onChange={(newVal) => voidSettingsService.setGlobalSetting('enableAutocomplete', newVal)} label={voidSettingsState.globalSettings.enableAutocomplete ? 'Enabled' : 'Disabled'} />
<div className='flex items-center gap-x-3'>
<VoidSwitch
size='xs'
value={voidSettingsState.globalSettings.enableAutocomplete}
onChange={(newVal) => voidSettingsService.setGlobalSetting('enableAutocomplete', newVal)}
/>
<span className='text-void-fg-3 text-xs pointer-events-none'>{voidSettingsState.globalSettings.enableAutocomplete ? 'Enabled' : 'Disabled'}</span>
</div>
<div className={!voidSettingsState.globalSettings.enableAutocomplete ? 'hidden' : ''}>