mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 12:15:57 +00:00
feat(dashboard): enrich droid docs and quick settings controls
- add reusable compatible-CLI docs registry with external provider fact-check links - render docs links in Droid notes panel and keep future CLI extension points - add left-column quick controls for key settings.json fields (reasoning/autonomy/diff/automation)
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
import { SlidersHorizontal } from 'lucide-react';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
|
||||
const UNSET_VALUE = '__unset__';
|
||||
|
||||
type DroidEnumSettingKey = 'reasoningEffort' | 'autonomyLevel' | 'diffMode';
|
||||
type DroidBooleanSettingKey =
|
||||
| 'todoEnabled'
|
||||
| 'todoAutoRefresh'
|
||||
| 'autoCompactEnabled'
|
||||
| 'soundEnabled';
|
||||
type DroidNumberSettingKey = 'maxTurns' | 'maxToolCalls' | 'autoCompactThreshold';
|
||||
|
||||
export interface DroidQuickSettingsValues {
|
||||
reasoningEffort: string | null;
|
||||
autonomyLevel: string | null;
|
||||
diffMode: string | null;
|
||||
maxTurns: number | null;
|
||||
maxToolCalls: number | null;
|
||||
autoCompactThreshold: number | null;
|
||||
todoEnabled: boolean | null;
|
||||
todoAutoRefresh: boolean | null;
|
||||
autoCompactEnabled: boolean | null;
|
||||
soundEnabled: boolean | null;
|
||||
}
|
||||
|
||||
interface DroidSettingsQuickControlsCardProps {
|
||||
values: DroidQuickSettingsValues;
|
||||
disabled: boolean;
|
||||
disabledReason?: string | null;
|
||||
onEnumSettingChange: (key: DroidEnumSettingKey, value: string | null) => void;
|
||||
onBooleanSettingChange: (key: DroidBooleanSettingKey, value: boolean | null) => void;
|
||||
onNumberSettingChange: (key: DroidNumberSettingKey, value: number | null) => void;
|
||||
}
|
||||
|
||||
const enumFieldConfig: Array<{
|
||||
key: DroidEnumSettingKey;
|
||||
label: string;
|
||||
description: string;
|
||||
options: Array<{ value: string; label: string }>;
|
||||
}> = [
|
||||
{
|
||||
key: 'reasoningEffort',
|
||||
label: 'Reasoning Effort',
|
||||
description: 'none | medium | high | max',
|
||||
options: [
|
||||
{ value: 'none', label: 'none' },
|
||||
{ value: 'medium', label: 'medium' },
|
||||
{ value: 'high', label: 'high' },
|
||||
{ value: 'max', label: 'max' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'autonomyLevel',
|
||||
label: 'Autonomy Level',
|
||||
description: 'suggest | aggressive | full',
|
||||
options: [
|
||||
{ value: 'suggest', label: 'suggest' },
|
||||
{ value: 'aggressive', label: 'aggressive' },
|
||||
{ value: 'full', label: 'full' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'diffMode',
|
||||
label: 'Diff Mode',
|
||||
description: 'auto | none | inline | split',
|
||||
options: [
|
||||
{ value: 'auto', label: 'auto' },
|
||||
{ value: 'none', label: 'none' },
|
||||
{ value: 'inline', label: 'inline' },
|
||||
{ value: 'split', label: 'split' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const booleanFieldConfig: Array<{
|
||||
key: DroidBooleanSettingKey;
|
||||
label: string;
|
||||
}> = [
|
||||
{ key: 'todoEnabled', label: 'Todo Enabled' },
|
||||
{ key: 'todoAutoRefresh', label: 'Todo Auto Refresh' },
|
||||
{ key: 'autoCompactEnabled', label: 'Auto Compact Enabled' },
|
||||
{ key: 'soundEnabled', label: 'Sound Enabled' },
|
||||
];
|
||||
|
||||
const numberFieldConfig: Array<{
|
||||
key: DroidNumberSettingKey;
|
||||
label: string;
|
||||
min: number;
|
||||
step: number;
|
||||
}> = [
|
||||
{ key: 'maxTurns', label: 'Max Turns', min: 1, step: 1 },
|
||||
{ key: 'maxToolCalls', label: 'Max Tool Calls', min: 1, step: 1 },
|
||||
{ key: 'autoCompactThreshold', label: 'Auto Compact Threshold', min: 1000, step: 1000 },
|
||||
];
|
||||
|
||||
function toBooleanSelectValue(value: boolean | null): string {
|
||||
if (value === true) return 'true';
|
||||
if (value === false) return 'false';
|
||||
return UNSET_VALUE;
|
||||
}
|
||||
|
||||
function toBooleanValue(value: string): boolean | null {
|
||||
if (value === 'true') return true;
|
||||
if (value === 'false') return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
export function DroidSettingsQuickControlsCard({
|
||||
values,
|
||||
disabled,
|
||||
disabledReason,
|
||||
onEnumSettingChange,
|
||||
onBooleanSettingChange,
|
||||
onNumberSettingChange,
|
||||
}: DroidSettingsQuickControlsCardProps) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<SlidersHorizontal className="h-4 w-4" />
|
||||
Quick Settings
|
||||
<Badge variant="outline" className="text-[10px] font-normal">
|
||||
settings.json
|
||||
</Badge>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{disabledReason && <p className="text-xs text-amber-600">{disabledReason}</p>}
|
||||
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
{enumFieldConfig.map((field) => (
|
||||
<div key={field.key} className="space-y-1">
|
||||
<p className="text-xs font-medium">{field.label}</p>
|
||||
<Select
|
||||
value={values[field.key] ?? UNSET_VALUE}
|
||||
onValueChange={(next) =>
|
||||
onEnumSettingChange(field.key, next === UNSET_VALUE ? null : next)
|
||||
}
|
||||
disabled={disabled}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs">
|
||||
<SelectValue placeholder="Use default" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={UNSET_VALUE}>Use default</SelectItem>
|
||||
{field.options.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-[11px] text-muted-foreground">{field.description}</p>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{numberFieldConfig.map((field) => (
|
||||
<div key={field.key} className="space-y-1">
|
||||
<p className="text-xs font-medium">{field.label}</p>
|
||||
<Input
|
||||
type="number"
|
||||
min={field.min}
|
||||
step={field.step}
|
||||
value={values[field.key] ?? ''}
|
||||
onChange={(event) => {
|
||||
const nextRaw = event.target.value.trim();
|
||||
if (!nextRaw) {
|
||||
onNumberSettingChange(field.key, null);
|
||||
return;
|
||||
}
|
||||
const next = Number.parseInt(nextRaw, 10);
|
||||
if (!Number.isFinite(next)) return;
|
||||
onNumberSettingChange(field.key, Math.max(field.min, next));
|
||||
}}
|
||||
className="h-8 text-xs"
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{booleanFieldConfig.map((field) => (
|
||||
<div key={field.key} className="space-y-1">
|
||||
<p className="text-xs font-medium">{field.label}</p>
|
||||
<Select
|
||||
value={toBooleanSelectValue(values[field.key])}
|
||||
onValueChange={(next) => onBooleanSettingChange(field.key, toBooleanValue(next))}
|
||||
disabled={disabled}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs">
|
||||
<SelectValue placeholder="Use default" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={UNSET_VALUE}>Use default</SelectItem>
|
||||
<SelectItem value="true">true</SelectItem>
|
||||
<SelectItem value="false">false</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user