> ## Documentation Index
> Fetch the complete documentation index at: https://docs.akapulu.com/llms.txt
> Use this file to discover all available pages before exploring further.

# React

> React provider, hooks, and media helpers for Akapulu conversations.

## Overview

`@akapulu/react` is the lower-level React package in the Akapulu Web SDK.

It gives you the provider, hooks, and media helpers you need to build your own React conversation UI.

## Installation

```bash theme={null}
npm install @akapulu/react
```

## Quickstart

`AkapuluProvider` takes paths to your local API routes:

* `connectPath`
* `updatesPath`

The browser calls those local routes. Your server routes then call Akapulu APIs through [`@akapulu/server`](/web-sdk/server-sdk).

```tsx theme={null}
import { AkapuluProvider, useAkapuluSession } from "@akapulu/react";

function ConversationState() {
  const { status, start, end, transcripts, currentNode } = useAkapuluSession();

  return (
    <div>
      <div>Status: {status}</div>
      <div>Current node: {currentNode?.label ?? "none"}</div>
      <button onClick={() => void start()}>Start</button>
      <button onClick={() => void end()}>End</button>
      <div>{transcripts.length} transcript rows</div>
    </div>
  );
}

export function App() {
  return (
    <AkapuluProvider
      config={{
        endpoints: {
          connectPath: "/api/akapulu/connect",
          updatesPath: "/api/akapulu/updates",
        },
      }}
    >
      <ConversationState />
    </AkapuluProvider>
  );
}
```

## Main exports

The public exports currently include:

* `AkapuluProvider`
* `useAkapuluSession`
* `useAkapuluEvents`
* `useAkapuluDailyCall`
* `useAkapuluMediaControls`
* `useAkapuluParticipantRoles`
* `AkapuluBotAudio`
* `AkapuluConfig`
* `AkapuluEvent`

## Provider

`AkapuluProvider` wraps your app and creates the conversation session store.

```ts theme={null}
type AkapuluProviderProps = {
  config: AkapuluConfig;
  children: ReactNode;
}
```

`AkapuluConfig` includes:

<ParamField path="endpoints.connectPath" type="string" required>
  Local connect endpoint called by the browser.
</ParamField>

<ParamField path="endpoints.updatesPath" type="string" required>
  Local updates endpoint called by the browser.
</ParamField>

<ParamField path="connectBody" type="Record<string, unknown>">
  Optional JSON payload forwarded to your local connect route.
</ParamField>

<ParamField path="updatesPollIntervalMs" type="number">
  Optional polling interval override for the local updates route.
</ParamField>

While `status === "connecting"`, the React SDK waits up to **100 seconds** for the backend to report that the call is ready.

If the updates route never reports `call_is_ready: true` within that window, the session transitions to:

* `status: "error"`
* `error.code: "UPDATES_TIMEOUT"`
* `error.message: "Timed out waiting for assistant to become ready."`

<ParamField path="transport" type="AkapuluTransport">
  Optional custom transport implementation.
</ParamField>

**Ending a call:** When **your user** ends the call in your app, call `end()` from `useAkapuluSession()`. That leaves the Daily room from the browser and runs the SDK teardown so the session is no longer live. If the **assistant** disconnects from Daily first, `AkapuluProvider` calls `end()` for you so `status` does not stay `connected` with no assistant in the room.

## Hooks

### `useAkapuluSession()`

Call `useAkapuluSession()` anywhere under `AkapuluProvider`. It exposes everything in the session store plus `start` / `end`:

```tsx theme={null}
const {
  status,
  start,
  end,
  error,
  callIsReady,
  conversationSessionId,
  completionPercent,
  latestUpdateText,
  currentNode,
  transcripts,
  botSpeakingState,
} = useAkapuluSession();
```

**Lifecycle & connection**

* `status` — where the client is in the join/leave flow: `"idle"` → `"connecting"` → `"connected"` → `"disconnecting"` / `"ended"`, or `"error"` if something failed.
* `start` / `end` — async actions that begin the conversation (connect + Daily join) or hang up and reset session state.
* `error` — when `status` is `"error"`, structured details (`code` optional, `message` required) for your error UI or logging.

**Call readiness & updates** (from your **updates** route)

* `callIsReady` — whether the backend considers the call ready (often used while `status === "connecting"` so you are not stuck on a spinner forever).
* `completionPercent` — numeric progress through the scenario (0–100).
* `latestUpdateText` — short human-readable status line for loading/progress copy.

**Scenario / flow**

* `currentNode` — the active scenario node (`{ key, label }`) or `null` if none.

**Transcript & bot**

* `transcripts` — ordered list of rows (`id`, `text`, `speaker`, `timestamp`, `isFinal`) for your own transcript UI.
* `botSpeakingState` — `"idle"`, `"speaking"`, or `"listening"` for indicators or turn-taking UI.

**Correlation**

