useView
useView subscribes to a session's default view and returns the visible messages, branch navigation, write operations, and pagination state. It is the primary hook for rendering a conversation UI.
By default the hook subscribes to the nearest ClientSessionProvider's default view. Pass session to use a different session's default view, or view to subscribe to a specific view created with useCreateView or session.createView().
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { useView } from '@ably/ai-transport/react';
function Conversation() {
const { messages, send } = useView({ limit: 30 });
return (
<>
{messages.map(({ codecMessageId, message }) => (
<Message key={codecMessageId} message={message} />
))}
<Composer
onSubmit={(text) =>
send({ kind: 'user-message', message: { role: 'user', parts: [{ type: 'text', text }] } })
}
/>
</>
);
}This hook must be used within a ClientSessionProvider unless session or view is supplied explicitly.
Parameters
sessionoptionalClientSession<TInput, TOutput, TProjection, TMessage>viewoptionalClientView<TInput, TMessage>session.limitoptionalNumberlimit, fewer only at the end of history). When provided, auto-loads the first page on mount.skipoptionalBooleantrue, skip all subscriptions and return an empty handle immediately.Returns
messagesCodecMessage<TMessage>[]codecMessageId. Read the domain object from each entry's message field.hasOlderBooleanloadOlder.loadingBooleanloadErrorAbly.ErrorInfo or UndefinedloadOlder call failed. Cleared automatically on the next successful load.loadOlder() => Promise<CodecMessage<TMessage>[]>[] when nothing was revealed: already loading, no view resolved, history exhausted, or the load failed.runOfRunInfoRunInfo for the Run that owns the given codecMessageId.runRunInforuns() => RunInfo[][] when the view isn't resolved.branchSelectionBranchHandleBranchHandle anchored at codecMessageId: the sibling state plus a select(index) verb to navigate it. Always returns a safe handle.send(events, options?) => Promise<ClientRun<TMessage>>codec.createUserMessage (or build the { kind: 'user-message', message } literal) to send a fresh user message. Takes optional regenerate(messageId, options?) => Promise<ClientRun<TMessage>>edit(messageId, inputs, options?) => Promise<ClientRun<TMessage>>Reveal older messages
loadOlder(): Promise<CodecMessage<TMessage>[]>Reveal older messages into the view, resolving to the revealed page (oldest-first). When limit is set on the hook, the first page auto-loads on mount. Call loadOlder again to reveal more. The call is gated so concurrent invocations collapse to one in-flight request; it returns [] when nothing was revealed (already loading, no view resolved, history exhausted, or the load failed).
On failure, loadError is set. On the next successful load, loadError is cleared automatically.
Look up a Run by message id
runOf(codecMessageId: string): RunInfo | undefinedLook up the RunInfo for the Run that owns the given codecMessageId. Returns undefined when the codec-message-id has not been observed.
Look up a Run by id
run(runId: string): RunInfo | undefinedDirect lookup by Run id. Symmetric with runOf for callers that already hold a runId.
Resolve a branch selection
branchSelection(codecMessageId: string): BranchHandle<TMessage>Resolve the BranchHandle anchored at codecMessageId: the sibling state plus a select(index) verb to navigate it. Always returns a safe handle: for branch anchors with N siblings, siblings carries every sibling Run's view of the anchor slot; for non-anchor messages, siblings is [thisMessage] and hasSiblings is false.
Select a sibling
Switch the visible sibling through the BranchHandle the branchSelection call returns, rather than a separate hook method:
1
view.branchSelection(codecMessageId).select(1);index is clamped to [0, siblings.length - 1]. The call is a silent no-op when the message is not a branch anchor, and emits 'update' on the underlying view when the visible output changes.
Send inputs
send(events: TInput | TInput[], options?: SendOptions): Promise<ClientRun<TMessage>>Send an input on the channel and fire a POST. Each TInput carries its own routing metadata (parent, target, codecMessageId).
To send a fresh user message, build a UserMessage input. Use the codec factory (codec.createUserMessage(message)) or build the literal directly: { kind: 'user-message', message }. A send introduces at most one new message: exactly one UserMessage for a fresh send (which mints a new Run), or none for a continuation. The array form exists only to carry the wire-only inputs that resolve a single assistant turn (tool-result, tool-result-error, tool-approval-response); pair it with options.runId to extend a suspended Run. Passing more than one new message rejects with InvalidArgument.
Regenerate an assistant message
regenerate(messageId: string, options?: SendOptions): Promise<ClientRun<TMessage>>Regenerate an assistant message. Creates a new Run that targets the message and threads under its parent user message. Both ids are computed automatically from this view's branch.
Edit a user message
edit(messageId: string, inputs: TInput | TInput[], options?: SendOptions): Promise<ClientRun<TMessage>>Edit a user message. Creates a new Run that forks the target message with the replacement inputs. forkOf, parent, and history are computed automatically from this view's branch.
Example
A conversation component that loads older messages on mount, renders branch navigation when a sibling is available, and offers a regenerate button on each assistant turn.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import { useView } from '@ably/ai-transport/react';
function Conversation() {
const {
messages,
hasOlder,
loadOlder,
branchSelection,
send,
regenerate,
} = useView({ limit: 30 });
return (
<>
{hasOlder && <button onClick={() => loadOlder()}>Load older</button>}
{messages.map(({ codecMessageId, message }) => {
const branch = branchSelection(codecMessageId);
return (
<div key={codecMessageId}>
<Message message={message} />
{branch.hasSiblings && (
<BranchControls
selection={branch}
onSelect={(i) => branch.select(i)}
/>
)}
{message.role === 'assistant' && (
<button onClick={() => regenerate(codecMessageId)}>Regenerate</button>
)}
</div>
);
})}
<Composer
onSubmit={(text) =>
send({ kind: 'user-message', message: { role: 'user', parts: [{ type: 'text', text }] } })
}
/>
</>
);
}