> 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/convai-unity-sdk/ui-and-presentation/transcript-ui/chat-and-subtitle-modes.md).

# 聊天和字幕模式

向场景中添加可滚动的聊天历史、实时字幕或两者，并配置每种转录显示的外观和行为。

`ConvaiManager.ActiveManager.Transcripts` (`ConvaiTranscripts`) 提供对话的两个独立视图：与实时语音对齐的字幕和持久化的轮次历史。本页介绍如何添加 `SubtitleTranscriptUI` 用于字幕， `ChatTranscriptUI` 用于历史，或两者都添加——两者都从同一个外观层读取并独立更新，因此添加其中一个不会移除或替换另一个。

### 选择你需要的投影

| 投影  | 外观层成员                                                                     | 被以下对象消费                       | 最适合                          |
| --- | ------------------------------------------------------------------------- | ----------------------------- | ---------------------------- |
| 字幕  | `CurrentCaptions`, `CaptionsChanged`, `SubscribeCaptions(...)`            | `SubtitleTranscriptUI` （参考脚本） | 低延迟、与语音对齐的文本。短暂存在——不作为聊天历史存储 |
| 时间线 | `CurrentTimeline`, `Changed`, `Subscribe(...)`, `SubscribeCommitted(...)` | `ChatTranscriptUI` （随包组件）     | 可持久保存、可滚动的对话历史，便于审阅、回放或导出    |

没有一个单独的模式开关会以牺牲另一个投影为代价来激活其中一个。自定义组件可以从 `ConvaiManager.ActiveManager.Transcripts` 直接订阅任一投影，或两者都订阅：

```csharp
using System;
using Convai.Domain.Models;
using Convai.Runtime.Components;
using UnityEngine;

public class TranscriptProjectionExample : MonoBehaviour
{
    private IDisposable _captionSubscription;
    private IDisposable _historySubscription;

    private void OnEnable()
    {
        if (!ConvaiManager.ActiveManager.TryGetTranscripts(out ConvaiTranscripts transcripts)) return;

        // 用于屏幕字幕的低延迟字幕
        _captionSubscription = transcripts.SubscribeCaptions(OnCaption);

        // 用于可滚动聊天记录的持久轮次历史
        _historySubscription = transcripts.Subscribe(
            OnHistoryChange,
            new TranscriptSubscriptionOptions { ReplayExisting = true });
    }

    private void OnDisable()
    {
        _captionSubscription?.Dispose();
        _historySubscription?.Dispose();
    }

    private void OnCaption(TranscriptCaption caption) { /* 更新字幕文本 */ }
    private void OnHistoryChange(TranscriptChange change) { /* 更新聊天气泡 */ }
}
```

两个随包显示组件也会遵循 `ConvaiTranscripts.IsPresentationEnabled`。当设置面板的 `Transcript` 开关关闭时，或一个 `ConvaiRuntimeSettingsPatch` 设置 `TranscriptEnabled = false`, `ChatTranscriptUI` 并且 `SubtitleTranscriptUI` 两者都会停止渲染——但 `CurrentTimeline` 仍会继续记录。关闭显示不会丢弃历史。

### 添加聊天历史显示

`ChatTranscriptUI` 将每一轮以消息气泡的形式渲染在可滚动列表中，角色和玩家轮次分列在不同列中。在一轮消息流式传输时，气泡会实时更新，并在该轮提交后固定下来。

{% stepper %}
{% step %}

#### 将预制体添加到场景中

拖拽 `TranscriptUI_Chat.prefab` 到场景中。它位于 `Prefabs/TranscriptUI/TranscriptUI_Chat.prefab` 在 <code class="expression">space.vars.sdk\_package\_id</code> 包中。该预制体包含自己的 `Canvas` ——不要将它嵌套在现有的 `Canvas`.

`ChatTranscriptUI` 自动查找 `ConvaiManager.ActiveManager` 并调用 `Subscribe(...)` 在 `ConvaiManager.ActiveManager.Transcripts` 一旦找到即可。无需手动注册。
{% endstep %}

{% step %}

#### 确保存在 EventSystem

聊天输入框需要场景中的一个 `EventSystem` 。如果你的场景没有，请通过以下路径添加： **GameObject → UI → Event System**.
{% endstep %}

{% step %}

#### 运行你的场景

