// — 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"; import { Button } from "../../ui/button/Button.js"; import { Disclosure } from "../../ui/disclosure/Disclosure.js"; import { SelectField } from "../../ui/select-field/SelectField.js"; import { TextField } from "../../ui/text-field/TextField.js"; import { EFFORTS, MAX_REVIEWS, MIN_REVIEWS, changed, draftOf, settingsProblem, settingsSummary, 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)" }, ]; // The empty effort option is the runtime's own default: claude's on the API // key runtime, opencode's on ACP. function effortOptions(runtime: Runtime) { return [ { value: "", label: runtime === "acp" ? "opencode's default" : "claude's default", }, ...EFFORTS.map((e) => ({ value: e, label: e })), ]; } export function ClassifierSettings() { // The settings as the controller holds them; null until the first read. const [saved, setSaved] = useState(null); const [draft, setDraft] = useState(null); const [loadError, setLoadError] = useState(null); const [saveError, setSaveError] = useState(null); const [saving, setSaving] = useState(false); const [justSaved, setJustSaved] = useState(false); // Folded until wanted: the settings are changed now and then, and the // folded header says what they are. const [open, setOpen] = useState(false); useEffect(() => { let cancelled = false; void (async () => { try { const res = await fetch(SETTINGS); if (cancelled) return; if (!res.ok) { setLoadError(await readApiError(res)); return; } const body = (await res.json()) as ClassifierSettingsBody; if (cancelled) return; setSaved(body); setDraft(draftOf(body)); } catch (e: unknown) { if (!cancelled) setLoadError({ detail: String(e) }); } })(); return () => { cancelled = true; }; }, []); function edit(next: SettingsDraft) { setDraft(next); setJustSaved(false); } async function save(e: Event) { e.preventDefault(); if (draft === null || settingsProblem(draft) !== null) return; setSaving(true); setSaveError(null); setJustSaved(false); try { const res = await fetch(SETTINGS, { method: "PUT", headers: { "content-type": "application/json" }, body: JSON.stringify(toSettings(draft)), }); if (!res.ok) { // The form stays as typed, so the operator can fix it and save again. setSaveError(await readApiError(res)); return; } const body = (await res.json()) as ClassifierSettingsBody; setSaved(body); setDraft(draftOf(body)); setJustSaved(true); } catch (err: unknown) { setSaveError({ detail: String(err) }); } finally { setSaving(false); } } const problem = draft === null ? null : settingsProblem(draft); return (

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.

{loadError ? ( ) : null} {draft ? (
void save(e)}>
edit({ ...draft, runtime: runtime as Runtime }) } /> {draft.runtime === "api-key" ? ( edit({ ...draft, model })} /> ) : null} edit({ ...draft, effort })} /> edit({ ...draft, max })} />
{draft.runtime === "acp" ? (
edit({ ...draft, acpUrl })} /> edit({ ...draft, acpModel })} /> edit({ ...draft, priceInput })} /> edit({ ...draft, priceOutput })} /> edit({ ...draft, priceCacheRead })} /> edit({ ...draft, priceCacheWrite }) } />
) : null} {saveError ? ( ) : null}
{justSaved ? ( Saved. ) : null}
{problem !== null ? (

{problem}

) : null} ) : null}
); }