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().

JavaScript

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>
A client session whose default view to subscribe to. Defaults to the nearest provider.
viewoptionalView<TInput, TMessage>
A specific view to subscribe to directly. Takes priority over session.
limitoptionalNumber
Maximum number of older Runs to reveal per page. When provided, auto-loads the first page on mount.
skipoptionalBoolean
When true, skip all subscriptions and return an empty handle immediately.

Returns

messagesCodecMessage<TMessage>[]
The visible messages along the selected branch, each paired with its codecMessageId. Read the domain object from each entry's message field.
hasOlderBoolean
Whether there are older Runs that can be revealed via loadOlder.
loadingBoolean
Whether a page load is currently in progress.
loadErrorAbly.ErrorInfo or Undefined
Set when the most recent loadOlder call failed. Cleared automatically on the next successful load.
loadOlder() => Promise<void>
Reveal older Runs. No-op if already loading.
runOfRunInfo
Look up the RunInfo for the Run that owns the given codecMessageId.
runRunInfo
Direct lookup by Run id.
runs() => RunInfo[]
Snapshot of the visible Runs along the selected branch, in chronological order. Returns [] when the view isn't resolved.
branchSelectionBranchSelection
Resolve the BranchSelection bundle anchored at codecMessageId. Always returns a safe object.
selectSibling(codecMessageId, index) => void
Select a sibling at the branch point anchored at codecMessageId.
send(events, options?) => Promise<ActiveRun>
Send one or more 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>
Regenerate an assistant message using this view's branch for history.
edit(messageId, inputs, options?) => Promise<ActiveRun>
Edit a user message, forking from this view's branch.

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 | undefined

Look 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 | undefined

Direct 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): void

Select 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.

JavaScript

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 }] } })
        }
      />
    </>
  );
}