mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 00:21:21 +00:00
feat(settings): add Display panel for output/visual settings
Split General settings panel by creating new Display tab containing: - Max output lines per response - Document graph settings (external links, max nodes) - Context window warnings (thresholds) Updated docs to reflect new panel location.
This commit is contained in:
@@ -12,7 +12,8 @@ Settings are organized into tabs:
|
||||
|
||||
| Tab | Contents |
|
||||
|-----|----------|
|
||||
| **General** | Font family and size, terminal width, log level and buffer, max output lines, shell configuration, input send behavior, default toggles (history, thinking), automatic tab naming, power management, updates, privacy, context warnings, usage stats, document graph, storage location |
|
||||
| **General** | Font family and size, terminal width, log level and buffer, shell configuration, input send behavior, default toggles (history, thinking), automatic tab naming, power management, updates, privacy, usage stats, storage location |
|
||||
| **Display** | Max output lines per response, document graph settings, context window warnings |
|
||||
| **Shortcuts** | Customize keyboard shortcuts (see [Keyboard Shortcuts](./keyboard-shortcuts)) |
|
||||
| **Themes** | Dark, light, and vibe mode themes, custom theme builder with import/export |
|
||||
| **Notifications** | OS notifications, custom command notifications, toast notification duration |
|
||||
|
||||
@@ -105,7 +105,7 @@ For best results, **compact your context before reaching 60-70% usage** — don'
|
||||
|
||||
### Configuring Warnings
|
||||
|
||||
Customize warning thresholds in **Settings** (`Cmd+,` / `Ctrl+,`) → **General** → **Context Window Warnings**:
|
||||
Customize warning thresholds in **Settings** (`Cmd+,` / `Ctrl+,`) → **Display** → **Context Window Warnings**:
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -272,7 +272,7 @@ interface SettingsModalProps {
|
||||
setCrashReportingEnabled: (value: boolean) => void;
|
||||
customAICommands: CustomAICommand[];
|
||||
setCustomAICommands: (commands: CustomAICommand[]) => void;
|
||||
initialTab?: 'general' | 'llm' | 'shortcuts' | 'theme' | 'notifications' | 'aicommands' | 'ssh';
|
||||
initialTab?: 'general' | 'display' | 'llm' | 'shortcuts' | 'theme' | 'notifications' | 'aicommands' | 'ssh';
|
||||
hasNoAgents?: boolean;
|
||||
onThemeImportError?: (message: string) => void;
|
||||
onThemeImportSuccess?: (message: string) => void;
|
||||
@@ -314,7 +314,7 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
|
||||
} = useSettings();
|
||||
|
||||
const [activeTab, setActiveTab] = useState<
|
||||
'general' | 'llm' | 'shortcuts' | 'theme' | 'notifications' | 'aicommands' | 'ssh'
|
||||
'general' | 'display' | 'llm' | 'shortcuts' | 'theme' | 'notifications' | 'aicommands' | 'ssh'
|
||||
>('general');
|
||||
const [systemFonts, setSystemFonts] = useState<string[]>([]);
|
||||
const [customFonts, setCustomFonts] = useState<string[]>([]);
|
||||
@@ -468,10 +468,10 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
|
||||
|
||||
const handleTabNavigation = (e: KeyboardEvent) => {
|
||||
const tabs: Array<
|
||||
'general' | 'llm' | 'shortcuts' | 'theme' | 'notifications' | 'aicommands' | 'ssh'
|
||||
'general' | 'display' | 'llm' | 'shortcuts' | 'theme' | 'notifications' | 'aicommands' | 'ssh'
|
||||
> = FEATURE_FLAGS.LLM_SETTINGS
|
||||
? ['general', 'llm', 'shortcuts', 'theme', 'notifications', 'aicommands', 'ssh']
|
||||
: ['general', 'shortcuts', 'theme', 'notifications', 'aicommands', 'ssh'];
|
||||
? ['general', 'display', 'llm', 'shortcuts', 'theme', 'notifications', 'aicommands', 'ssh']
|
||||
: ['general', 'display', 'shortcuts', 'theme', 'notifications', 'aicommands', 'ssh'];
|
||||
const currentIndex = tabs.indexOf(activeTab);
|
||||
|
||||
if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === '[') {
|
||||
@@ -877,6 +877,15 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
|
||||
<Settings className="w-4 h-4" />
|
||||
{activeTab === 'general' && <span>General</span>}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('display')}
|
||||
className={`px-4 py-4 text-sm font-bold border-b-2 ${activeTab === 'display' ? 'border-indigo-500' : 'border-transparent'} flex items-center gap-2`}
|
||||
tabIndex={-1}
|
||||
title="Display"
|
||||
>
|
||||
<Monitor className="w-4 h-4" />
|
||||
{activeTab === 'display' && <span>Display</span>}
|
||||
</button>
|
||||
{FEATURE_FLAGS.LLM_SETTINGS && (
|
||||
<button
|
||||
onClick={() => setActiveTab('llm')}
|
||||
@@ -1025,29 +1034,6 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Max Output Lines */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold opacity-70 uppercase mb-2">
|
||||
Max Output Lines per Response
|
||||
</label>
|
||||
<ToggleButtonGroup
|
||||
options={[
|
||||
{ value: 15 },
|
||||
{ value: 25 },
|
||||
{ value: 50 },
|
||||
{ value: 100 },
|
||||
{ value: Infinity, label: 'All' },
|
||||
]}
|
||||
value={props.maxOutputLines}
|
||||
onChange={props.setMaxOutputLines}
|
||||
theme={theme}
|
||||
/>
|
||||
<p className="text-xs opacity-50 mt-2">
|
||||
Long outputs will be collapsed into a scrollable window. Set to "All" to always
|
||||
show full output.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Default Shell */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold opacity-70 uppercase mb-1 flex items-center gap-2">
|
||||
@@ -1662,181 +1648,6 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
|
||||
theme={theme}
|
||||
/>
|
||||
|
||||
{/* Context Window Warnings */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold opacity-70 uppercase mb-2 flex items-center gap-2">
|
||||
<AlertTriangle className="w-3 h-3" />
|
||||
Context Window Warnings
|
||||
</label>
|
||||
<div
|
||||
className="p-3 rounded border space-y-3"
|
||||
style={{ borderColor: theme.colors.border, backgroundColor: theme.colors.bgMain }}
|
||||
>
|
||||
{/* Enable/Disable Toggle */}
|
||||
<div
|
||||
className="flex items-center justify-between cursor-pointer"
|
||||
onClick={() =>
|
||||
updateContextManagementSettings({
|
||||
contextWarningsEnabled: !contextManagementSettings.contextWarningsEnabled,
|
||||
})
|
||||
}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
updateContextManagementSettings({
|
||||
contextWarningsEnabled: !contextManagementSettings.contextWarningsEnabled,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex-1 pr-3">
|
||||
<div className="font-medium" style={{ color: theme.colors.textMain }}>
|
||||
Show context consumption warnings
|
||||
</div>
|
||||
<div
|
||||
className="text-xs opacity-50 mt-0.5"
|
||||
style={{ color: theme.colors.textDim }}
|
||||
>
|
||||
Display warning banners when context window usage reaches configurable
|
||||
thresholds
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
updateContextManagementSettings({
|
||||
contextWarningsEnabled: !contextManagementSettings.contextWarningsEnabled,
|
||||
});
|
||||
}}
|
||||
className="relative w-10 h-5 rounded-full transition-colors flex-shrink-0"
|
||||
style={{
|
||||
backgroundColor: contextManagementSettings.contextWarningsEnabled
|
||||
? theme.colors.accent
|
||||
: theme.colors.bgActivity,
|
||||
}}
|
||||
role="switch"
|
||||
aria-checked={contextManagementSettings.contextWarningsEnabled}
|
||||
>
|
||||
<span
|
||||
className={`absolute left-0 top-0.5 w-4 h-4 rounded-full bg-white transition-transform ${
|
||||
contextManagementSettings.contextWarningsEnabled
|
||||
? 'translate-x-5'
|
||||
: 'translate-x-0.5'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Threshold Sliders (ghosted when disabled) */}
|
||||
<div
|
||||
className="space-y-4 pt-3 border-t"
|
||||
style={{
|
||||
borderColor: theme.colors.border,
|
||||
opacity: contextManagementSettings.contextWarningsEnabled ? 1 : 0.4,
|
||||
pointerEvents: contextManagementSettings.contextWarningsEnabled
|
||||
? 'auto'
|
||||
: 'none',
|
||||
}}
|
||||
>
|
||||
{/* Yellow Warning Threshold */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label
|
||||
className="text-xs font-medium flex items-center gap-2"
|
||||
style={{ color: theme.colors.textMain }}
|
||||
>
|
||||
<div
|
||||
className="w-2.5 h-2.5 rounded-full"
|
||||
style={{ backgroundColor: '#eab308' }}
|
||||
/>
|
||||
Yellow warning threshold
|
||||
</label>
|
||||
<span
|
||||
className="text-xs font-mono px-2 py-0.5 rounded"
|
||||
style={{ backgroundColor: 'rgba(234, 179, 8, 0.2)', color: '#fde047' }}
|
||||
>
|
||||
{contextManagementSettings.contextWarningYellowThreshold}%
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min={30}
|
||||
max={90}
|
||||
step={5}
|
||||
value={contextManagementSettings.contextWarningYellowThreshold}
|
||||
onChange={(e) => {
|
||||
const newYellow = Number(e.target.value);
|
||||
// Validation: ensure yellow < red by at least 10%
|
||||
if (newYellow >= contextManagementSettings.contextWarningRedThreshold) {
|
||||
// Bump red threshold up
|
||||
updateContextManagementSettings({
|
||||
contextWarningYellowThreshold: newYellow,
|
||||
contextWarningRedThreshold: Math.min(95, newYellow + 10),
|
||||
});
|
||||
} else {
|
||||
updateContextManagementSettings({
|
||||
contextWarningYellowThreshold: newYellow,
|
||||
});
|
||||
}
|
||||
}}
|
||||
className="w-full h-2 rounded-lg appearance-none cursor-pointer"
|
||||
style={{
|
||||
background: `linear-gradient(to right, #eab308 0%, #eab308 ${((contextManagementSettings.contextWarningYellowThreshold - 30) / 60) * 100}%, ${theme.colors.bgActivity} ${((contextManagementSettings.contextWarningYellowThreshold - 30) / 60) * 100}%, ${theme.colors.bgActivity} 100%)`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Red Warning Threshold */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label
|
||||
className="text-xs font-medium flex items-center gap-2"
|
||||
style={{ color: theme.colors.textMain }}
|
||||
>
|
||||
<div
|
||||
className="w-2.5 h-2.5 rounded-full"
|
||||
style={{ backgroundColor: '#ef4444' }}
|
||||
/>
|
||||
Red warning threshold
|
||||
</label>
|
||||
<span
|
||||
className="text-xs font-mono px-2 py-0.5 rounded"
|
||||
style={{ backgroundColor: 'rgba(239, 68, 68, 0.2)', color: '#fca5a5' }}
|
||||
>
|
||||
{contextManagementSettings.contextWarningRedThreshold}%
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min={50}
|
||||
max={95}
|
||||
step={5}
|
||||
value={contextManagementSettings.contextWarningRedThreshold}
|
||||
onChange={(e) => {
|
||||
const newRed = Number(e.target.value);
|
||||
// Validation: ensure red > yellow by at least 10%
|
||||
if (newRed <= contextManagementSettings.contextWarningYellowThreshold) {
|
||||
// Bump yellow threshold down
|
||||
updateContextManagementSettings({
|
||||
contextWarningRedThreshold: newRed,
|
||||
contextWarningYellowThreshold: Math.max(30, newRed - 10),
|
||||
});
|
||||
} else {
|
||||
updateContextManagementSettings({ contextWarningRedThreshold: newRed });
|
||||
}
|
||||
}}
|
||||
className="w-full h-2 rounded-lg appearance-none cursor-pointer"
|
||||
style={{
|
||||
background: `linear-gradient(to right, #ef4444 0%, #ef4444 ${((contextManagementSettings.contextWarningRedThreshold - 50) / 45) * 100}%, ${theme.colors.bgActivity} ${((contextManagementSettings.contextWarningRedThreshold - 50) / 45) * 100}%, ${theme.colors.bgActivity} 100%)`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Data Management */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold opacity-70 uppercase mb-2 flex items-center gap-2">
|
||||
@@ -2037,89 +1848,6 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Document Graph Settings */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold opacity-70 uppercase mb-2 flex items-center gap-2">
|
||||
<Sparkles className="w-3 h-3" />
|
||||
Document Graph
|
||||
<span
|
||||
className="px-1.5 py-0.5 rounded text-[9px] font-bold uppercase"
|
||||
style={{
|
||||
backgroundColor: theme.colors.warning + '30',
|
||||
color: theme.colors.warning,
|
||||
}}
|
||||
>
|
||||
Beta
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
className="p-3 rounded border space-y-3"
|
||||
style={{ borderColor: theme.colors.border, backgroundColor: theme.colors.bgMain }}
|
||||
>
|
||||
{/* Show External Links */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm" style={{ color: theme.colors.textMain }}>
|
||||
Show external links by default
|
||||
</p>
|
||||
<p className="text-xs opacity-50 mt-0.5">
|
||||
Display external website links as nodes. Can be toggled in the graph view.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() =>
|
||||
setDocumentGraphShowExternalLinks(!documentGraphShowExternalLinks)
|
||||
}
|
||||
className="relative w-10 h-5 rounded-full transition-colors"
|
||||
style={{
|
||||
backgroundColor: documentGraphShowExternalLinks
|
||||
? theme.colors.accent
|
||||
: theme.colors.bgActivity,
|
||||
}}
|
||||
role="switch"
|
||||
aria-checked={documentGraphShowExternalLinks}
|
||||
>
|
||||
<span
|
||||
className={`absolute left-0 top-0.5 w-4 h-4 rounded-full bg-white transition-transform ${
|
||||
documentGraphShowExternalLinks ? 'translate-x-5' : 'translate-x-0.5'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Max Nodes */}
|
||||
<div>
|
||||
<label className="block text-xs opacity-60 mb-2">
|
||||
Maximum nodes to display
|
||||
</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="range"
|
||||
min={50}
|
||||
max={1000}
|
||||
step={50}
|
||||
value={documentGraphMaxNodes}
|
||||
onChange={(e) => setDocumentGraphMaxNodes(Number(e.target.value))}
|
||||
className="flex-1 h-2 rounded-lg appearance-none cursor-pointer"
|
||||
style={{
|
||||
background: `linear-gradient(to right, ${theme.colors.accent} 0%, ${theme.colors.accent} ${((documentGraphMaxNodes - 50) / 950) * 100}%, ${theme.colors.bgActivity} ${((documentGraphMaxNodes - 50) / 950) * 100}%, ${theme.colors.bgActivity} 100%)`,
|
||||
}}
|
||||
/>
|
||||
<span
|
||||
className="text-sm font-mono w-12 text-right"
|
||||
style={{ color: theme.colors.textMain }}
|
||||
>
|
||||
{documentGraphMaxNodes}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs opacity-50 mt-1">
|
||||
Limits initial graph size for performance. Use "Load more" to show
|
||||
additional nodes.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Settings Storage Location */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold opacity-70 uppercase mb-2 flex items-center gap-2">
|
||||
@@ -2314,6 +2042,291 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'display' && (
|
||||
<div className="space-y-5">
|
||||
{/* Max Output Lines */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold opacity-70 uppercase mb-2">
|
||||
Max Output Lines per Response
|
||||
</label>
|
||||
<ToggleButtonGroup
|
||||
options={[
|
||||
{ value: 15 },
|
||||
{ value: 25 },
|
||||
{ value: 50 },
|
||||
{ value: 100 },
|
||||
{ value: Infinity, label: 'All' },
|
||||
]}
|
||||
value={props.maxOutputLines}
|
||||
onChange={props.setMaxOutputLines}
|
||||
theme={theme}
|
||||
/>
|
||||
<p className="text-xs opacity-50 mt-2">
|
||||
Long outputs will be collapsed into a scrollable window. Set to "All" to always
|
||||
show full output.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Document Graph Settings */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold opacity-70 uppercase mb-2 flex items-center gap-2">
|
||||
<Sparkles className="w-3 h-3" />
|
||||
Document Graph
|
||||
<span
|
||||
className="px-1.5 py-0.5 rounded text-[9px] font-bold uppercase"
|
||||
style={{
|
||||
backgroundColor: theme.colors.warning + '30',
|
||||
color: theme.colors.warning,
|
||||
}}
|
||||
>
|
||||
Beta
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
className="p-3 rounded border space-y-3"
|
||||
style={{ borderColor: theme.colors.border, backgroundColor: theme.colors.bgMain }}
|
||||
>
|
||||
{/* Show External Links */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm" style={{ color: theme.colors.textMain }}>
|
||||
Show external links by default
|
||||
</p>
|
||||
<p className="text-xs opacity-50 mt-0.5">
|
||||
Display external website links as nodes. Can be toggled in the graph view.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() =>
|
||||
setDocumentGraphShowExternalLinks(!documentGraphShowExternalLinks)
|
||||
}
|
||||
className="relative w-10 h-5 rounded-full transition-colors"
|
||||
style={{
|
||||
backgroundColor: documentGraphShowExternalLinks
|
||||
? theme.colors.accent
|
||||
: theme.colors.bgActivity,
|
||||
}}
|
||||
role="switch"
|
||||
aria-checked={documentGraphShowExternalLinks}
|
||||
>
|
||||
<span
|
||||
className={`absolute left-0 top-0.5 w-4 h-4 rounded-full bg-white transition-transform ${
|
||||
documentGraphShowExternalLinks ? 'translate-x-5' : 'translate-x-0.5'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Max Nodes */}
|
||||
<div>
|
||||
<label className="block text-xs opacity-60 mb-2">
|
||||
Maximum nodes to display
|
||||
</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="range"
|
||||
min={50}
|
||||
max={1000}
|
||||
step={50}
|
||||
value={documentGraphMaxNodes}
|
||||
onChange={(e) => setDocumentGraphMaxNodes(Number(e.target.value))}
|
||||
className="flex-1 h-2 rounded-lg appearance-none cursor-pointer"
|
||||
style={{
|
||||
background: `linear-gradient(to right, ${theme.colors.accent} 0%, ${theme.colors.accent} ${((documentGraphMaxNodes - 50) / 950) * 100}%, ${theme.colors.bgActivity} ${((documentGraphMaxNodes - 50) / 950) * 100}%, ${theme.colors.bgActivity} 100%)`,
|
||||
}}
|
||||
/>
|
||||
<span
|
||||
className="text-sm font-mono w-12 text-right"
|
||||
style={{ color: theme.colors.textMain }}
|
||||
>
|
||||
{documentGraphMaxNodes}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs opacity-50 mt-1">
|
||||
Limits initial graph size for performance. Use "Load more" to show
|
||||
additional nodes.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Context Window Warnings */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold opacity-70 uppercase mb-2 flex items-center gap-2">
|
||||
<AlertTriangle className="w-3 h-3" />
|
||||
Context Window Warnings
|
||||
</label>
|
||||
<div
|
||||
className="p-3 rounded border space-y-3"
|
||||
style={{ borderColor: theme.colors.border, backgroundColor: theme.colors.bgMain }}
|
||||
>
|
||||
{/* Enable/Disable Toggle */}
|
||||
<div
|
||||
className="flex items-center justify-between cursor-pointer"
|
||||
onClick={() =>
|
||||
updateContextManagementSettings({
|
||||
contextWarningsEnabled: !contextManagementSettings.contextWarningsEnabled,
|
||||
})
|
||||
}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
updateContextManagementSettings({
|
||||
contextWarningsEnabled: !contextManagementSettings.contextWarningsEnabled,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex-1 pr-3">
|
||||
<div className="font-medium" style={{ color: theme.colors.textMain }}>
|
||||
Show context consumption warnings
|
||||
</div>
|
||||
<div
|
||||
className="text-xs opacity-50 mt-0.5"
|
||||
style={{ color: theme.colors.textDim }}
|
||||
>
|
||||
Display warning banners when context window usage reaches configurable
|
||||
thresholds
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
updateContextManagementSettings({
|
||||
contextWarningsEnabled: !contextManagementSettings.contextWarningsEnabled,
|
||||
});
|
||||
}}
|
||||
className="relative w-10 h-5 rounded-full transition-colors flex-shrink-0"
|
||||
style={{
|
||||
backgroundColor: contextManagementSettings.contextWarningsEnabled
|
||||
? theme.colors.accent
|
||||
: theme.colors.bgActivity,
|
||||
}}
|
||||
role="switch"
|
||||
aria-checked={contextManagementSettings.contextWarningsEnabled}
|
||||
>
|
||||
<span
|
||||
className={`absolute left-0 top-0.5 w-4 h-4 rounded-full bg-white transition-transform ${
|
||||
contextManagementSettings.contextWarningsEnabled
|
||||
? 'translate-x-5'
|
||||
: 'translate-x-0.5'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Threshold Sliders (ghosted when disabled) */}
|
||||
<div
|
||||
className="space-y-4 pt-3 border-t"
|
||||
style={{
|
||||
borderColor: theme.colors.border,
|
||||
opacity: contextManagementSettings.contextWarningsEnabled ? 1 : 0.4,
|
||||
pointerEvents: contextManagementSettings.contextWarningsEnabled
|
||||
? 'auto'
|
||||
: 'none',
|
||||
}}
|
||||
>
|
||||
{/* Yellow Warning Threshold */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label
|
||||
className="text-xs font-medium flex items-center gap-2"
|
||||
style={{ color: theme.colors.textMain }}
|
||||
>
|
||||
<div
|
||||
className="w-2.5 h-2.5 rounded-full"
|
||||
style={{ backgroundColor: '#eab308' }}
|
||||
/>
|
||||
Yellow warning threshold
|
||||
</label>
|
||||
<span
|
||||
className="text-xs font-mono px-2 py-0.5 rounded"
|
||||
style={{ backgroundColor: 'rgba(234, 179, 8, 0.2)', color: '#fde047' }}
|
||||
>
|
||||
{contextManagementSettings.contextWarningYellowThreshold}%
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min={30}
|
||||
max={90}
|
||||
step={5}
|
||||
value={contextManagementSettings.contextWarningYellowThreshold}
|
||||
onChange={(e) => {
|
||||
const newYellow = Number(e.target.value);
|
||||
// Validation: ensure yellow < red by at least 10%
|
||||
if (newYellow >= contextManagementSettings.contextWarningRedThreshold) {
|
||||
// Bump red threshold up
|
||||
updateContextManagementSettings({
|
||||
contextWarningYellowThreshold: newYellow,
|
||||
contextWarningRedThreshold: Math.min(95, newYellow + 10),
|
||||
});
|
||||
} else {
|
||||
updateContextManagementSettings({
|
||||
contextWarningYellowThreshold: newYellow,
|
||||
});
|
||||
}
|
||||
}}
|
||||
className="w-full h-2 rounded-lg appearance-none cursor-pointer"
|
||||
style={{
|
||||
background: `linear-gradient(to right, #eab308 0%, #eab308 ${((contextManagementSettings.contextWarningYellowThreshold - 30) / 60) * 100}%, ${theme.colors.bgActivity} ${((contextManagementSettings.contextWarningYellowThreshold - 30) / 60) * 100}%, ${theme.colors.bgActivity} 100%)`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Red Warning Threshold */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label
|
||||
className="text-xs font-medium flex items-center gap-2"
|
||||
style={{ color: theme.colors.textMain }}
|
||||
>
|
||||
<div
|
||||
className="w-2.5 h-2.5 rounded-full"
|
||||
style={{ backgroundColor: '#ef4444' }}
|
||||
/>
|
||||
Red warning threshold
|
||||
</label>
|
||||
<span
|
||||
className="text-xs font-mono px-2 py-0.5 rounded"
|
||||
style={{ backgroundColor: 'rgba(239, 68, 68, 0.2)', color: '#fca5a5' }}
|
||||
>
|
||||
{contextManagementSettings.contextWarningRedThreshold}%
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min={50}
|
||||
max={95}
|
||||
step={5}
|
||||
value={contextManagementSettings.contextWarningRedThreshold}
|
||||
onChange={(e) => {
|
||||
const newRed = Number(e.target.value);
|
||||
// Validation: ensure red > yellow by at least 10%
|
||||
if (newRed <= contextManagementSettings.contextWarningYellowThreshold) {
|
||||
// Bump yellow threshold down
|
||||
updateContextManagementSettings({
|
||||
contextWarningRedThreshold: newRed,
|
||||
contextWarningYellowThreshold: Math.max(30, newRed - 10),
|
||||
});
|
||||
} else {
|
||||
updateContextManagementSettings({ contextWarningRedThreshold: newRed });
|
||||
}
|
||||
}}
|
||||
className="w-full h-2 rounded-lg appearance-none cursor-pointer"
|
||||
style={{
|
||||
background: `linear-gradient(to right, #ef4444 0%, #ef4444 ${((contextManagementSettings.contextWarningRedThreshold - 50) / 45) * 100}%, ${theme.colors.bgActivity} ${((contextManagementSettings.contextWarningRedThreshold - 50) / 45) * 100}%, ${theme.colors.bgActivity} 100%)`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'llm' && FEATURE_FLAGS.LLM_SETTINGS && (
|
||||
<div className="space-y-5">
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user