mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-15 20:20:09 +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,105 @@
|
||||
export interface CompatibleCliDocLink {
|
||||
id: string;
|
||||
label: string;
|
||||
url: string;
|
||||
category: 'overview' | 'configuration' | 'byok' | 'reference';
|
||||
source: 'factory' | 'provider';
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface CompatibleCliProviderDocLink {
|
||||
provider: string;
|
||||
label: string;
|
||||
apiFormat: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface CompatibleCliDocsReference {
|
||||
providerValues: string[];
|
||||
settingsHierarchy: string[];
|
||||
notes: string[];
|
||||
links: CompatibleCliDocLink[];
|
||||
providerDocs: CompatibleCliProviderDocLink[];
|
||||
}
|
||||
|
||||
interface CompatibleCliDocsRegistryEntry {
|
||||
cliId: string;
|
||||
displayName: string;
|
||||
docsReference: CompatibleCliDocsReference;
|
||||
}
|
||||
|
||||
const COMPATIBLE_CLI_DOCS_REGISTRY: Record<string, CompatibleCliDocsRegistryEntry> = {
|
||||
droid: {
|
||||
cliId: 'droid',
|
||||
displayName: 'Droid CLI',
|
||||
docsReference: {
|
||||
providerValues: ['anthropic', 'openai', 'generic-chat-completion-api'],
|
||||
settingsHierarchy: [
|
||||
'project-level config',
|
||||
'user-level config',
|
||||
'home-level config',
|
||||
'CLI flags and env vars',
|
||||
],
|
||||
notes: [
|
||||
'BYOK custom models are read from ~/.factory/settings.json customModels[]',
|
||||
'Factory docs mention legacy support for ~/.factory/config.json',
|
||||
'Interactive model selection uses settings.model (custom:<alias>)',
|
||||
'droid exec supports --model for one-off execution mode',
|
||||
],
|
||||
links: [
|
||||
{
|
||||
id: 'droid-cli-overview',
|
||||
label: 'Droid CLI Overview',
|
||||
url: 'https://docs.factory.ai/cli/',
|
||||
category: 'overview',
|
||||
source: 'factory',
|
||||
description: 'Primary entry docs for setup, auth, and core CLI usage.',
|
||||
},
|
||||
{
|
||||
id: 'droid-byok-overview',
|
||||
label: 'BYOK Overview',
|
||||
url: 'https://docs.factory.ai/cli/byok/overview/',
|
||||
category: 'byok',
|
||||
source: 'factory',
|
||||
description: 'BYOK model/provider shape, provider values, and migration notes.',
|
||||
},
|
||||
{
|
||||
id: 'droid-settings-reference',
|
||||
label: 'settings.json Reference',
|
||||
url: 'https://docs.factory.ai/cli/configuration/settings/',
|
||||
category: 'configuration',
|
||||
source: 'factory',
|
||||
description: 'Supported settings keys, defaults, and allowed values.',
|
||||
},
|
||||
],
|
||||
providerDocs: [
|
||||
{
|
||||
provider: 'anthropic',
|
||||
label: 'Anthropic Messages API',
|
||||
apiFormat: 'Messages API',
|
||||
url: 'https://docs.anthropic.com/en/api/messages',
|
||||
},
|
||||
{
|
||||
provider: 'openai',
|
||||
label: 'OpenAI Responses API',
|
||||
apiFormat: 'Responses API',
|
||||
url: 'https://platform.openai.com/docs/api-reference/responses',
|
||||
},
|
||||
{
|
||||
provider: 'generic-chat-completion-api',
|
||||
label: 'OpenAI Chat Completions Spec',
|
||||
apiFormat: 'Chat Completions API',
|
||||
url: 'https://platform.openai.com/docs/api-reference/chat',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function getCompatibleCliDocsReference(cliId: string): CompatibleCliDocsReference {
|
||||
const entry = COMPATIBLE_CLI_DOCS_REGISTRY[cliId];
|
||||
if (!entry) {
|
||||
throw new Error(`Unsupported compatible CLI docs reference: ${cliId}`);
|
||||
}
|
||||
return entry.docsReference;
|
||||
}
|
||||
@@ -44,6 +44,30 @@ export interface DroidByokDiagnostics {
|
||||
customModels: DroidCustomModelDiagnostics[];
|
||||
}
|
||||
|
||||
export interface CompatibleCliDocLink {
|
||||
id: string;
|
||||
label: string;
|
||||
url: string;
|
||||
category: 'overview' | 'configuration' | 'byok' | 'reference';
|
||||
source: 'factory' | 'provider';
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface CompatibleCliProviderDocLink {
|
||||
provider: string;
|
||||
label: string;
|
||||
apiFormat: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface CompatibleCliDocsReference {
|
||||
providerValues: string[];
|
||||
settingsHierarchy: string[];
|
||||
notes: string[];
|
||||
links: CompatibleCliDocLink[];
|
||||
providerDocs: CompatibleCliProviderDocLink[];
|
||||
}
|
||||
|
||||
export interface DroidDashboardDiagnostics {
|
||||
binary: DroidBinaryDiagnostics;
|
||||
files: {
|
||||
@@ -52,11 +76,7 @@ export interface DroidDashboardDiagnostics {
|
||||
};
|
||||
byok: DroidByokDiagnostics;
|
||||
warnings: string[];
|
||||
docsReference: {
|
||||
providerValues: string[];
|
||||
settingsHierarchy: string[];
|
||||
notes: string[];
|
||||
};
|
||||
docsReference: CompatibleCliDocsReference;
|
||||
}
|
||||
|
||||
export interface DroidRawSettingsResponse {
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
probeJsonObjectFile,
|
||||
writeJsonObjectFileAtomic,
|
||||
} from './compatible-cli-json-file-service';
|
||||
import { getCompatibleCliDocsReference } from './compatible-cli-docs-registry';
|
||||
|
||||
interface DroidConfigPaths {
|
||||
settingsPath: string;
|
||||
@@ -160,6 +161,7 @@ export function summarizeDroidCustomModels(customModelsValue: unknown): DroidByo
|
||||
export async function getDroidDashboardDiagnostics(): Promise<DroidDashboardDiagnostics> {
|
||||
const paths = resolveDroidConfigPaths();
|
||||
const binaryPath = detectDroidCli();
|
||||
const docsReference = getCompatibleCliDocsReference('droid');
|
||||
|
||||
const source = process.env.CCS_DROID_PATH ? 'CCS_DROID_PATH' : binaryPath ? 'PATH' : 'missing';
|
||||
|
||||
@@ -204,21 +206,7 @@ export async function getDroidDashboardDiagnostics(): Promise<DroidDashboardDiag
|
||||
},
|
||||
byok,
|
||||
warnings,
|
||||
docsReference: {
|
||||
providerValues: ['anthropic', 'openai', 'generic-chat-completion-api'],
|
||||
settingsHierarchy: [
|
||||
'project-level config',
|
||||
'user-level config',
|
||||
'home-level config',
|
||||
'CLI flags and env vars',
|
||||
],
|
||||
notes: [
|
||||
'BYOK custom models are read from ~/.factory/settings.json customModels[]',
|
||||
'Factory docs mention legacy support for ~/.factory/config.json',
|
||||
'Interactive model selection uses settings.model (custom:<alias>)',
|
||||
'droid exec supports --model for one-off execution mode',
|
||||
],
|
||||
},
|
||||
docsReference,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import * as path from 'path';
|
||||
import {
|
||||
DroidRawSettingsConflictError,
|
||||
DroidRawSettingsValidationError,
|
||||
getDroidDashboardDiagnostics,
|
||||
getDroidRawSettings,
|
||||
maskApiKeyPreview,
|
||||
resolveDroidConfigPaths,
|
||||
@@ -110,6 +111,19 @@ describe('droid-dashboard-service', () => {
|
||||
expect(raw.rawText).toContain('invalid-json');
|
||||
});
|
||||
|
||||
it('includes structured docs links for fact-checking providers', async () => {
|
||||
const diagnostics = await getDroidDashboardDiagnostics();
|
||||
|
||||
expect(diagnostics.docsReference.links.length).toBeGreaterThan(0);
|
||||
expect(diagnostics.docsReference.providerDocs.length).toBeGreaterThan(0);
|
||||
expect(diagnostics.docsReference.links.every((link) => link.url.startsWith('https://'))).toBe(
|
||||
true
|
||||
);
|
||||
expect(
|
||||
diagnostics.docsReference.providerDocs.some((doc) => doc.provider === 'anthropic')
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('saves valid raw settings content', async () => {
|
||||
const result = await saveDroidRawSettings({
|
||||
rawText: JSON.stringify({
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -36,6 +36,22 @@ export interface DroidCustomModelDiagnostics {
|
||||
apiKeyPreview: string | null;
|
||||
}
|
||||
|
||||
export interface CompatibleCliDocLink {
|
||||
id: string;
|
||||
label: string;
|
||||
url: string;
|
||||
category: 'overview' | 'configuration' | 'byok' | 'reference';
|
||||
source: 'factory' | 'provider';
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface CompatibleCliProviderDocLink {
|
||||
provider: string;
|
||||
label: string;
|
||||
apiFormat: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface DroidDashboardDiagnostics {
|
||||
binary: DroidBinaryDiagnostics;
|
||||
files: {
|
||||
@@ -56,6 +72,8 @@ export interface DroidDashboardDiagnostics {
|
||||
providerValues: string[];
|
||||
settingsHierarchy: string[];
|
||||
notes: string[];
|
||||
links: CompatibleCliDocLink[];
|
||||
providerDocs: CompatibleCliProviderDocLink[];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+140
-8
@@ -4,6 +4,7 @@ import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||
import {
|
||||
AlertTriangle,
|
||||
CheckCircle2,
|
||||
ExternalLink,
|
||||
Folder,
|
||||
GripVertical,
|
||||
Loader2,
|
||||
@@ -15,6 +16,10 @@ import {
|
||||
import { useDroid } from '@/hooks/use-droid';
|
||||
import { isApiConflictError } from '@/lib/api-client';
|
||||
import { RawJsonSettingsEditorPanel } from '@/components/compatible-cli/raw-json-settings-editor-panel';
|
||||
import {
|
||||
DroidSettingsQuickControlsCard,
|
||||
type DroidQuickSettingsValues,
|
||||
} from '@/components/compatible-cli/droid-settings-quick-controls-card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
@@ -33,18 +38,32 @@ function formatBytes(value: number | null | undefined): string {
|
||||
return `${(value / (1024 * 1024)).toFixed(2)} MB`;
|
||||
}
|
||||
|
||||
function validateJsonObject(text: string): { valid: true } | { valid: false; error: string } {
|
||||
function parseJsonObjectText(
|
||||
text: string
|
||||
): { valid: true; value: Record<string, unknown> } | { valid: false; error: string } {
|
||||
try {
|
||||
const parsed = JSON.parse(text);
|
||||
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||
return { valid: false, error: 'JSON root must be an object.' };
|
||||
}
|
||||
return { valid: true };
|
||||
return { valid: true, value: parsed as Record<string, unknown> };
|
||||
} catch (error) {
|
||||
return { valid: false, error: (error as Error).message };
|
||||
}
|
||||
}
|
||||
|
||||
function asStringValue(value: unknown): string | null {
|
||||
return typeof value === 'string' ? value : null;
|
||||
}
|
||||
|
||||
function asNumberValue(value: unknown): number | null {
|
||||
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
||||
}
|
||||
|
||||
function asBooleanValue(value: unknown): boolean | null {
|
||||
return typeof value === 'boolean' ? value : null;
|
||||
}
|
||||
|
||||
function DetailRow({
|
||||
label,
|
||||
value,
|
||||
@@ -79,6 +98,59 @@ export function DroidPage() {
|
||||
const rawBaseText = rawSettings?.rawText ?? '{}';
|
||||
const rawEditorText = rawDraftText ?? rawBaseText;
|
||||
const rawConfigDirty = rawDraftText !== null && rawDraftText !== rawBaseText;
|
||||
const rawEditorParsed = parseJsonObjectText(rawEditorText);
|
||||
const rawEditorValidation = rawEditorParsed.valid
|
||||
? { valid: true as const }
|
||||
: { valid: false as const, error: rawEditorParsed.error };
|
||||
|
||||
const setRawEditorDraftText = (nextText: string) => {
|
||||
if (nextText === rawBaseText) {
|
||||
setRawDraftText(null);
|
||||
return;
|
||||
}
|
||||
setRawDraftText(nextText);
|
||||
};
|
||||
|
||||
const updateSettingsField = (key: string, value: unknown | null) => {
|
||||
if (!rawEditorParsed.valid) {
|
||||
toast.error('Fix JSON syntax before using quick settings controls.');
|
||||
return;
|
||||
}
|
||||
|
||||
const nextSettings = { ...rawEditorParsed.value };
|
||||
if (value === null || value === undefined) {
|
||||
delete nextSettings[key];
|
||||
} else {
|
||||
nextSettings[key] = value;
|
||||
}
|
||||
setRawEditorDraftText(JSON.stringify(nextSettings, null, 2) + '\n');
|
||||
};
|
||||
|
||||
const quickSettingsValues: DroidQuickSettingsValues = rawEditorParsed.valid
|
||||
? {
|
||||
reasoningEffort: asStringValue(rawEditorParsed.value.reasoningEffort),
|
||||
autonomyLevel: asStringValue(rawEditorParsed.value.autonomyLevel),
|
||||
diffMode: asStringValue(rawEditorParsed.value.diffMode),
|
||||
maxTurns: asNumberValue(rawEditorParsed.value.maxTurns),
|
||||
maxToolCalls: asNumberValue(rawEditorParsed.value.maxToolCalls),
|
||||
autoCompactThreshold: asNumberValue(rawEditorParsed.value.autoCompactThreshold),
|
||||
todoEnabled: asBooleanValue(rawEditorParsed.value.todoEnabled),
|
||||
todoAutoRefresh: asBooleanValue(rawEditorParsed.value.todoAutoRefresh),
|
||||
autoCompactEnabled: asBooleanValue(rawEditorParsed.value.autoCompactEnabled),
|
||||
soundEnabled: asBooleanValue(rawEditorParsed.value.soundEnabled),
|
||||
}
|
||||
: {
|
||||
reasoningEffort: null,
|
||||
autonomyLevel: null,
|
||||
diffMode: null,
|
||||
maxTurns: null,
|
||||
maxToolCalls: null,
|
||||
autoCompactThreshold: null,
|
||||
todoEnabled: null,
|
||||
todoAutoRefresh: null,
|
||||
autoCompactEnabled: null,
|
||||
soundEnabled: null,
|
||||
};
|
||||
|
||||
const refreshAll = async () => {
|
||||
await Promise.all([refetchDiagnostics(), refetchRawSettings()]);
|
||||
@@ -112,7 +184,6 @@ export function DroidPage() {
|
||||
() => Object.entries(diagnostics?.byok.providerBreakdown ?? {}).sort((a, b) => b[1] - a[1]),
|
||||
[diagnostics?.byok.providerBreakdown]
|
||||
);
|
||||
const rawEditorValidation = validateJsonObject(rawEditorText);
|
||||
|
||||
const renderOverview = () => {
|
||||
if (diagnosticsLoading) {
|
||||
@@ -202,6 +273,23 @@ export function DroidPage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<DroidSettingsQuickControlsCard
|
||||
values={quickSettingsValues}
|
||||
disabled={rawSettingsLoading || !rawEditorParsed.valid}
|
||||
disabledReason={
|
||||
rawEditorParsed.valid ? null : `Quick settings disabled: ${rawEditorParsed.error}`
|
||||
}
|
||||
onEnumSettingChange={(key, value) => {
|
||||
updateSettingsField(key, value);
|
||||
}}
|
||||
onBooleanSettingChange={(key, value) => {
|
||||
updateSettingsField(key, value);
|
||||
}}
|
||||
onNumberSettingChange={(key, value) => {
|
||||
updateSettingsField(key, value);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
@@ -255,6 +343,54 @@ export function DroidPage() {
|
||||
</p>
|
||||
))}
|
||||
<Separator />
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs text-muted-foreground uppercase tracking-wide">
|
||||
Factory Docs
|
||||
</p>
|
||||
<div className="space-y-1.5">
|
||||
{diagnostics.docsReference.links.map((link) => (
|
||||
<a
|
||||
key={link.id}
|
||||
href={link.url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="block rounded-md border px-2.5 py-2 transition-colors hover:bg-muted/50"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="text-xs font-medium">{link.label}</span>
|
||||
<ExternalLink className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
</div>
|
||||
<p className="mt-0.5 text-[11px] text-muted-foreground">{link.description}</p>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs text-muted-foreground uppercase tracking-wide">
|
||||
Provider Fact-Check Docs
|
||||
</p>
|
||||
<div className="space-y-1.5">
|
||||
{diagnostics.docsReference.providerDocs.map((providerDoc) => (
|
||||
<a
|
||||
key={`${providerDoc.provider}-${providerDoc.url}`}
|
||||
href={providerDoc.url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="block rounded-md border px-2.5 py-2 transition-colors hover:bg-muted/50"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="text-xs font-medium">{providerDoc.label}</span>
|
||||
<ExternalLink className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
</div>
|
||||
<p className="mt-0.5 text-[11px] text-muted-foreground">
|
||||
provider: {providerDoc.provider} | format: {providerDoc.apiFormat}
|
||||
</p>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Provider values: {diagnostics.docsReference.providerValues.join(', ')}
|
||||
</p>
|
||||
@@ -358,11 +494,7 @@ export function DroidPage() {
|
||||
!rawEditorValidation.valid
|
||||
}
|
||||
onChange={(next) => {
|
||||
if (next === rawBaseText) {
|
||||
setRawDraftText(null);
|
||||
return;
|
||||
}
|
||||
setRawDraftText(next);
|
||||
setRawEditorDraftText(next);
|
||||
}}
|
||||
onSave={handleSaveRawSettings}
|
||||
onRefresh={refreshAll}
|
||||
|
||||
Reference in New Issue
Block a user