React UI
Human input
Render tool approvals and tool questions from Anvia chat state.
Human-input primitives render approval and question state from useChat(...). They do not create
the review workflow by themselves. Your stream must emit human-input events, and your app must
provide decision or answer routes.
const chat = useChat({
endpoint: "http://localhost:8787/api/chat",
humanInput: {
endpoint: "http://localhost:8787/api/review",
},
});
The default human-input handlers post to:
http://localhost:8787/api/review/approvals/:approvalId/decisionhttp://localhost:8787/api/review/questions/:questionId/answer
For the route contract, see Human review end to end.
Panel
HumanInput.Panel renders when pending approvals or questions exist. Use HumanInput.Status when
the surface should show a pending count.
import { HumanInput } from "@anvia/react-ui";
export function ReviewPanel() {
return (
<HumanInput.Panel className="review-panel">
<HumanInput.Status />
<HumanInput.Approvals className="approvals" />
<HumanInput.Questions className="questions" />
</HumanInput.Panel>
);
}.review-panel {
display: grid;
gap: 14px;
border: 1px solid #3f3f46;
border-radius: 12px;
background: #18181b;
padding: 14px;
}
.approvals,
.questions {
display: grid;
gap: 10px;
}
[data-anvia-human-input-status][data-pending="0"] {
display: none;
}import { HumanInput } from "@anvia/react-ui";
export function ReviewPanel() {
return (
<HumanInput.Panel className="grid gap-3.5 rounded-xl border border-zinc-700 bg-zinc-900 p-3.5">
<HumanInput.Status className="data-[pending=0]:hidden" />
<HumanInput.Approvals className="grid gap-2.5" />
<HumanInput.Questions className="grid gap-2.5" />
</HumanInput.Panel>
);
}If your application wants a persistent review lane, render the collections directly with
keepMounted.
<aside className="review-lane">
<HumanInput.Status />
<HumanInput.Approvals keepMounted />
<HumanInput.Questions keepMounted />
</aside>
Approvals
HumanInput.Approvals reads approvals from the chat controller. Each approval gets its own context
so HumanInput.Approve and HumanInput.Reject know which request to resolve.
import { HumanInput } from "@anvia/react-ui";
export function Approvals() {
return (
<HumanInput.Approvals className="approvals">
{(approval) => (
<HumanInput.Approval className="approval">
<header className="approval-header">
<strong>{approval.toolName}</strong>
<span>{approval.status}</span>
</header>
{approval.args !== undefined ? <pre>{approval.args}</pre> : null}
<HumanInput.ApprovalReason placeholder="Why approve or reject?" />
<div className="approval-actions">
<HumanInput.Reject>Reject</HumanInput.Reject>
<HumanInput.Approve>Approve</HumanInput.Approve>
</div>
</HumanInput.Approval>
)}
</HumanInput.Approvals>
);
}.approval {
display: grid;
gap: 10px;
border: 1px solid #3f3f46;
border-radius: 10px;
padding: 12px;
}
.approval-header,
.approval-actions {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.approval textarea {
min-height: 76px;
resize: vertical;
}import { HumanInput } from "@anvia/react-ui";
export function Approvals() {
return (
<HumanInput.Approvals className="grid gap-2.5">
{(approval) => (
<HumanInput.Approval className="grid gap-2.5 rounded-lg border border-zinc-700 p-3">
<header className="flex items-center justify-between gap-2.5">
<strong>{approval.toolName}</strong>
<span className="text-sm text-zinc-400">{approval.status}</span>
</header>
{approval.args !== undefined ? <pre>{approval.args}</pre> : null}
<HumanInput.ApprovalReason className="min-h-20 resize-y rounded-lg border border-zinc-700 bg-zinc-950 p-2" placeholder="Why approve or reject?" />
<div className="flex justify-end gap-2">
<HumanInput.Reject>Reject</HumanInput.Reject>
<HumanInput.Approve>Approve</HumanInput.Approve>
</div>
</HumanInput.Approval>
)}
</HumanInput.Approvals>
);
}Use filter="all" when a review surface should show completed approvals too. The default filter is
"pending".
<HumanInput.Approvals filter="all" />
Approval buttons call chat.approveTool(...) and chat.rejectTool(...) through context. Use
HumanInput.ApprovalReason when the decision should include an audit note.
Questions
HumanInput.Questions renders pending tool questions. A question can contain one or more prompts,
and each prompt can expose multiple choices.
<HumanInput.Questions>
<HumanInput.Question className="question">
<HumanInput.QuestionPrompt />
<HumanInput.QuestionSubmit>Answer</HumanInput.QuestionSubmit>
</HumanInput.Question>
</HumanInput.Questions>
When a prompt has no choices, the default prompt renderer shows HumanInput.QuestionTextAnswer.
Use it directly when a custom question layout needs free-text answers.
import { HumanInput, useQuestionPrompt } from "@anvia/react-ui";
function QuestionPromptFields() {
const { prompt } = useQuestionPrompt();
return (
<div className="question-prompt">
<label>{prompt.question}</label>
{prompt.choices.length > 0 ? (
prompt.choices.map((choice) => (
<HumanInput.QuestionChoice key={choice.value} value={choice.value}>
{choice.label}
</HumanInput.QuestionChoice>
))
) : (
<HumanInput.QuestionTextAnswer placeholder="Type an answer..." />
)}
</div>
);
}
export function CustomQuestionPrompt() {
return (
<HumanInput.QuestionPrompt>
<QuestionPromptFields />
</HumanInput.QuestionPrompt>
);
}
For a combined tool and review flow, see Tool and human input.
