Status LEDs, image model selection, backup restore, provider test, monthly versions
- Sidebar: three status LEDs (server, models, MCP) with an overview popover (/api/status) - Images: preview stage, purpose-based model choice and reference images (Nano Banana Pro edit refines the chosen preview) - Settings: restore a stored or uploaded backup (staged, applied on restart) - Provider form: test connection; saving or reloading re-checks the status at once - opencode Zen/Go: send x-opencode-session with a stable id per conversation - Releases are named vYYYYMM-NNNNN; scripts/release.sh numbers, tags, builds and publishes - Fix: backups made in the same second no longer overwrite each other Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
49d404eec7
commit
1f69d63b09
35 files changed
+1156
-88
No files matched your search
@@ -461,6 +461,33 @@ function ProviderEditor({ name, p, reload, status }: { name: string; p: Json; re
|
||||
return savePatches(patches);
|
||||
});
|
||||
|
||||
// Check URL and key as entered, without saving.
|
||||
const [testResult, setTestResult] = useState<{ ok: boolean; text: string }>();
|
||||
const [testing, setTesting] = useState(false);
|
||||
const test = async () => {
|
||||
setTesting(true);
|
||||
setTestResult(undefined);
|
||||
try {
|
||||
const r = await post<{ status: ProviderStatus; models: number }>("/providers/test", {
|
||||
name,
|
||||
kind: form.kind,
|
||||
base_url: form.base_url,
|
||||
api_key: form.api_key,
|
||||
auth_prefix: form.auth_prefix ?? "",
|
||||
});
|
||||
const st = r.status;
|
||||
setTestResult(
|
||||
st.up
|
||||
? { ok: true, text: t("settings.testOk", { ms: st.latency_ms, n: r.models }) }
|
||||
: { ok: false, text: `${st.state === "auth" ? t("settings.providerAuth") : t("settings.providerDown")}: ${st.error ?? ""}` },
|
||||
);
|
||||
} catch (e: any) {
|
||||
setTestResult({ ok: false, text: e.message });
|
||||
} finally {
|
||||
setTesting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const remove = async () => {
|
||||
if (!(await confirmDialog({ message: t("settings.deleteProviderConfirm", { name }), danger: true, confirmLabel: t("common.delete") }))) return;
|
||||
void run(() => savePatches([{ path: ["providers", name], value: null }]));
|
||||
@@ -614,7 +641,15 @@ function ProviderEditor({ name, p, reload, status }: { name: string; p: Json; re
|
||||
</div>
|
||||
{models.length === 0 && <div className="hint" style={{ color: "var(--muted)", fontSize: 12 }}>{t("settings.modelsEmptyHint")}</div>}
|
||||
{view}
|
||||
{testResult && (
|
||||
<div className={`notice ${testResult.ok ? "ok" : "err"}`} style={{ marginTop: 12 }}>
|
||||
{testResult.text}
|
||||
</div>
|
||||
)}
|
||||
<div className="row" style={{ marginTop: 12 }}>
|
||||
<button onClick={test} disabled={testing || !form.base_url}>
|
||||
<Icon name="plug" /> {testing ? t("settings.testing") : t("settings.testConnection")}
|
||||
</button>
|
||||
<button className="primary" onClick={save}>
|
||||
{t("common.save")}
|
||||
</button>
|
||||
@@ -637,9 +672,10 @@ function Providers({ cfg, reload }: { cfg: ConfigResponse; reload: () => void })
|
||||
const providers = cfg.config.providers ?? {};
|
||||
const [status, setStatus] = useState<Record<string, ProviderStatus>>({});
|
||||
const [checking, setChecking] = useState(false);
|
||||
// Saving a provider re-checks it on the server, so refresh whenever the config changes.
|
||||
useEffect(() => {
|
||||
get<Record<string, ProviderStatus>>("/providers/status").then(setStatus).catch(() => {});
|
||||
}, []);
|
||||
}, [cfg]);
|
||||
const recheck = () => {
|
||||
setChecking(true);
|
||||
post<Record<string, ProviderStatus>>("/providers/status")
|
||||
|
||||
Reference in new issue
Block a user