连接到一个角色并进行说话。角色语音会显示在一列气泡中，玩家语音显示在另一列，面板会自动滚动到最新消息。
{% endstep %}
{% endstepper %}

#### `ChatTranscriptUI` 检查器字段

| 字段                       | 说明                                   |
| ------------------------ | ------------------------------------ |
| `scrollRect`             | `ScrollRect` 包含消息列表。自动滚动所必需          |
| `chatContainer`          | `RectTransform` 消息气泡 GameObject 的父对象 |
| `characterMessagePrefab` | 每个角色轮次实例化的预制体                        |
| `playerMessagePrefab`    | 每个玩家轮次实例化的预制体                        |
| `chatInputField`         | 可选 `TMP_InputField` 用于输入文本           |
| `fadeDuration`           | 面板淡入/淡出动画的秒数（默认 `0.5`)               |
| `canvasFader`            | `CanvasFader` 驱动淡入淡出动画               |
| `canvasGroup`            | `CanvasGroup` 控制淡入淡出期间的可交互性          |

#### `ChatMessageBubble` 检查器字段

每个消息气泡预制体都必须在其根节点包含一个 `ChatMessageBubble` 组件：

| 字段          | 说明                        |
| ----------- | ------------------------- |
| `senderUI`  | `TextMeshProUGUI` 显示说话者名称 |
| `messageUI` | `TextMeshProUGUI` 显示转录文本  |

`ChatTranscriptUI` 使用该角色配置的 `NameTagColor` 为角色气泡的发送者名称着色（来自 `ConvaiCharacter` 或其角色配置资源）。若要在脚本中覆盖此设置，请调用 `bubble.SetSenderColor(Color)`.

调用 `ChatTranscriptUI.ClearAll()` 以销毁所有已渲染的消息气泡并重置面板。这只会清除可视显示—— `ConvaiManager.ActiveManager.Transcripts.CurrentTimeline` 中的底层轮次历史不受影响。有关注意事项，请参阅 [Transcript UI — 清除转录显示](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/ui-and-presentation/transcript-ui.md#clear-the-transcript-display) 。

### 添加字幕

`SubtitleTranscriptUI` 显示固定位置的一段单独文本，由 `SubscribeCaptions`驱动。当字幕最终确定后，文本会在可配置的延迟后自动隐藏。

{% hint style="warning" %}
`SubtitleTranscriptUI` 作为仅脚本形式提供——没有配套预制体。它是位于 `SamplesShared`中的参考实现；在修改前请将其复制到你自己的程序集，因为 `SamplesShared` 脚本会在 SDK 更新时被覆盖。
{% endhint %}

{% stepper %}
{% step %}

#### 将脚本添加到 UI GameObject

添加 `SubtitleTranscriptUI` 为角色气泡的发送者名称着色（来自 `Scripts/UI/Transcript/Subtitle/SubtitleTranscriptUI.cs` 在 `SamplesShared`）到一个 `GameObject` 下的一个 `Canvas`。创建或复用两个 `TMP_Text` 元素分别用于字幕正文和说话者名称，再加一个容器 `GameObject` 用于切换可见性。
{% endstep %}

{% step %}

#### 连接检查器字段

分配 `subtitleText`, `speakerLabel`，以及 `subtitleContainer`。添加一个 `CanvasFader` 并且 `CanvasGroup` 如果你想要淡入淡出过渡。
{% endstep %}

{% step %}

#### 运行你的场景

进行发言，或让角色发言。字幕会淡入，在轮次流式传输期间逐字更新，并在 `autoHideDelay` 秒后在轮次最终确定后隐藏。
{% endstep %}
{% endstepper %}

#### `SubtitleTranscriptUI` 检查器字段

| 字段                  | 默认    | 说明                           |
| ------------------- | ----- | ---------------------------- |
| `subtitleText`      | —     | `TMP_Text` 渲染字幕正文            |
| `speakerLabel`      | —     | `TMP_Text` 渲染说话者名称           |
| `subtitleContainer` | —     | `GameObject` 包装两个文本元素，可切换开/关 |
| `fadeDuration`      | `0.3` | 淡入/淡出动画所用秒数                  |
| `canvasFader`       | —     | `CanvasFader` 驱动淡入淡出动画       |
| `canvasGroup`       | —     | `CanvasGroup` 控制可交互性         |
| `autoHideDelay`     | `3.0` | 字幕最终确定后等待隐藏的秒数               |

**过滤器：**

| 字段                    | 默认          | 说明                                                                                           |
| --------------------- | ----------- | -------------------------------------------------------------------------------------------- |
| `finalOnly`           | `false`     | 当 `true`时，只显示最终确定的字幕；流式文本会被跳过                                                                |
| `filterBySpeakerType` | `false`     | 当 `true`时，将字幕限制为 `speakerType`                                                               |
| `speakerType`         | `Character` | `TranscriptSpeakerType` 在启用时按其进行过滤（ `filterBySpeakerType` （`Player`, `Character`，或 `System`) |
| `speakerIdFilter`     | —           | 将字幕限制为特定说话者 ID                                                                               |
| `participantIdFilter` | —           | 将字幕限制为特定房间参与者 ID（多人房间）                                                                       |

