Spaces:
Running
Running
File size: 5,737 Bytes
0ed8124 36a170e 6c62075 0ed8124 6c62075 0ed8124 6c62075 0ed8124 6c62075 0ed8124 6c62075 0ed8124 21ad36a 0ed8124 b4f163f 0ed8124 36a170e 0ed8124 6c62075 0ed8124 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | import { useEffect, useRef } from 'react';
import type { ModelOption, RuntimeSettings, TelemetrySnapshot } from '../../lib/contracts';
import { DEFAULT_MAX_TOKENS } from '../../lib/runtime-defaults';
import type { ThemeMode } from '../theme';
interface SettingsSheetProps {
open: boolean;
themeMode: ThemeMode;
onThemeModeChange: (theme: ThemeMode) => void;
activeModel: ModelOption;
telemetry: TelemetrySnapshot;
settings: RuntimeSettings;
onSettingsChange: (settings: RuntimeSettings) => void;
onPersistStorage: () => void;
onClearStorage: () => void;
onClearConversation: () => void;
onClose: () => void;
}
export function SettingsSheet({
open,
themeMode,
onThemeModeChange,
activeModel,
telemetry,
settings,
onSettingsChange,
onPersistStorage,
onClearStorage,
onClearConversation,
onClose,
}: SettingsSheetProps) {
const closeButtonRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
if (!open) return;
closeButtonRef.current?.focus();
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') onClose();
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [open, onClose]);
if (!open) return null;
const contextLimit = activeModel.id === 'bonsai-27b'
? activeModel.defaultContext
: activeModel.contextLimit;
const update = <Key extends keyof RuntimeSettings>(key: Key, value: RuntimeSettings[Key]) => {
onSettingsChange({ ...settings, [key]: value });
};
return (
<div className="settings-layer" role="presentation" onMouseDown={(event) => {
if (event.target === event.currentTarget) onClose();
}}>
<section className="settings-sheet" role="dialog" aria-modal="true" aria-labelledby="settings-title">
<header>
<h2 id="settings-title">Chat settings</h2>
<button ref={closeButtonRef} className="sheet-close" type="button" onClick={onClose} aria-label="Close settings">×</button>
</header>
<label className="setting-group setting-select">
<div><small>Appearance</small><strong>Follow the system or choose a theme</strong></div>
<select aria-label="Color theme" value={themeMode} onChange={(event) => onThemeModeChange(event.target.value as ThemeMode)}>
<option value="system">System</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</label>
<label className="setting-group setting-select">
<div><small>Compute backend</small><strong>Auto prefers WebGPU</strong></div>
<select value={settings.backend} onChange={(event) => update('backend', event.target.value as RuntimeSettings['backend'])}>
<option value="auto">Auto</option>
<option value="webgpu">WebGPU</option>
{activeModel.id !== 'bonsai-27b' && <option value="wasm">CPU · WASM</option>}
</select>
</label>
<div className="setting-group">
<div><small>Selected model</small><strong>{activeModel.label}</strong></div>
<span className="setting-locked">{activeModel.footprint}</span>
</div>
<label className="range-setting">
<span><small>Context allocation · applied on load</small><strong>{settings.contextSize.toLocaleString()} tokens</strong></span>
<input
type="range"
min={Math.min(1024, activeModel.defaultContext)}
max={contextLimit}
step={16}
value={settings.contextSize}
onChange={(event) => update('contextSize', Number(event.target.value))}
/>
<span className="range-limits"><i>{Math.min(1024, activeModel.defaultContext).toLocaleString()}</i><i>{contextLimit.toLocaleString()}</i></span>
</label>
<label className="range-setting">
<span><small>Temperature</small><strong>{settings.temperature.toFixed(2)}</strong></span>
<input type="range" min="0" max="2" step="0.05" value={settings.temperature} onChange={(event) => update('temperature', Number(event.target.value))} />
<span className="range-limits"><i>deterministic</i><i>exploratory</i></span>
</label>
<label className="range-setting">
<span><small>Maximum completion</small><strong>{settings.maxTokens} tokens</strong></span>
<input type="range" min="64" max={DEFAULT_MAX_TOKENS} step="16" value={settings.maxTokens} onChange={(event) => update('maxTokens', Number(event.target.value))} />
<span className="range-limits"><i>64</i><i>{DEFAULT_MAX_TOKENS.toLocaleString()}</i></span>
</label>
<label className="system-prompt-setting">
<span><small>System instructions</small><strong>Saved with this chat</strong></span>
<textarea rows={5} value={settings.systemPrompt} onChange={(event) => update('systemPrompt', event.target.value.slice(0, 8192))} />
</label>
<div className="settings-actions">
<button type="button" onClick={onPersistStorage}>Request persistent storage</button>
<button type="button" onClick={onClearConversation}>Clear conversation</button>
<button className="danger-action" type="button" onClick={onClearStorage}>Delete cached models</button>
</div>
<div className="settings-note">
<span className="signal-dot" aria-hidden="true" />
<p><strong>{telemetry.backend} boundary.</strong> Sampling and tools run in local workers. Changing backend or context reloads the selected model on the next run.</p>
</div>
</section>
</div>
);
}
|