swarm-ui: forms share one submit flow, result line and field column
useSubmit holds the idle, sending, done and error state and the request flow that six forms each wrote out; FormResult shows the success line and the problem panel; FormStack is the column the link, claude-login, inference and limits forms each styled by hand. The create-agent, link account, backend-env and agent-input forms use the submit flow; the link, claude-login, inference and limits forms use FormStack.
This commit is contained in:
parent
0e10473e6f
commit
811b23cd6b
9 changed files with 517 additions and 377 deletions
|
|
@ -1,12 +1,4 @@
|
|||
/* <ClaudeLoginDialog> — the code form's column, as
|
||||
`LinkMatrixAccountForm.css` lays its form out. */
|
||||
.claude-login-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.75em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
/* <ClaudeLoginDialog> */
|
||||
.claude-login-ok {
|
||||
margin-block: 1em;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ 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 { FormStack } from "../../ui/form-stack/FormStack.js";
|
||||
import { Dialog } from "../../ui/dialog/Dialog.js";
|
||||
import { ExternalLink } from "../../ui/external-link/ExternalLink.js";
|
||||
import { Hint } from "../../ui/hint/Hint.js";
|
||||
|
|
@ -103,7 +104,7 @@ export function ClaudeLoginDialog({
|
|||
<ExternalLink href={step.url}>the claude login page</ExternalLink>
|
||||
, sign in with this agent's account, and paste the code it shows.
|
||||
</p>
|
||||
<form class="claude-login-form" onSubmit={submit}>
|
||||
<FormStack onSubmit={submit}>
|
||||
<TextField
|
||||
id="claude-login-code"
|
||||
label="code"
|
||||
|
|
@ -118,7 +119,7 @@ export function ClaudeLoginDialog({
|
|||
>
|
||||
{step.step === "submitting" ? "logging in…" : "log in"}
|
||||
</Button>
|
||||
</form>
|
||||
</FormStack>
|
||||
</>
|
||||
)}
|
||||
{step.step === "stored" && (
|
||||
|
|
|
|||
|
|
@ -20,10 +20,3 @@
|
|||
.ui-agent-inference-source {
|
||||
color: var(--muted);
|
||||
}
|
||||
.ui-agent-inference-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.75em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import { Panel } from "../../ui/panel/Panel.js";
|
|||
import { SelectField } from "../../ui/select-field/SelectField.js";
|
||||
import { TextField } from "../../ui/text-field/TextField.js";
|
||||
import { Button } from "../../ui/button/Button.js";
|
||||
import { FormStack } from "../../ui/form-stack/FormStack.js";
|
||||
import "../../ui/badge-row/BadgeRow.css";
|
||||
import "./AgentInference.css";
|
||||
import { MAX_SUBAGENTS, maxSubagentsValue } from "./max-subagents.js";
|
||||
|
|
@ -365,7 +366,7 @@ function InferenceForm({
|
|||
started with, and subagents already running past a lowered cap keep
|
||||
running.
|
||||
</p>
|
||||
<form class="ui-agent-inference-form" onSubmit={save}>
|
||||
<FormStack onSubmit={save}>
|
||||
<InferenceFields draft={draft} onChange={setDraft} />
|
||||
{problem ? (
|
||||
<ApiErrorPanel context="saving failed" problem={problem} />
|
||||
|
|
@ -385,7 +386,7 @@ function InferenceForm({
|
|||
clear the swarm's setting
|
||||
</Button>
|
||||
</ActionRow>
|
||||
</form>
|
||||
</FormStack>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,13 +14,6 @@
|
|||
.ui-agent-limits-muted {
|
||||
color: var(--muted);
|
||||
}
|
||||
.ui-agent-limits-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.5em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
.ui-agent-limits-hint {
|
||||
margin: 0 0 0.5em;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
|||
import { Badge } from "@hive/shared/badge.js";
|
||||
import { ActionRow } from "../../ui/action-row/ActionRow.js";
|
||||
import { Button } from "../../ui/button/Button.js";
|
||||
import { FormStack } from "../../ui/form-stack/FormStack.js";
|
||||
import { Dialog } from "../../ui/dialog/Dialog.js";
|
||||
import { Hint } from "../../ui/hint/Hint.js";
|
||||
import { Panel } from "../../ui/panel/Panel.js";
|
||||
|
|
@ -231,7 +232,7 @@ function LimitsForm({
|
|||
hive. The hive reads a change within about 30 seconds, and a running
|
||||
agent takes it at its next spawn or rebuild.
|
||||
</p>
|
||||
<form class="ui-agent-limits-form" onSubmit={save}>
|
||||
<FormStack onSubmit={save}>
|
||||
<LimitsFields
|
||||
cpu={cpu}
|
||||
memory={memory}
|
||||
|
|
@ -252,7 +253,7 @@ function LimitsForm({
|
|||
clear the swarm's caps
|
||||
</Button>
|
||||
</ActionRow>
|
||||
</form>
|
||||
</FormStack>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import { Badge } from "@hive/shared/badge.js";
|
|||
import { ActionRow } from "../../ui/action-row/ActionRow.js";
|
||||
import { BadgePopover } from "../../ui/badge-popover/BadgePopover.js";
|
||||
import { Button } from "../../ui/button/Button.js";
|
||||
import { FormStack } from "../../ui/form-stack/FormStack.js";
|
||||
import { CheckboxField } from "../../ui/checkbox-field/CheckboxField.js";
|
||||
import { Dialog } from "../../ui/dialog/Dialog.js";
|
||||
import { Panel } from "../../ui/panel/Panel.js";
|
||||
|
|
@ -214,7 +215,7 @@ export function AddForm({
|
|||
: "A name that resolves to a private address stays blocked. The entry adds to the names each hive's own configuration allows."}{" "}
|
||||
The hive reads a change within about 30 seconds.
|
||||
</p>
|
||||
<form class="ui-agent-limits-form" onSubmit={(e) => void save(e)}>
|
||||
<FormStack onSubmit={(e) => void save(e)}>
|
||||
<TextField
|
||||
id={deny ? "denied-domain" : "allowed-domain"}
|
||||
label="domain"
|
||||
|
|
@ -242,7 +243,7 @@ export function AddForm({
|
|||
{busy ? "adding…" : "add"}
|
||||
</Button>
|
||||
</ActionRow>
|
||||
</form>
|
||||
</FormStack>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,5 +13,4 @@
|
|||
}
|
||||
.ui-agent-input-sent {
|
||||
margin-block-start: 0.25rem;
|
||||
color: var(--green);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,42 +5,27 @@
|
|||
// sender from the gateway, not from this request).
|
||||
import { useState } from "preact/hooks";
|
||||
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
||||
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
||||
import { TextField } from "../../ui/text-field/TextField.js";
|
||||
import { Button } from "../../ui/button/Button.js";
|
||||
import { Hint } from "../../ui/hint/Hint.js";
|
||||
import { useSubmit } from "../../ui/submit/useSubmit.js";
|
||||
import "./AgentInputForm.css";
|
||||
|
||||
type SubmitState =
|
||||
| { status: "idle" }
|
||||
| { status: "submitting" }
|
||||
| { status: "sent" }
|
||||
| { status: "error"; problem: ProblemDetails };
|
||||
|
||||
export function AgentInputForm({ agentName }: { agentName: string }) {
|
||||
const [body, setBody] = useState("");
|
||||
const [result, setResult] = useState<SubmitState>({ status: "idle" });
|
||||
const { state, submitting, run } = useSubmit();
|
||||
|
||||
async function submit(e: Event) {
|
||||
function submit(e: Event) {
|
||||
e.preventDefault();
|
||||
setResult({ status: "submitting" });
|
||||
try {
|
||||
const r = await fetch(
|
||||
`/api/agents/${encodeURIComponent(agentName)}/input`,
|
||||
{
|
||||
void run(
|
||||
() =>
|
||||
fetch(`/api/agents/${encodeURIComponent(agentName)}/input`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ body }),
|
||||
},
|
||||
);
|
||||
if (!r.ok) {
|
||||
setResult({ status: "error", problem: await readApiError(r) });
|
||||
return;
|
||||
}
|
||||
setResult({ status: "sent" });
|
||||
setBody("");
|
||||
} catch (err) {
|
||||
setResult({ status: "error", problem: { detail: String(err) } });
|
||||
}
|
||||
}),
|
||||
{ onDone: () => setBody("") },
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -53,17 +38,17 @@ export function AgentInputForm({ agentName }: { agentName: string }) {
|
|||
required
|
||||
onInput={setBody}
|
||||
/>
|
||||
<Button
|
||||
variant="primary"
|
||||
type="submit"
|
||||
disabled={result.status === "submitting"}
|
||||
>
|
||||
{result.status === "submitting" ? "sending…" : "send"}
|
||||
<Button variant="primary" type="submit" disabled={submitting}>
|
||||
{submitting ? "sending…" : "send"}
|
||||
</Button>
|
||||
</form>
|
||||
{result.status === "sent" && <p class="ui-agent-input-sent">sent</p>}
|
||||
{result.status === "error" && (
|
||||
<ApiErrorPanel context="failed to send" problem={result.problem} />
|
||||
{state.status === "done" && (
|
||||
<Hint tone="ok" class="ui-agent-input-sent">
|
||||
sent
|
||||
</Hint>
|
||||
)}
|
||||
{state.status === "error" && (
|
||||
<ApiErrorPanel context="failed to send" problem={state.problem} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue