> 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`         | `角色`    | `TranscriptSpeakerType` 在打开时按以下类型过滤（ `filterBySpeakerType` Player`，或`, `角色`系统 `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` 设置为 `角色`，这样只有展区角色的语音会成为字幕
* 将 `TranscriptUI_Chat.prefab` 到讲解员站点的 `Canvas`
* 两者都从同一个 `ConvaiManager.ActiveManager.Transcripts` 读取并独立更新——无需按模式进行配置

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

### 故障排除

| 症状                                               | 可能原因                                                | 修复                                                                                                              |
| ------------------------------------------------ | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `[ChatTranscriptUI] chatContainer 未分配 - 消息将不会显示` | `chatContainer` 未在检查器中连接                            | 分配 `RectTransform` 消息气泡应作为其父对象的                                                                                 |
| `[ChatTranscriptUI] scrollRect 未分配 - 自动滚动将无法工作`  | `scrollRect` 未在检查器中连接                               | 分配 `ScrollRect` 组件；不会自动滚动，但气泡仍会出现                                                                               |
| `[ChatTranscriptUI] 未找到活动的 ConvaiManager。`       | `ConvaiManager` 缺失或尚未初始化                            | 将 `ConvaiManager` 到场景中，并确保在此组件查找它之前它已完成初始化                                                                      |
| `[ChatTranscriptUI] 无法发送消息 - 依赖项未注入`             | 在 `chatInputField` 中输入的文本在依赖项注入之前                   | 确保 `ConvaiManager` 和一个 `ConvaiPlayer` 存在于场景中并已初始化                                                               |
| `[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 dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
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=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
