feat: setup wizard + settings
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
dd6b880f49
commit
1c3f222f9d
@@ -0,0 +1,43 @@
|
||||
"use client";
|
||||
import { useActionState } from "react";
|
||||
import type { SettingsView } from "@/lib/settings";
|
||||
|
||||
type Result = { ok?: boolean; error?: string } | null;
|
||||
|
||||
export function SettingsForm({ action, view, step, account }: {
|
||||
action: (state: Result, fd: FormData) => Promise<Result>;
|
||||
view: SettingsView; step?: string; account?: boolean;
|
||||
}) {
|
||||
const [state, formAction, pending] = useActionState<Result, FormData>(action, null);
|
||||
const F = (name: string, label: string, value: string, help: string, type = "text") => (
|
||||
<div key={name} style={{ display: "grid", gap: "var(--qai-space-xs)" }}>
|
||||
<label className="label" htmlFor={name}>{label}</label>
|
||||
<input className="field" id={name} name={name} type={type} defaultValue={value} />
|
||||
<span style={{ fontSize: "var(--qai-text-xs)", color: "var(--qai-ink-muted)" }}>{help}</span>
|
||||
</div>);
|
||||
const Group = ({ title, children }: { title: string; children: React.ReactNode }) => (
|
||||
<fieldset style={{ border: 0, padding: 0, margin: 0, display: "grid", gap: "var(--qai-space-md)" }}>
|
||||
<legend className="label">{title}</legend>{children}</fieldset>);
|
||||
return <form action={formAction} className="card" style={{ display: "grid", gap: "var(--qai-space-l)" }}>
|
||||
{step && <p className="label">{step}</p>}
|
||||
<Group title="Analyst model — OpenAI-compatible">
|
||||
{F("analyst_base_url", "Base URL", view.analyst_base_url, "e.g. https://api.openai.com/v1")}
|
||||
{F("analyst_key", "API key", "", view.has_analyst
|
||||
? `Saved ${view.analyst_key_masked} — leave blank to keep it` : "Encrypted with APP_SECRET before it hits disk")}
|
||||
{F("analyst_model", "Model name", view.analyst_model, "e.g. gpt-4o-mini, llama-3.3-70b")}
|
||||
{F("analyst_extra", "Extra params (JSON)", view.analyst_extra, 'Merged into the request body, e.g. {"temperature":0.2}')}
|
||||
</Group>
|
||||
<Group title="Jev — TypeSafe System One">
|
||||
{F("jev_key", "TypeSafe API key", "", view.has_jev
|
||||
? `Saved ${view.jev_key_masked} — leave blank to keep it` : "console.typesafe.ai/keys")}
|
||||
{F("jev_model", "Jev model", view.jev_model, "jev-latest unless you know better")}
|
||||
</Group>
|
||||
{account && <Group title="Account">
|
||||
{F("email", "Email", "", "Used to sign in", "email")}
|
||||
{F("password", "New password", "", "Leave blank to keep the current one", "password")}
|
||||
</Group>}
|
||||
{state && !state.ok && <p role="alert" style={{ color: "var(--qai-danger)" }}>{state.error}</p>}
|
||||
{state?.ok && <p role="status" style={{ color: "var(--qai-accent)" }}>Saved.</p>}
|
||||
<button className="btn btn-primary" disabled={pending}>Save</button>
|
||||
</form>;
|
||||
}
|
||||
Reference in New Issue
Block a user