feat(webui): registry editor context-window/output/temperature fields + advanced section

This commit is contained in:
m4
2026-08-11 15:07:53 +08:00
parent 94e64cc0c2
commit eab9178743
+106 -65
View File
@@ -245,6 +245,7 @@ function ModelEditor({
const [expanded, setExpanded] = useState( const [expanded, setExpanded] = useState(
() => availabilityFor(data, ref) === null () => availabilityFor(data, ref) === null
); );
const [showAdvanced, setShowAdvanced] = useState(false);
const capability = (key: "tools" | "vision" | "structured_output") => { const capability = (key: "tools" | "vision" | "structured_output") => {
const protocolAllows = spec?.protocol_capabilities[key] !== false; const protocolAllows = spec?.protocol_capabilities[key] !== false;
return ( return (
@@ -350,6 +351,52 @@ function ModelEditor({
</Field> </Field>
</div> </div>
<div className="grid grid-cols-2 gap-2 sm:grid-cols-3"> <div className="grid grid-cols-2 gap-2 sm:grid-cols-3">
<NumberField
label="Context window"
value={runtime.context_window_tokens}
nullable
onChange={(value) => setRuntime({ context_window_tokens: value })}
/>
<NumberField
label="Max output tokens"
value={runtime.max_output_tokens}
rule={rule("max_output_tokens")}
onChange={(value) =>
setRuntime({ max_output_tokens: value ?? runtime.max_output_tokens })
}
/>
<NumberField
label={rule("temperature")?.supported === false ? "Temperature (unsupported)" : "Temperature"}
value={runtime.temperature}
nullable
rule={rule("temperature")}
onChange={(value) => setRuntime({ temperature: value })}
/>
</div>
<div className="flex items-center gap-4">
<span className="text-xs font-medium text-muted-foreground">
Capabilities
</span>
{capability("tools")}
{capability("vision")}
{capability("structured_output")}
</div>
<div>
<button
type="button"
aria-expanded={showAdvanced}
className="flex items-center gap-1 text-xs font-medium text-muted-foreground hover:text-foreground"
onClick={() => setShowAdvanced((current) => !current)}
>
{showAdvanced ? (
<ChevronDown className="size-3.5" aria-hidden="true" />
) : (
<ChevronRight className="size-3.5" aria-hidden="true" />
)}
Advanced
</button>
{showAdvanced && (
<div className="mt-2 grid grid-cols-2 gap-2 sm:grid-cols-3">
<Field label="Limit mode"> <Field label="Limit mode">
<select <select
className={inputClass} className={inputClass}
@@ -364,26 +411,12 @@ function ModelEditor({
<option value="input_only">input_only</option> <option value="input_only">input_only</option>
</select> </select>
</Field> </Field>
<NumberField
label="Context window"
value={runtime.context_window_tokens}
nullable
onChange={(value) => setRuntime({ context_window_tokens: value })}
/>
<NumberField <NumberField
label="Max input tokens" label="Max input tokens"
value={runtime.max_input_tokens} value={runtime.max_input_tokens}
nullable nullable
onChange={(value) => setRuntime({ max_input_tokens: value })} onChange={(value) => setRuntime({ max_input_tokens: value })}
/> />
<NumberField
label="Max output tokens"
value={runtime.max_output_tokens}
rule={rule("max_output_tokens")}
onChange={(value) =>
setRuntime({ max_output_tokens: value ?? runtime.max_output_tokens })
}
/>
<NumberField <NumberField
label="Min effective input" label="Min effective input"
value={runtime.min_effective_input_tokens} value={runtime.min_effective_input_tokens}
@@ -448,13 +481,6 @@ function ModelEditor({
<option value="tested_contract">tested contract</option> <option value="tested_contract">tested contract</option>
</select> </select>
</Field> </Field>
<NumberField
label={rule("temperature")?.supported === false ? "Temperature (unsupported)" : "Temperature"}
value={runtime.temperature}
nullable
rule={rule("temperature")}
onChange={(value) => setRuntime({ temperature: value })}
/>
<NumberField <NumberField
label="Top p" label="Top p"
value={runtime.top_p} value={runtime.top_p}
@@ -493,13 +519,7 @@ function ModelEditor({
</select> </select>
</Field> </Field>
</div> </div>
<div className="flex items-center gap-4"> )}
<span className="text-xs font-medium text-muted-foreground">
Capabilities
</span>
{capability("tools")}
{capability("vision")}
{capability("structured_output")}
</div> </div>
</> </>
)} )}
@@ -562,6 +582,8 @@ function ProviderEditor({
const spec = adapterSpecFor(data.adapter_specs, provider.adapter); const spec = adapterSpecFor(data.adapter_specs, provider.adapter);
const authModes = supportedAuthModes(spec); const authModes = supportedAuthModes(spec);
const credentialStatus = credentialStatusFor(data, provider.auth.credential_id); const credentialStatus = credentialStatusFor(data, provider.auth.credential_id);
const [showSecret, setShowSecret] = useState(false);
const [showAdvanced, setShowAdvanced] = useState(false);
const setRuntime = (patch: Partial<ProviderConfig["runtime"]>) => const setRuntime = (patch: Partial<ProviderConfig["runtime"]>) =>
onChange({ ...provider, runtime: { ...provider.runtime, ...patch } }); onChange({ ...provider, runtime: { ...provider.runtime, ...patch } });
@@ -690,50 +712,67 @@ function ProviderEditor({
</select> </select>
</Field> </Field>
{provider.auth.mode !== "none" && ( {provider.auth.mode !== "none" && (
<> <Field
<Field label="API Key"
label="Credential id" hint={
hint={ stagedCredential
credentialStatus?.configured ? "staged — will be saved with the registry"
? `configured${credentialStatus.hint ? ` (${credentialStatus.hint})` : ""}` : credentialStatus?.configured
: "not configured" ? `configured${credentialStatus.hint ? ` (${credentialStatus.hint})` : ""} — leave blank to keep`
} : "paste your API key"
> }
>
<div className="relative">
<input <input
type="text" type={showSecret ? "text" : "password"}
className={inputClass} className={`${inputClass} pr-8`}
value={provider.auth.credential_id ?? ""}
onChange={(event) =>
onChange({
...provider,
auth: { ...provider.auth, credential_id: event.target.value || null },
})
}
/>
</Field>
<Field
label="Secret (write-once)"
hint={
stagedCredential
? "staged — saved with the registry"
: "leave blank to keep the current secret"
}
>
<input
type="password"
className={inputClass}
value={stagedCredential ?? ""} value={stagedCredential ?? ""}
autoComplete="new-password" autoComplete="new-password"
onChange={(event) => placeholder={
provider.auth.credential_id && credentialStatus?.configured
onCredentialStage(provider.auth.credential_id, event.target.value) ? (credentialStatus.hint ?? "configured")
: "sk-..."
} }
onChange={(event) => {
const credentialId =
provider.auth.credential_id ?? `${provider.id}-credential`;
if (!provider.auth.credential_id) {
onChange({
...provider,
auth: { ...provider.auth, credential_id: credentialId },
});
}
onCredentialStage(credentialId, event.target.value);
}}
/> />
</Field> <button
</> type="button"
aria-label={showSecret ? "Hide API key" : "Show API key"}
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
onClick={() => setShowSecret((current) => !current)}
>
{showSecret ? <EyeOff size={16} /> : <Eye size={16} />}
</button>
</div>
</Field>
)} )}
</div> </div>
<div className="grid grid-cols-2 gap-2 sm:grid-cols-3"> <div>
<button
type="button"
aria-expanded={showAdvanced}
className="flex items-center gap-1 text-xs font-medium text-muted-foreground hover:text-foreground"
onClick={() => setShowAdvanced((current) => !current)}
>
{showAdvanced ? (
<ChevronDown className="size-3.5" aria-hidden="true" />
) : (
<ChevronRight className="size-3.5" aria-hidden="true" />
)}
Advanced
</button>
{showAdvanced && (
<div className="mt-2 grid grid-cols-2 gap-2 sm:grid-cols-3">
<NumberField <NumberField
label="Timeout (s)" label="Timeout (s)"
value={provider.runtime.timeout_seconds} value={provider.runtime.timeout_seconds}
@@ -779,6 +818,8 @@ function ProviderEditor({
))} ))}
</select> </select>
</Field> </Field>
</div>
)}
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">