@gea-ai/chat-ui
v0.1.260923-alpha.1
Published
Public React renderers for GEA Agent chat messages. The package owns standard message-part behavior, streaming Markdown, request `yaml-render`, and safe fallbacks while leaving visual identity under the Extension's control.
Readme
@gea-ai/chat-ui
Public React renderers for GEA Agent chat messages. The package owns standard
message-part behavior, streaming Markdown, request yaml-render, and safe
fallbacks while leaving visual identity under the Extension's control.
Usage
import {
type AgentChatMessage,
GeaChatUIProvider,
GeaMessages,
} from "@gea-ai/chat-ui";
import "@gea-ai/chat-ui/styles.css";
export function Chat({
addToolOutput,
messages,
resolveAssetUrl,
}: {
addToolOutput: (input: {
messageId?: string;
tool: "request";
toolCallId: string;
output: unknown;
}) => Promise<void>;
messages: AgentChatMessage[];
resolveAssetUrl: (url: string) => string | null;
}) {
return (
<GeaChatUIProvider
actions={{
addToolOutput,
resolveAssetUrl,
}}
locale="zh-CN"
theme={{ accent: "#e46161", radius: "16px" }}
>
<GeaMessages messages={messages} status="streaming" />
</GeaChatUIProvider>
);
}The host owns transport and authorization. The renderer never starts chats or requests protected resources by itself.
GEA's own Web/Desktop app-ui uses the same GeaRequestPart and
GeaYamlRender runtime, injecting host actions, locale, and thin shadcn visual
components through yamlComponents. Request
state binding, streaming previews, skipped/submitted states, and submission
semantics therefore have one implementation across first-party and Extension
hosts.
Custom YAML components receive normalized bindings, canonical button actions,
and field-validation metadata. A visual adapter applies the supplied ARIA/data
attributes, calls onFieldBlur, and renders the supplied errors; it does not
parse YAML, run validation rules, own form state, infer request lifecycle, or
implement submission transport.
Styling
Import the default stylesheet and override semantic variables or stable
data-slot selectors. GeaChatUIProvider also accepts visual component slots
for message frames, avatars, and part renderers. yamlComponents replaces the
visual implementation of catalog components without changing their schema,
state binding, or action semantics.
<GeaChatUIProvider
components={{ AssistantAvatar: BrandAvatar }}
theme={{ accent: "#c64141" }}
yamlComponents={{ Card: BrandRequestCard }}
>
{children}
</GeaChatUIProvider>yaml-render
Interactive request UI is rendered directly from a request tool part. New
Agents call request({ yaml: "root: ..." }); yaml contains the complete
source without a Markdown fence and puts initial values in top-level YAML
state. Invalid specs and legacy request-key inputs fail closed. Text
yaml-render fences remain display-only and are never joined to a request.
While an AI SDK part is input-streaming, the renderer shows a semantic
loading placeholder until the partial YAML has a valid root, then renders the
last valid spec as a locked preview. It remounts the final state once when the
part becomes input-available, performs strict request validation, and
immediately unlocks the active form even if adjacent assistant text continues
streaming.
Submission calls the host-provided
addToolOutput({ messageId, tool: "request", toolCallId, output }) action. The
renderer includes the persisted assistant messageId when it renders from a
message, but it does not own persistence or continuation transport. An
Extension host should submit the result through
app.gea.chats.submitToolOutput(...), then update its local chat state and
resume the stream. Without the action, an active request fails closed. When a
newer message makes an unanswered request inactive, the form remains visible
with locked controls and a Skipped/已跳过 submit label; it does not submit
form state as a user message.
Fields bind checks to the same state path as their value (or checked for
Checkbox). The runtime supports required, requiredIf, email, url,
numeric, length/range, pattern, and cross-field comparison checks. Submit
always validates every field before calling addToolOutput; validateOn
controls earlier change/blur feedback. Empty optional fields skip other
checks, while required Checkbox and Rating fields require an affirmative
selection. Requiredness must be declared with a required or requiredIf
check; words such as "required" in a label have no validation meaning.
name:
type: Input
props:
label: Name
value: { $bindState: /name }
checks:
- type: required
message: Name is required
validateOn: blur
children: []Completed request YAML with unsupported rules, invalid rule arguments, or unsafe state references fails closed. Validation messages are generated UI copy and should use the same language as the conversation.
