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>viewoptionalView<TInput, TMessage>session.limitoptionalNumberskipoptionalBooleantrue, 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<void>runOfRunInfoRunInfo for the Run that owns the given codecMessageId.runRunInforuns() => RunInfo[][] when the view isn't resolved.branchSelectionBranchSelectionBranchSelection bundle anchored at codecMessageId. Always returns a safe object.selectSibling(codecMessageId, index) => voidcodecMessageId.send(events, options?) => Promise<ActiveRun>TInputs on the channel and fire a POST. Wrap a domain message via codec.createUserMessage (or build the { kind: 'user-message', message } literal) to send a fresh user message. Takes optional regenerate(messageId, options?) => Promise<ActiveRun>edit(messageId, inputs, options?) => Promise<ActiveRun>Reveal older Runs
loadOlder(): Promise<void>Load older messages into the view by revealing more Runs. 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.
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): BranchSelection<TMessage>Resolve the BranchSelection bundle anchored at codecMessageId. Always returns a safe object: 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
selectSibling(codecMessageId: string, index: number): voidSelect a sibling at the branch point anchored at codecMessageId. index is clamped to [0, siblings.length - 1]. Silent no-op when the message is not a branch anchor. Emits 'update' on the underlying view when the visible output changes.
Send inputs
send(events: TInput | TInput[], options?: SendOptions): Promise<ActiveRun>Send one or more TInputs 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 containing at least one UserMessage mints a fresh Run. A send containing only tool-resolution inputs (tool-result, tool-result-error, tool-approval-response) is a continuation; pair it with options.runId to extend a suspended Run.
Regenerate an assistant message
regenerate(messageId: string, options?: SendOptions): Promise<ActiveRun>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<ActiveRun>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
41
import { useView } from '@ably/ai-transport/react';
function Conversation() {
const {
messages,
hasOlder,
loadOlder,
branchSelection,
selectSibling,
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) => selectSibling(codecMessageId, 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 }] } })
}
/>
</>
);
}