**说话者标签颜色：** 角色语音——青色；玩家语音——绿色。

### 为聊天消息添加反馈按钮

聊天气泡可以包含点赞/点踩反馈按钮，允许用户对单个 AI 回复进行评分。

{% stepper %}
{% step %}

#### 将 FeedbackButtons 添加到你的气泡预制体

添加 `FeedbackButtons.prefab` 作为你的角色消息气泡预制体的子对象。它位于 `Prefabs/TranscriptUI/FeedbackButtons.prefab` 在 <code class="expression">space.vars.sdk\_package\_id</code> 包中。该预制体包含反馈按钮视觉效果和一个 `FeedbackHandler` 组件。
{% endstep %}

{% step %}

#### 在用户可以对消息评分之前设置交互 ID

调用 `ChatMessageBubble.SetInteractionID(string)` 用于你想要设为可评分的那一轮。交互 ID 可从 `ConvaiManager.ActiveManager.Events.OnInteractionCreated` (`InteractionCreated.InteractionId`获取，以 `CharacterId`).
{% endstep %}

{% step %}

#### 运行你的场景

使用拇指按钮对角色回复进行评分。选中的按钮会高亮；另一个按钮会停用。 `FeedbackHandler.ResetState()` 被调用时会将两个按钮都重置为中性状态。
{% endstep %}
{% endstepper %}

{% hint style="warning" %}
没有随包组件会自动调用 `ChatMessageBubble.SetInteractionID(string)` 。在你的代码为某轮调用它之前， `ChatMessageBubble.SendFeedback(bool)` 返回 `false` ，并且该消息的按钮不会高亮。
{% endhint %}

#### `ChatMessageBubble` 反馈 API

| 方法                                               | 说明                                                              |
| ------------------------------------------------ | --------------------------------------------------------------- |
| `SetInteractionID(string interactionID)`         | 在可发送反馈前必须先为此气泡设置                                                |
| `SetAgentRegistry(IAgentRegistry agentRegistry)` | 角色查找所必需。由以下对象自动注入： `ChatTranscriptUI`                           |
| `bool SendFeedback(bool isPositiveFeedback)`     | 返回 `true` 如果 `interactionID` 已设置且能在代理注册表中找到该角色，则返回 `false` 否则返回 |

`FeedbackHandler.ResetState()` 会停用正向和负向按钮的填充视觉效果，使按钮恢复为中性状态。反馈按钮只与角色消息气泡相关——玩家气泡不会接收交互 ID。

### 使用示例

#### 安全培训——用于课后复盘的聊天历史

一个工作场所安全培训模拟使用聊天历史，以便培训者在完成场景后可以回顾完整的 AI 指导员对话：

* 拖拽 `TranscriptUI_Chat.prefab` 到场景中 `Canvas`
* 设置 `fadeDuration` 为 `0.3` 以便在活动场景期间获得响应式过渡
* 调用 `ChatTranscriptUI.ClearAll()` 在新场景开始时，这样旧消息就不会延续过来

在运行时，培训者会看到 AI 指导员说过的所有内容的持久记录，并在进入下一个模块之前在复盘过程中滚动查看。

#### 医疗模拟——用于干净叠加层的字幕

一个程序化医疗模拟使用字幕，以便 AI 患者的语音作为干净的叠加层显示在患者模型上方，而不会遮挡屏幕上的临床读数：

* 添加 `SubtitleTranscriptUI` 使用 `subtitleContainer` 底部居中锚点
* 设置 `autoHideDelay` 为 `2.0` ——患者回复之间的快速清除让屏幕保持整洁
* 保持 `filterBySpeakerType` 关闭，这样患者和培训者的发言都会显示为字幕

