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

# Web

> Connect a browser to a live conversation using the Equos Browser or React SDKs.

Once your backend has called `startConversation`, the response contains everything your web client needs to join the live room:

* `conversation.serverUrl` — the LiveKit WebSocket URL
* `consumerAccessToken` — the end-user's JWT
* `conversation.character.livekitIdentity` — the character's identity for event wiring

Pick the SDK that matches your stack.

<CardGroup cols={2}>
  <Card icon="js" title="@equos/browser-sdk" href="#browser-sdk">Vanilla TypeScript/JavaScript. Great for custom UIs or non-React frameworks.</Card>
  <Card icon="react" title="@equos/react" href="#react-sdk">Provider + renderer components for React, Next.js, Remix, etc.</Card>
</CardGroup>

***

## Browser SDK

Install:

```bash theme={null}
npm i @equos/browser-sdk
```

Fetch the conversation credentials from your backend, then hand them to `EquosConversation`:

```ts theme={null}
import { EquosConversation, EquosEvent } from "@equos/browser-sdk";

const video = document.getElementById("character-video") as HTMLVideoElement;

// 1. Get credentials from your backend
const res = await fetch("/api/start-conversation", { method: "POST" });
const { conversation: conv, consumerAccessToken } = await res.json();

// 2. Build the client with the three fields
const conversation = new EquosConversation({
  config: {
    wsUrl: conv.serverUrl,
    token: consumerAccessToken,
    agentIdentity: conv.character.livekitIdentity,
  },
});

// 3. Handle events and connect
conversation.on(EquosEvent.AgentConnected, () => conversation.attach(video));
conversation.on(EquosEvent.Utterance, ({ utterance }) =>
  console.log(utterance.author, utterance.content)
);

await conversation.connect();
```

Common controls:

```ts theme={null}
await conversation.setMicrophoneEnabled(true);   // mic on
conversation.sendText("Hello!");                  // send a text message
await conversation.disconnect();                  // hang up
conversation.detach(video);                       // unbind the <video>
```

<Card icon="github" title="Full vanilla example" href="https://github.com/EquosAI/equos-examples/tree/main/examples/equos-vanilla-integration">
  Plain HTML + TypeScript project using Vite.
</Card>

***

## React SDK

Install:

```bash theme={null}
npm i @equos/react
```

The React SDK wraps the browser SDK in a provider + renderer so you don't have to wire events manually:

```tsx theme={null}
import {
  EquosConversationProvider,
  EquosConversationRenderer,
} from "@equos/react";
import { useState } from "react";

export default function App() {
  const [response, setResponse] = useState(null);

  const start = async () => {
    const res = await fetch("/api/start-conversation", { method: "POST" });
    setResponse(await res.json()); // { conversation, consumerAccessToken }
  };

  if (!response) return <button onClick={start}>Start conversation</button>;

  return (
    <EquosConversationProvider
      conversation={response.conversation}
      accessToken={response.consumerAccessToken}
      autoPublishMic
    >
      <EquosConversationRenderer
        allowMic
        allowCamera
        allowScreenshare
        allowHangUp
        onHangUp={() => setResponse(null)}
      />
    </EquosConversationProvider>
  );
}
```

Inside the provider, a `useEquosConversation()` hook exposes methods like `sendText()` and `sendContext()` for building custom controls.

<Card icon="github" title="Full React example" href="https://github.com/EquosAI/equos-examples/tree/main/examples/equos-react-integration">
  Vite + React app using `<EquosConversationProvider>` and `<EquosConversationRenderer>`.
</Card>

***

## Next.js (full-stack pattern)

Next.js is the canonical full-stack pattern: your API key stays on the server (in a server action or route handler), and only the conversation payload crosses to the client.

**Server action** — `app/actions.ts`:

```ts theme={null}
"use server";
import { equos } from "@/lib/equos"; // server-only EquosClient

export async function startConversationAction() {
  return equos.conversations.startConversation({
    createEquosConversationRequest: {
      name: `nextjs-demo-${Date.now()}`,
      characterId: process.env.EQUOS_CHARACTER_ID!,
      consumer: { name: "Demo User", identity: "demo-user" },
    },
  });
}

export async function stopConversationAction(id: string) {
  await equos.conversations.stopConversation({ id });
}
```

**Client component** — `app/conversation-client.tsx`:

