Docs

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/decision
  • http://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>
  );
}

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>
  );
}

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.