Docs

React UI

Composer controls

Add app-owned model selectors, reasoning controls, and custom submit behavior to @anvia/react-ui composers.

Model selection, reasoning effort, temperature, provider choice, and similar controls belong to application state. Render those controls inside Composer.Root when they are part of the prompt bar, then include their values in useChat({ createRequest }).

import type { Message } from "@anvia/core/completion";
import { useChat } from "@anvia/react";
import { ChatProvider, Composer } from "@anvia/react-ui";
import { useState } from "react";

type ReasoningEffort = "low" | "medium" | "high";

type ChatRequest = {
  messages: Message[];
  stream: true;
  model: string;
  reasoning: { effort: ReasoningEffort };
};

export function ConfigurableComposer() {
  const [model, setModel] = useState("gpt-5");
  const [reasoningEffort, setReasoningEffort] = useState<ReasoningEffort>("medium");
  const chat = useChat<ChatRequest>({
    endpoint: "http://localhost:8787/api/chat",
    createRequest: ({ coreMessages }) => ({
      messages: coreMessages,
      stream: true,
      model,
      reasoning: { effort: reasoningEffort },
    }),
  });

  return (
    <ChatProvider controller={chat}>
      <Composer.Root
        className="composer"
        submitMessage={async ({ input, attachments, chat: chatController, clear }) => {
          await chatController.sendMessage({
            text: input,
            attachments,
            metadata: { model, reasoningEffort },
          });
          clear();
        }}
      >
        <select
          aria-label="Model"
          disabled={chat.status === "streaming"}
          value={model}
          onChange={(event) => setModel(event.currentTarget.value)}
        >
          <option value="gpt-5">GPT-5</option>
          <option value="gpt-5-mini">GPT-5 mini</option>
        </select>
        <select
          aria-label="Reasoning effort"
          disabled={chat.status === "streaming"}
          value={reasoningEffort}
          onChange={(event) => setReasoningEffort(event.currentTarget.value as ReasoningEffort)}
        >
          <option value="low">Low</option>
          <option value="medium">Medium</option>
          <option value="high">High</option>
        </select>
        <Composer.Attachments className="attachments" />
        <Composer.AddAttachment accept="image/*,.pdf" multiple>
          Attach
        </Composer.AddAttachment>
        <Composer.Input minRows={1} maxRows={6} placeholder="Message Anvia..." />
        <Composer.Stop>Stop</Composer.Stop>
        <Composer.Submit>Send</Composer.Submit>
      </Composer.Root>
    </ChatProvider>
  );
}

Request options

Use createRequest for request-level options. The example sends model and reasoning next to the converted core messages, so the server route can read them from the request body alongside messages.

For the full request-body contract and metadata tradeoffs, see Request shape.

Custom submit

Use Composer.Root’s submitMessage when the composer should send a custom message payload, such as metadata that records the selected model or reasoning effort. A custom submit handler replaces the default send behavior, so call clear() after the message is accepted.