```tsx theme={null}
"use client";
import {
  EquosConversationProvider,
  EquosConversationRenderer,
} from "@equos/react";
import { useState, useTransition } from "react";
import {
  startConversationAction,
  stopConversationAction,
} from "./actions";

export function ConversationClient() {
  const [response, setResponse] = useState(null);
  const [, startTransition] = useTransition();

  const start = () =>
    startTransition(async () =>
      setResponse(await startConversationAction())
    );

  const stop = async () => {
    const id = response.conversation.id;
    setResponse(null);
    await stopConversationAction(id);
  };

  if (!response) return <button onClick={start}>Start conversation</button>;

  return (
    <EquosConversationProvider
      conversation={response.conversation}
      accessToken={response.consumerAccessToken}
      autoPublishMic
    >
      <EquosConversationRenderer
        allowMic
        allowCamera
        allowHangUp
        onHangUp={stop}
      />
    </EquosConversationProvider>
  );
}
```

<Tip>
  Mark your server-side Equos client with `import "server-only"` so Next.js fails the build if anything tries to import the API key into a client bundle.
</Tip>

<Card icon="github" title="Full Next.js example" href="https://github.com/EquosAI/equos-examples/tree/main/examples/equos-nextjs-integration">
  Next.js 15 app with server actions + `@equos/react`.
</Card>

***

## Features

The following features are available in both the Browser SDK and the React SDK.

### Mode switch (text · audio · video)

A conversation can run in one of three modes:

* `EquosMode.Text` — typed messages only, no audio or video
* `EquosMode.Audio` — voice conversation, no video
* `EquosMode.Video` — full video + voice (default)

<CodeGroup>
  ```ts Browser SDK theme={null}
  import { EquosMode, EquosEvent, type EquosModeType } from "@equos/browser-sdk";

  // Change mode at runtime
  conversation.setMode(EquosMode.Audio);

  // React to remote changes
  conversation.on(EquosEvent.ModeChanged, (mode: EquosModeType) => {
    console.log("Mode is now", mode); // "text" | "audio" | "video"
  });
  ```

  ```tsx React SDK theme={null}
  import { EquosMode, type EquosModeType } from "@equos/browser-sdk";
  import { EquosConversationProvider } from "@equos/react";
  import { useState } from "react";

  export function Conversation({ response }) {
    const [mode, setMode] = useState<EquosModeType>(EquosMode.Video);

    return (
      <EquosConversationProvider
        conversation={response.conversation}
        accessToken={response.consumerAccessToken}
        autoPublishMic
        mode={mode}
      >
        <button onClick={() => setMode(EquosMode.Text)}>Text</button>
        <button onClick={() => setMode(EquosMode.Audio)}>Audio</button>
        <button onClick={() => setMode(EquosMode.Video)}>Video</button>
        {/* ... */}
      </EquosConversationProvider>
    );
  }
  ```
</CodeGroup>

<Note>
  In the React SDK, `mode` is a **controlled prop** on `EquosConversationProvider`—update your state and re-render to change it.
</Note>

***

### Send user text

Send a text message from the user to the character—useful for typed input, accessibility, or in `EquosMode.Text` conversations. Behaves like the user spoke the message.

<CodeGroup>
  ```ts Browser SDK theme={null}
  conversation.sendText("What's the refund policy?");
  ```

  ```tsx React SDK theme={null}
  import { useEquosConversation } from "@equos/react";

  function Composer() {
    const { sendText } = useEquosConversation();
    const [value, setValue] = useState("");

    return (
      <form onSubmit={(e) => {
        e.preventDefault();
        const text = value.trim();
        if (!text) return;
        sendText(text);
        setValue("");
      }}>
        <input value={value} onChange={(e) => setValue(e.target.value)} />
        <button type="submit">Send</button>
      </form>
    );
  }
  ```
</CodeGroup>

***

### Send context

Inject **silent context** into the conversation—information the character should know about, but that isn't spoken by the user and doesn't appear as a message. Great for things like:

* Telling the character who the logged-in user is (`"The user's name is Alex, Pro tier, signed up in 2024."`)
* Passing in the current page or screen the user is on
* Feeding live data (cart contents, current order, sensor readings) as the conversation evolves

<CodeGroup>
  ```ts Browser SDK theme={null}
  conversation.sendContext("The user is currently viewing the pricing page.");
  ```

  ```tsx React SDK theme={null}
  import { useEquosConversation } from "@equos/react";

  function PageTracker({ page }) {
    const { sendContext } = useEquosConversation();

    useEffect(() => {
      sendContext(`The user is currently on the ${page} page.`);
    }, [page]);

    return null;
  }
  ```
</CodeGroup>

<Tip>
  Unlike `sendText`, `sendContext` does **not** surface as a user utterance. The character picks it up as background knowledge for its next response.
</Tip>
