@apteva/ui
v0.1.1
Published
Generic React UI components for Apteva applications, starting with a production-ready agent chat surface.
Readme
@apteva/ui
Generic React components for Apteva applications. The first surface is a complete agent chat UI backed by @apteva/web-sdk.
The package is intentionally independent from the Apteva dashboard and its internal @apteva/ui-kit. It has scoped CSS, works in ordinary React sites, and exposes both a ready-made component and a headless hook.
Install
bun add @apteva/web-sdk @apteva/uiReady-made chat
import { AptevaClient } from "@apteva/web-sdk";
import { AptevaChat } from "@apteva/ui/chat";
import "@apteva/ui/styles.css";
const client = new AptevaClient({
baseURL: "https://agents.example.com",
// Prefer a logged-in session cookie in browser applications.
});
export function SupportChat() {
return (
<AptevaChat
client={client}
agentId={42}
heading="Support"
description="Usually replies in a few seconds"
placeholder="How can we help?"
/>
);
}AptevaChat resumes the agent's most recently updated conversation. If none exists, it creates one. Pass conversationId to open a specific conversation or createIfMissing={false} to prevent automatic creation.
Image attachments are enabled by default. Users can choose, paste, or drag up
to four PNG, JPEG, WebP, or GIF images into the unified composer, preview and
remove them before sending, and reopen durable images from chat history. Set
allowImageAttachments={false} to keep a text-only composer.
Provider
For an application with several Apteva surfaces, provide the client once:
import { AptevaProvider } from "@apteva/ui";
import { AptevaChat } from "@apteva/ui/chat";
<AptevaProvider client={client}>
<AptevaChat agentId={42} />
</AptevaProvider>Headless chat
useAptevaChat provides the same conversation resolution, history merge, live stream, and send behavior without imposing markup:
import { useAptevaChat } from "@apteva/ui/chat";
function CustomChat() {
const chat = useAptevaChat({ agentId: 42 });
return (
<>
{chat.messages.map((message) => (
<p key={message.id}>{message.content}</p>
))}
{chat.streamingMessages.map((message) => (
<p key={message.callId}>{message.text}</p>
))}
<button onClick={() => chat.send("Hello")}>Send</button>
</>
);
}The hook handles several details inherited from the production dashboard:
- REST history, POST responses, and SSE rows are merged by durable message ID.
- A late lower-ID POST response is not lost behind a newer SSE cursor.
- Streaming text is cumulative and replaced per
call_id, not appended. - Provisional streaming bubbles are settled when the durable agent message arrives.
- EventSource reconnects are reflected through
status. - A dashboard-style thinking placeholder appears immediately for an active user turn and is replaced by cumulative SSE response text.
- Durable messages are reconciled in the background as a safety net if an intermediary drops an SSE frame.
- Remounting resumes an existing conversation instead of creating duplicates.
Tool activity
Tool calls are enabled by default. AptevaChat subscribes to the selected
agent's telemetry SSE feed, accepts events only from the conversation's exact
runtime thread, correlates tool.call with tool.result, and restores the same
rows from telemetry after a refresh. Internal delivery and pacing tools stay
hidden.
The generic row shows the agent's reason, tool name, running/success/error state, duration, and expandable arguments/result. Replace individual tools with site-owned renderers when needed:
function SearchActivity({ activity }) {
return <p>{activity.reason || "Searching…"}</p>;
}
<AptevaChat
agentId={42}
toolActivityRenderers={{
crm_search: SearchActivity,
}}
/>Set showToolActivity={false} to disable it. Headless UIs can use
useAptevaToolActivity({ agentId, chatId }) and render the returned
activities themselves.
Rich message components
Agents can attach message.components. Register site-owned renderers by app:name:
function ContactCard({ component }) {
return <a href={`/contacts/${component.props?.id}`}>Open contact</a>;
}
<AptevaChat
agentId={42}
componentRenderers={{
"crm:contact-card": ContactCard,
}}
/>Unknown components are ignored while the message text remains visible.
Styling
Every selector is scoped below .apteva-chat. Override CSS variables on the component or a parent class:
.support-chat {
--apteva-chat-accent: #155eef;
--apteva-chat-accent-text: white;
--apteva-chat-radius: 20px;
--apteva-chat-font: "DM Sans", sans-serif;
height: 640px;
}<AptevaChat className="support-chat" agentId={42} theme="light" />theme accepts "light", "dark", or "auto".
Authentication
The chat API requires an authenticated Apteva user. For browser applications, prefer session-cookie authentication. Do not put a full account API key in a publicly accessible website bundle. A public anonymous widget should use a separate short-lived, chat-scoped token flow.
