mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
thinking slider UI
This commit is contained in:
parent
e1a1ca6275
commit
6b5792aaf7
3 changed files with 96 additions and 94 deletions
|
|
@ -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} />
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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' : ''}>
|
||||
|
|
|
|||
Loading…
Reference in a new issue