swarm-ui: choose the review classifier's runtime on the settings page
The classifier settings page showed model and effort only, so the API-key or ACP runtime could be switched only through a raw API call. It now has a runtime select, and under ACP the provider's URL, model and prices. A PUT carries the provider only on the ACP runtime, so the stored record stays for a switch back. Closes #5249
This commit is contained in:
parent
739c35757a
commit
a04096ea19
4 changed files with 213 additions and 35 deletions
|
|
@ -1,9 +1,11 @@
|
|||
// <ClassifierSettings> — the review classifier's model, effort and how many
|
||||
// requested reviews run at once, shown and saved here
|
||||
// (`GET`/`PUT /api/review-classifier/settings`). A PUT replaces the stored
|
||||
// settings whole, so the form sends all three fields. A refused save shows the
|
||||
// route's own reason and leaves the form as typed. There is no API key field:
|
||||
// admins set the key in the bao UI.
|
||||
// <ClassifierSettings> — the review classifier's runtime, model, effort and
|
||||
// how many requested reviews run at once, shown and saved here
|
||||
// (`GET`/`PUT /api/review-classifier/settings`). A PUT changes only the
|
||||
// fields it carries: the form sends the runtime, model, effort and limit, and
|
||||
// the ACP provider only while the runtime is ACP, so the stored provider
|
||||
// survives a switch back. A refused save shows the route's own reason and
|
||||
// leaves the form as typed. There is no API key field: admins set each
|
||||
// runtime's key in the bao UI.
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
||||
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
||||
|
|
@ -20,12 +22,18 @@ import {
|
|||
settingsProblem,
|
||||
toSettings,
|
||||
type ClassifierSettingsBody,
|
||||
type Runtime,
|
||||
type SettingsDraft,
|
||||
} from "./SettingsTypes.js";
|
||||
import "./ClassifierSettings.css";
|
||||
|
||||
const SETTINGS = "/api/review-classifier/settings";
|
||||
|
||||
const RUNTIME_OPTIONS = [
|
||||
{ value: "api-key", label: "API key (claude)" },
|
||||
{ value: "acp", label: "ACP (opencode)" },
|
||||
];
|
||||
|
||||
const EFFORT_OPTIONS = [
|
||||
{ value: "", label: "claude's default" },
|
||||
...EFFORTS.map((e) => ({ value: e, label: e })),
|
||||
|
|
@ -101,10 +109,12 @@ export function ClassifierSettings() {
|
|||
return (
|
||||
<Panel title="classifier settings" icon="⚙️">
|
||||
<p>
|
||||
The model and effort the review classifier runs with, and how many
|
||||
requested reviews run at once. A change takes effect from the next run,
|
||||
with no restart. Try-outs don't count toward the limit. The API key is
|
||||
set in the bao UI, not here.
|
||||
The runtime, model and effort the review classifier runs with, and how
|
||||
many requested reviews run at once. The API key runtime runs claude on
|
||||
the Anthropic key; the ACP runtime runs opencode on the provider given
|
||||
here. A change takes effect from the next run, with no restart. Try-outs
|
||||
don't count toward the limit. Each runtime's key is set in the bao UI,
|
||||
not here.
|
||||
</p>
|
||||
{loadError ? (
|
||||
<ApiErrorPanel
|
||||
|
|
@ -115,13 +125,24 @@ export function ClassifierSettings() {
|
|||
{draft ? (
|
||||
<form class="ui-classifier-settings" onSubmit={(e) => void save(e)}>
|
||||
<div class="ui-classifier-settings-fields">
|
||||
<TextField
|
||||
id="classifier-model"
|
||||
label="model"
|
||||
value={draft.model}
|
||||
placeholder="haiku"
|
||||
onInput={(model) => edit({ ...draft, model })}
|
||||
<SelectField
|
||||
id="classifier-runtime"
|
||||
label="runtime"
|
||||
value={draft.runtime}
|
||||
options={RUNTIME_OPTIONS}
|
||||
onChange={(runtime) =>
|
||||
edit({ ...draft, runtime: runtime as Runtime })
|
||||
}
|
||||
/>
|
||||
{draft.runtime === "api-key" ? (
|
||||
<TextField
|
||||
id="classifier-model"
|
||||
label="model"
|
||||
value={draft.model}
|
||||
placeholder="haiku"
|
||||
onInput={(model) => edit({ ...draft, model })}
|
||||
/>
|
||||
) : null}
|
||||
<SelectField
|
||||
id="classifier-effort"
|
||||
label="effort"
|
||||
|
|
@ -137,6 +158,50 @@ export function ClassifierSettings() {
|
|||
onInput={(max) => edit({ ...draft, max })}
|
||||
/>
|
||||
</div>
|
||||
{draft.runtime === "acp" ? (
|
||||
<div class="ui-classifier-settings-fields">
|
||||
<TextField
|
||||
id="classifier-acp-url"
|
||||
label="provider URL"
|
||||
value={draft.acpUrl}
|
||||
placeholder="https://llm.example/v1"
|
||||
onInput={(acpUrl) => edit({ ...draft, acpUrl })}
|
||||
/>
|
||||
<TextField
|
||||
id="classifier-acp-model"
|
||||
label="provider model"
|
||||
value={draft.acpModel}
|
||||
placeholder="Qwen/Qwen3-35B"
|
||||
onInput={(acpModel) => edit({ ...draft, acpModel })}
|
||||
/>
|
||||
<TextField
|
||||
id="classifier-acp-price-input"
|
||||
label="input price (USD per million tokens)"
|
||||
value={draft.priceInput}
|
||||
onInput={(priceInput) => edit({ ...draft, priceInput })}
|
||||
/>
|
||||
<TextField
|
||||
id="classifier-acp-price-output"
|
||||
label="output price (USD per million tokens)"
|
||||
value={draft.priceOutput}
|
||||
onInput={(priceOutput) => edit({ ...draft, priceOutput })}
|
||||
/>
|
||||
<TextField
|
||||
id="classifier-acp-price-cache-read"
|
||||
label="cache read price (optional)"
|
||||
value={draft.priceCacheRead}
|
||||
onInput={(priceCacheRead) => edit({ ...draft, priceCacheRead })}
|
||||
/>
|
||||
<TextField
|
||||
id="classifier-acp-price-cache-write"
|
||||
label="cache write price (optional)"
|
||||
value={draft.priceCacheWrite}
|
||||
onInput={(priceCacheWrite) =>
|
||||
edit({ ...draft, priceCacheWrite })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{saveError ? (
|
||||
<ApiErrorPanel
|
||||
context="the settings were not saved"
|
||||
|
|
|
|||
Loading…
Reference in a new issue