在运行时，每次患者回复都会短暂显示为字幕并清除，而不会累积历史，从而在整个过程中保持模拟界面整洁。

#### 博物馆信息亭——字幕与历史同时运行

一家自然历史博物馆为游客运行一个展区屏幕，并为讲解员运行一个单独的复盘站点，这两者都由同一段对话驱动：

* 添加 `SubtitleTranscriptUI` 到展区屏幕的 `Canvas`，并将 `filterBySpeakerType` 打开且 `speakerType` 设置为 `Character`，这样只有展区角色的发言会成为字幕
* 添加 `TranscriptUI_Chat.prefab` 到讲解员站点的 `Canvas`
* 两者都从同一个 `ConvaiManager.ActiveManager.Transcripts` 读取并独立更新——无需按模式配置

在运行时，游客在展区会看到整洁滚动的字幕，而讲解员站点会积累完整对话供复盘，全部来自单次会话。

### 故障排查

| 症状                                               | 可能原因                                                  | 修复方法                                                                                                              |
| ------------------------------------------------ | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `[ChatTranscriptUI] chatContainer 未分配 - 消息将不会显示` | `chatContainer` 在检查器中未连接                              | 分配 `RectTransform` 消息气泡应作为其父对象的                                                                                   |
| `[ChatTranscriptUI] scrollRect 未分配 - 自动滚动将无法工作`  | `scrollRect` 在检查器中未连接                                 | 分配 `ScrollRect` 组件；自动滚动不可用，但气泡仍会出现                                                                                |
| `[ChatTranscriptUI] 未找到活动的 ConvaiManager。`       | `ConvaiManager` 缺失或尚未初始化                              | 添加 `ConvaiManager` 到场景中，并确认它在此组件查找它之前已完成初始化                                                                       |
| `[ChatTranscriptUI] 无法发送消息 - 依赖项未注入`             | 在 `chatInputField` 中输入的文本                             | 在依赖项注入之前 `ConvaiManager` 确保 `以及一个` 都存在并已在场景中初始化                                                                   |
| `[SubtitleTranscriptUI] 未找到活动的 ConvaiManager。`   | 与上面相同的原因，适用于字幕脚本                                      | 添加 `ConvaiManager` 到场景中                                                                                           |
| 字幕从不出现                                           | 没有组件正在调用 `SubscribeCaptions` 在场景中，或者字幕的说话者与配置的过滤条件不匹配 | 添加 `SubtitleTranscriptUI`，请确认其 `TMP_Text` 字段已分配，并检查 `filterBySpeakerType`/`speakerIdFilter`/`participantIdFilter` |
| 聊天和字幕显示都不更新                                      | `ConvaiTranscripts.IsPresentationEnabled` 是 `false`   | 检查设置面板的 `Transcript` 开关，或应用 `TranscriptEnabled = true` 通过一个 `ConvaiRuntimeSettingsPatch`                          |
| 反馈按钮从不高亮                                         | `SendFeedback` 返回 `false`                             | 确认你的代码为该轮调用了 `ChatMessageBubble.SetInteractionID(string)` ——没有随包组件会自动执行此操作                                        |

### 下一步

你已经添加了聊天历史显示、字幕，或两者都添加了，并在聊天消息上接好了反馈按钮。有关自定义气泡视觉外观或构建完全自定义转录显示的信息，请参阅“自定义 UI 组件”。若要让用户在运行时显示或隐藏转录显示，请参阅“设置面板”。

{% content-ref url="/pages/5f60eaa8d444f5ee8a995392852da827736d3b54" %}
[自定义 UI 组件](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/ui-and-presentation/customizing-ui-components.md)
{% endcontent-ref %}

{% content-ref url="/pages/dddf624ca0ae7ec32afa0c9e601f2cb7c7ab2b6a" %}
[设置面板](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/ui-and-presentation/settings-panel.md)
{% endcontent-ref %}

{% content-ref url="/pages/b846767a1ed5a04b3cfb93646c3c8472baf91067" %}
[转录历史与查询](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/ui-and-presentation/transcript-ui/transcript-history-and-queries.md)
{% endcontent-ref %}


---

# 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/convai-unity-sdk/ui-and-presentation/transcript-ui/chat-and-subtitle-modes.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.
