Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/backends/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,11 @@ export type AgentEngineSettingField =
| {
key: string;
label: string;
// TODO: Frontend rendering for 'number' fields is not yet implemented (Story #2).
// The catalog definition is registered here; the dashboard will render it once
// numeric field support is added.
type: 'number';
description?: string;
min?: number;
max?: number;
step?: number;
};

export interface AgentEngineSettingsDefinition {
Expand Down
147 changes: 91 additions & 56 deletions web/src/components/settings/engine-settings-fields.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Input } from '@/components/ui/input.js';
import { Label } from '@/components/ui/label.js';
import {
Select,
Expand Down Expand Up @@ -29,11 +30,11 @@ type EngineSettingField =
| {
key: string;
label: string;
// TODO: Frontend rendering for 'number' fields is not yet implemented (Story #2).
// The field type is defined here for type compatibility with the backend catalog;
// the dashboard will render it once numeric field support is added.
type: 'number';
description?: string;
min?: number;
max?: number;
step?: number;
};

interface EngineDefinition {
Expand All @@ -60,6 +61,79 @@ function normalizeValue(
return Object.keys(value).length > 0 ? value : undefined;
}

interface FieldControlProps {
field: EngineSettingField;
rawValue: unknown;
inheritLabel: string;
onUpdate: (key: string, value: unknown) => void;
}

function FieldControl({ field, rawValue, inheritLabel, onUpdate }: FieldControlProps) {
if (field.type === 'select') {
return (
<Select
value={typeof rawValue === 'string' ? rawValue : '_default'}
onValueChange={(next) => onUpdate(field.key, next === '_default' ? undefined : next)}
>
<SelectTrigger className="w-full">
<SelectValue placeholder={inheritLabel} />
</SelectTrigger>
<SelectContent>
<SelectItem value="_default">{inheritLabel}</SelectItem>
{field.options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
);
}

if (field.type === 'number') {
return (
<Input
type="number"
min={field.min}
max={field.max}
step={field.step}
placeholder={inheritLabel}
value={typeof rawValue === 'number' ? rawValue : ''}
onChange={(e) => {
const trimmed = e.target.value.trim();
if (trimmed === '') {
onUpdate(field.key, undefined);
} else {
const parsed = Number(trimmed);
if (!Number.isNaN(parsed)) {
onUpdate(field.key, parsed);
}
}
}}
/>
);
}

// boolean
return (
<Select
value={typeof rawValue === 'boolean' ? (rawValue ? 'true' : 'false') : '_default'}
onValueChange={(next) =>
onUpdate(field.key, next === '_default' ? undefined : next === 'true')
}
>
<SelectTrigger className="w-full">
<SelectValue placeholder={inheritLabel} />
</SelectTrigger>
<SelectContent>
<SelectItem value="_default">{inheritLabel}</SelectItem>
<SelectItem value="true">Enabled</SelectItem>
<SelectItem value="false">Disabled</SelectItem>
</SelectContent>
</Select>
);
}

export function EngineSettingsFields({
engine,
value,
Expand Down Expand Up @@ -105,59 +179,20 @@ export function EngineSettingsFields({
</div>

<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{engine.settings.fields.map((field) => {
// TODO: 'number' field rendering is not yet implemented (Story #2).
if (field.type === 'number') return null;

const rawValue = activeEngineValues[field.key];

return (
<div key={field.key} className="space-y-2">
<Label>{field.label}</Label>
{field.type === 'select' ? (
<Select
value={typeof rawValue === 'string' ? rawValue : '_default'}
onValueChange={(next) =>
updateField(field.key, next === '_default' ? undefined : next)
}
>
<SelectTrigger className="w-full">
<SelectValue placeholder={inheritLabel} />
</SelectTrigger>
<SelectContent>
<SelectItem value="_default">{inheritLabel}</SelectItem>
{field.options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
) : (
<Select
value={
typeof rawValue === 'boolean' ? (rawValue ? 'true' : 'false') : '_default'
}
onValueChange={(next) =>
updateField(field.key, next === '_default' ? undefined : next === 'true')
}
>
<SelectTrigger className="w-full">
<SelectValue placeholder={inheritLabel} />
</SelectTrigger>
<SelectContent>
<SelectItem value="_default">{inheritLabel}</SelectItem>
<SelectItem value="true">Enabled</SelectItem>
<SelectItem value="false">Disabled</SelectItem>
</SelectContent>
</Select>
)}
{field.description && (
<p className="text-xs text-muted-foreground">{field.description}</p>
)}
</div>
);
})}
{engine.settings.fields.map((field) => (
<div key={field.key} className="space-y-2">
<Label>{field.label}</Label>
<FieldControl
field={field}
rawValue={activeEngineValues[field.key]}
inheritLabel={inheritLabel}
onUpdate={updateField}
/>
{field.description && (
<p className="text-xs text-muted-foreground">{field.description}</p>
)}
</div>
))}
</div>
</div>
)}
Expand Down
Loading