* `conversationSessionId` — Akapulu conversation session id from connect (used when polling updates; also useful if your app logs or links out to dashboard/API records).

See [Customize Conversation UI](/guides/conversations/customize-conversation-ui) for how this fits into a full custom layout with Daily and media helpers.

### `useAkapuluEvents(listener)`

Subscribes to the normalized `AkapuluEvent` stream.

See [Callbacks and events](/web-sdk/callbacks-and-events).

### `useAkapuluParticipantRoles(options?)`

Returns participant-role mapping helpers for Daily sessions under `AkapuluProvider`.

Use this hook when your call may include more than one remote participant and you want a stable assistant video participant id instead of relying on participant order.

```ts theme={null}
type AkapuluParticipantRoleOptions = {
  akapuluBotUserId?: string;
};

type AkapuluParticipantRoles = {
  akapuluBotParticipantId: string;
  userParticipantIds: string[];
  remoteParticipantIds: string[];
};
```

```tsx theme={null}
import { useAkapuluParticipantRoles } from "@akapulu/react";
import { DailyVideo } from "@daily-co/daily-react";

function AssistantVideo() {
  const { akapuluBotParticipantId } = useAkapuluParticipantRoles();
  if (!akapuluBotParticipantId) return null;
  return <DailyVideo sessionId={akapuluBotParticipantId} type="video" />;
}
```

Use `useAkapuluParticipantRoles` for the assistant tile and `useDaily()` for the local/self tile:

```tsx theme={null}
import { useAkapuluParticipantRoles } from "@akapulu/react";
import { DailyVideo, useDaily } from "@daily-co/daily-react";

function VideoLayout() {
  const { akapuluBotParticipantId } = useAkapuluParticipantRoles();
  const daily = useDaily();
  const localParticipantId = daily?.participants().local?.session_id ?? "";

  return (
    <>

      // bot participant
      {akapuluBotParticipantId ? (
        <DailyVideo sessionId={akapuluBotParticipantId} type="video" />
      ) : null}

      // local participant
      {localParticipantId ? (
        <DailyVideo sessionId={localParticipantId} type="video" automirror />
      ) : null}
    </>
  );
}
```

### `useAkapuluMediaControls()`

Returns media control helpers for the underlying Daily call.

```ts theme={null}
type AkapuluMediaControls = {
  isMicMuted: boolean;
  isCamOff: boolean;
  canControlMedia: boolean;
  setMicMuted: (muted: boolean) => void;
  setCamOff: (off: boolean) => void;
  toggleMic: () => void;
  toggleCam: () => void;
}
```

### `useAkapuluDailyCall()`

Returns the active [**Daily call object**](https://docs.daily.co/guides/products/call-object) for the current session—the same [`DailyCall`](https://docs.daily.co/reference/daily-js/daily-call-client) instance `AkapuluProvider` wires into Daily’s [`DailyProvider`](https://docs.daily.co/reference/daily-react/daily-provider). Before the client exists, this hook returns `null`.

For most React UI you should use [`@daily-co/daily-react`](https://docs.daily.co/reference/daily-react) under `AkapuluProvider`; [`useDaily()`](https://docs.daily.co/reference/daily-react/use-daily) reads that call object from context and is the usual way to drive video tiles, participants, and tracks. Use `useAkapuluDailyCall()` when you want the call object reference directly (for example code aligned with [Daily call-object methods](https://docs.daily.co/reference/daily-js/daily-call-client) without importing `useDaily`).

## Main unions and types

### `SessionStatus`

```ts theme={null}
type SessionStatus =
  | "idle"
  | "connecting"
  | "connected"
  | "disconnecting"
  | "ended"
  | "error";
```

When the session enters `status: "error"`, the SDK disconnects from the active Daily call and clears the active session state before surfacing the error to your UI.

### `BotSpeakingState`

```ts theme={null}
type BotSpeakingState = "idle" | "speaking" | "listening";
```

### `AkapuluEvent`

The main event union includes:

* `status_changed`
* `bot_speaking_state_changed`
* `node_changed`
* `tool_event`
* `transcript_updated`
* `call_ready`
* `timeout`

See [Callbacks and events](/web-sdk/callbacks-and-events) for the full breakdown.

## When to use this package

Use `@akapulu/react` when you want to:

* build your own layout
* render your own transcript UI
* control how tool events appear
* own your call controls and post-call flow

If you want a prebuilt conversation UI instead, use [React UI](/web-sdk/react-ui).

## Related implementation guides

* [Customize Conversation UI](/guides/conversations/customize-conversation-ui)
* [Prebuilt UI](/examples/web-sdk/prebuilt-ui)
* [Customized UI](/examples/web-sdk/customized-ui)

## Related packages

* [React UI](/web-sdk/react-ui)
* [Server](/web-sdk/server-sdk)

## Related APIs

* [Connect API](/api-reference/conversations/connect)
* [Updates API](/api-reference/conversations/updates)
