> For the complete documentation index, see [llms.txt](https://docs.convai.com/api-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/convai-web-sdk/choose-a-transport.md).

# 选择一种传输方式

为 Convai Web SDK 应用在 SSE、WebRTC 和 WebSocket 之间做出选择，查看推荐的 SSE 聊天加实时语音模式及其原因，并进行任一配置。

Convai Web SDK 可通过三种传输方式连接到角色，而其中一种 `ConvaiClient` 可以使用其中任何一种。使用本页选择一种，或按我们推荐的方式结合 SSE 和实时。

* **SSE** 适用于更简单的聊天：文本输入，文本或音频输出。每条消息都是一个 HTTP 请求。
* **实时（WebRTC，默认）** 适用于对话：语音输入和输出、视频、屏幕共享、唇形同步以及低延迟。会话在你连接期间保持打开。
* **WebSocket** 在 WebRTC 被阻止的地方传输实时语音，例如某些 webview 和阻止 UDP 的网络。参见 [使用 WebSocket 传输](/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/convai-web-sdk/websocket-transport-layer.md).

{% hint style="warning" %}
SSE 使用的交互 API 目前处于测试版。它可在生产环境中访问（`https://interaction-api.convai.com/v1/interactions`），在预发布环境中（`https://interaction-api-stg.convai.com/v1/interactions`）以及在自托管部署中访问。参见 [通过 SSE 传输进行聊天](/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/convai-web-sdk/sse-transport.md).
{% endhint %}

### 比较这些传输方式

|                    | 实时（WebRTC）          | WebSocket                | SSE                                      |
| ------------------ | ------------------- | ------------------------ | ---------------------------------------- |
| 文本输入，流式文本输出        | 是                   | 是                        | 是                                        |
| 角色语音（TTS）          | 是，作为音轨              | 是                        | 是，作为由你的页面播放的 PCM 分块                      |
| 语音输入（麦克风）          | 是                   | 是                        | 否                                        |
| 通过说话中断             | 是                   | 是                        | 否； `sendInterruptMessage()` 会停止一次回复      |
| 视频和屏幕共享            | 是                   | 否                        | 否                                        |
| 唇形同步 blendshape    | 是                   | 是                        | 否                                        |
| 连接                 | 一个房间，在会话期间保持打开      | 一个套接字，在会话期间保持打开          | 消息之间不保持任何连接                              |
| 麦克风权限提示            | 当麦克风开启时             | 当麦克风开启时                  | 从不                                       |
| 网络                 | WebRTC，默认通过 TURN 中继 | WebSocket                | 普通 HTTPS                                 |
| 可在 Node 或无服务器环境下运行 | 否                   | 否                        | 是                                        |
| 由                  | `url` （默认）          | `transport: 'websocket'` | `interactionApiUrl` 或 `transport: 'sse'` |

### 推荐：聊天用 SSE，语音模式用实时

每次对话都先在 SSE 上开始，只有当用户选择说话时才切换到实时。Convai Playground 在生产环境中就是这样运行其角色聊天的。

* **大多数轮次都是打字输入。** 在 SSE 中，消息之间不会保持任何连接。实时会话在连接期间会维持一个房间和角色的管线持续运行，无论是否空闲，直到服务器因不活动而关闭它。
* **聊天会立即打开，不会请求任何东西。** SSE 的 `connect()` 不会发起网络请求，也从不提示使用麦克风。实时会连接一个房间并等待角色先加入，这需要几秒钟。
* **它在更多网络上都能工作。** SSE 就是普通的 HTTPS。限制较严的网络可能会阻止 WebRTC。
* **角色仍然可以说话。** 开启 TTS 后，SSE 会在同一个响应中流式传输每次回复的语音。
* **语音模式会延续对话。** 在实时模式下使用相同的角色会话 ID 重新连接同一客户端，会延续对话，而退出语音模式则相反。

#### 在 SSE 上设置文本聊天

指向 `interactionApiUrl` 交互 API，或指向你自己的服务器路由，以便加入 API 密钥，让密钥永远不会到达浏览器。保持机器人音频关闭，直到用户将其打开，然后播放 `audioData` 这些分块。每个分块都是 base64 编码的有符号 16 位小端 PCM，没有 WAV 头；从分块中读取 `sample_rate` 并且 `channels` 。

{% code title="chat.ts" %}

```ts
import { ConvaiClient } from '@convai/web-sdk/core';

const client = new ConvaiClient({
  apiKey: 'YOUR_API_KEY',
  characterId: 'YOUR_CHARACTER_ID',
  interactionApiUrl: 'YOUR_INTERACTION_API_URL',
  ttsEnabled: false,
});

const ctx = new AudioContext();
let botMuted = true;
let endsAt = 0;

client.on('audioData', ({ audio, sample_rate, channels }) => {
  if (botMuted) return;
  const bytes = Uint8Array.from(atob(audio), (c) => c.charCodeAt(0));
  const view = new DataView(bytes.buffer);
  const frames = bytes.length / 2 / channels;
  const buffer = ctx.createBuffer(channels, frames, sample_rate);
  for (let ch = 0; ch < channels; ch++) {
    const out = buffer.getChannelData(ch);
    for (let i = 0; i < frames; i++) out[i] = view.getInt16((i * channels + ch) * 2, true) / 0x8000;
  }
  const source = ctx.createBufferSource();
  source.buffer = buffer;
  source.connect(ctx.destination);
  const startAt = Math.max(endsAt, ctx.currentTime + 0.04);
  source.start(startAt);
  endsAt = startAt + buffer.duration;
});

// 将其连接到扬声器按钮。通过点击取消静音可让浏览器开始音频。
function setBotMuted(muted: boolean) {
  botMuted = muted;
  if (!muted) void ctx.resume();
  client.toggleTts(!muted);
}

await client.connect();
client.sendUserTextMessage('Hello!');
```

{% endcode %}

`botTurnCompleted` 会在最后一个分块传送完成时触发，因此当用户再次发送时，回复的语音可能仍在播放。在发送时停止排队的源，以免旧回复与新回复同时发声。

#### 将对话切换到实时模式以进行语音模式

保存角色会话 ID，然后用它在实时模式下重新连接同一客户端。连接成功后开启 TTS 并取消麦克风静音。麦克风可能需要几秒钟才会启动，因此请从 `audioStateChange` 上的事件 `client.audioControls`.

{% code title="voice-mode.ts" %}

```ts
let sessionId: string | undefined;
client.on('characterSessionId', (id) => {
  sessionId = id;
});

async function enterVoiceMode() {
  await client.disconnect();
  await client.connect({
    apiKey: 'YOUR_API_KEY',
    characterId: 'YOUR_CHARACTER_ID',
    characterSessionId: sessionId,
    startWithAudioOn: false,
    ttsEnabled: false,
  });
  client.toggleTts(true);
  await client.audioControls.unmuteAudio();
}

async function exitVoiceMode() {
  await client.audioControls.muteAudio();
  await client.disconnect();
  await client.connect({
    apiKey: 'YOUR_API_KEY',
    characterId: 'YOUR_CHARACTER_ID',
    interactionApiUrl: 'YOUR_INTERACTION_API_URL',
    characterSessionId: sessionId,
    ttsEnabled: false,
  });
}
```

{% endcode %}

{% hint style="info" %}
`disconnect()` 会清除客户端按连接划分的状态。切换时请在你自己的状态中保留转写内容，并附加下一次连接的消息，同时通过 `id`移除重复项。口语轮次会作为 `user-transcription` 消息到达。
{% endhint %}

### 基于一种传输方式构建

该推荐方案是默认方案。每种传输方式也都可单独使用。

#### 仅实时

当语音是人们使用你的应用的主要方式时，例如会说话的头像、语音助手或自助终端，或当你需要视频、屏幕共享或唇形同步时，请从第一条消息开始就使用实时模式。连接等待只会在开始时发生一次，之后的每一轮都具有实时延迟。预构建的 `ConvaiWidget` 就是这样工作的；从 [Web SDK 快速入门](/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/convai-web-sdk/quickstart.md).

{% code title="realtime.ts" %}

```ts
import { ConvaiClient } from '@convai/web-sdk/core';

const client = new ConvaiClient({
  apiKey: 'YOUR_API_KEY',
  characterId: 'YOUR_CHARACTER_ID',
  startWithAudioOn: true,
});

await client.connect();
```

{% endcode %}

#### 仅 SSE

当你的应用从不监听用户时，请单独使用 SSE，例如聊天窗口、支持小组件或表单助手，无论是否带有角色语音。SSE 也是唯一可在浏览器外运行的传输方式，因此可用于服务端集成、CI 检查和批处理任务。在那里导入 `@convai/web-sdk/core` ；根目录和 `/react` 入口点会加载仅限浏览器的代码。设置方式就是上面的文本聊天，不使用语音模式。

#### 使用 WebSocket 而不是 WebRTC

在本页写着实时的地方，当 WebRTC 不可用时，可以用 WebSocket 传输方式代替。它可以传输语音和唇形同步，但不传输视频。参见 [使用 WebSocket 传输](/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/convai-web-sdk/websocket-transport-layer.md).

### 快速决定

| 你的应用                   | 使用                |
| ---------------------- | ----------------- |
| 主要是聊天，语音作为可选项          | 带有实时语音模式的 SSE（推荐） |
| 主要是口语交互：头像、语音助手或自助终端   | 仅实时               |
| 需要视频、屏幕共享或唇形同步         | 仅实时，或在这些部分切换到实时   |
| 只接受打字输入，可带或不带语音回复      | 仅 SSE             |
| 在服务器上、CI 中或批处理任务中运行    | 仅 SSE             |
| 需要在 WebRTC 被阻止的环境中使用语音 | WebSocket         |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/convai-web-sdk/choose-a-transport.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
