> 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/transcript-history-and-queries.md).

# 转录历史与查询

`ConvaiManager.Transcripts` 为你的代码提供对当前会话中每一次对话回合的结构化访问。订阅 `已更改` 以便在回合到达时作出响应，或者轮询 `CurrentTimeline` 以在任意时刻获得完整状态的一致快照。此 API 反映了运行时的状态管理，由 `IRoomTranscriptEngine` — 它不控制 UI 显示，也不会修改转录历史。

若要基于这些数据驱动视觉显示，请参见 [转录 UI](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/ui-and-presentation/transcript-ui.md).

### 访问模式

#### 订阅 `已更改`

`ConvaiManager.Transcripts.Changed` 每次时间线更新时都会触发。 `TranscriptUpdateBatch` 载荷告诉你具体发生了什么变化——新增回合、现有回合更新、回合完成以及回合中断——因此你无需对完整时间线做 diff 就能高效响应。

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

public class TranscriptReactor : MonoBehaviour
{
    private void OnEnable()
    {
        ConvaiManager.ActiveManager.Transcripts.Changed += OnTranscriptChanged;
    }

    private void OnDisable()
    {
        ConvaiManager.ActiveManager.Transcripts.Changed -= OnTranscriptChanged;
    }

    private void OnTranscriptChanged(TranscriptUpdateBatch batch)
    {
        foreach (string turnId in batch.CompletedTurnIds)
        {
            TranscriptTurnSnapshot turn = batch.Timeline.TurnsById[turnId];
            Debug.Log($"[{turn.Participant.DisplayName}] {turn.DisplayText}");
        }
    }
}
```

#### 轮询 `CurrentTimeline`

`ConvaiManager.Transcripts.CurrentTimeline` 返回一个不可变的 `TranscriptTimelineSnapshot` — 在你调用时刻提供完整回合状态的一致视图。对于会话后导出、复盘界面或由游戏事件触发的一次性读取，请使用轮询。

```csharp
public string BuildSessionReport()
{
    var timeline = ConvaiManager.ActiveManager.Transcripts.CurrentTimeline;
    var sb = new System.Text.StringBuilder();

    foreach (TranscriptTurnSnapshot turn in timeline.CommittedTurns)
        sb.AppendLine($"[{turn.StartedAtUtc:HH:mm:ss}] {turn.Participant.DisplayName}: {turn.DisplayText}");

    return sb.ToString();
}
```

### 核心数据结构

#### `ConvaiTranscripts` — 门面

通过以下方式访问 `ConvaiManager.Transcripts`。在 `ConvaiManager` 初始化后，所有成员都可用。

| 成员                                        | 类型                                      | 说明              |
| ----------------------------------------- | --------------------------------------- | --------------- |
| `CurrentTimeline`                         | `TranscriptTimelineSnapshot`            | 当前完整回合时间线的不可变快照 |
| `已更改`                                     | `event Action<TranscriptUpdateBatch>`   | 每当时间线变化时触发      |
| `GetTurns(TranscriptQuery)`               | `IReadOnlyList<TranscriptTurnSnapshot>` | 带可选筛选条件查询回合     |
| `GetTurn(string turnId)`                  | `TranscriptTurnSnapshot`                | 通过回合 ID 快速查找    |
| `GetLatestTurn(TranscriptParticipantRef)` | `TranscriptTurnSnapshot`                | 特定参与者的最新回合      |

#### `TranscriptTimelineSnapshot`

整个房间范围内的不可变快照。每个字段都是集合；都可以安全迭代，无需空值检查。

| 属性                        | 类型                                                    | 说明                                |
| ------------------------- | ----------------------------------------------------- | --------------------------------- |
| `ActiveTurns`             | `IReadOnlyList<TranscriptTurnSnapshot>`               | 当前正在进行中的回合（Streaming 或 Stable）    |
| `CommittedTurns`          | `IReadOnlyList<TranscriptTurnSnapshot>`               | 按房间顺序排列的已完成回合                     |
| `TurnsById`               | `IReadOnlyDictionary<string, TranscriptTurnSnapshot>` | 通过以下项快速查找 `TurnId` — 包括进行中和已提交的回合 |
| `LatestTurnByParticipant` | `IReadOnlyDictionary<string, TranscriptTurnSnapshot>` | 按以下键索引的最新回合 `ParticipantId`       |
| `光标`                      | `long`                                                | 单调递增计数器。可用于检测自上次读取以来时间线是否发生变化。    |

#### `TranscriptTurnSnapshot`

表示来自单一参与者的一段连续语音。不可变——每次更新都会生成一个新的快照。

| 属性                              | 类型                                         | 说明                                         |
| ------------------------------- | ------------------------------------------ | ------------------------------------------ |
| `TurnId`                        | `string`                                   | 此回合的稳定标识符。也可作为 `MessageId`                 |
| `RoomSequence`                  | `long`                                     | 房间会话内的单调排序位置                               |
| `参与者`                           | `TranscriptParticipantRef`                 | 谁产生了此回合（见下文）                               |
| `StartedAtUtc`                  | `DateTime`                                 | 回合开始的 UTC 时间                               |
| `LastUpdatedAtUtc`              | `DateTime`                                 | 此回合最近一次更新的 UTC 时间                          |
| `CompletedAtUtc`                | `DateTime?`                                | 回合最终完成的 UTC 时间。 `null` 在仍处于活动状态时           |
| `生命周期`                          | `TranscriptLifecycle`                      | 当前稳定性状态（Streaming / Stable / Completed）    |
| `CommittedText`                 | `string`                                   | 转录中最终确认的部分                                 |
| `InterimText`                   | `string`                                   | 当前部分内容（尚未确认）                               |
| `DisplayText`                   | `string`                                   | 拼接自 `CommittedText` + `InterimText` — 用于显示 |
| `WasInterrupted`                | `bool`                                     | `true` 如果该回合被其他发言者打断而提前结束                  |
| `HasText`                       | `bool`                                     | `true` 如果 `DisplayText` 包含非空白内容            |
| `ConversationTargetCharacterId` | `string`                                   | 此玩家回合所面向的角色（仅适用于玩家回合）                      |
| `Segments`                      | `IReadOnlyList<TranscriptSegmentSnapshot>` | 回合内按顺序排列的子片段（高级用法）                         |

#### `TranscriptParticipantRef`

附加到每个 `TranscriptTurnSnapshot`.

| 属性                    | 类型                          | 说明                                           |
| --------------------- | --------------------------- | -------------------------------------------- |
| `类型`                  | `TranscriptParticipantKind` | `玩家` 或 `角色`                                  |
| `PlayerOrCharacterId` | `string`                    | 玩家或角色的稳定 SDK 标识符                             |
| `DisplayName`         | `string`                    | 在转录 UI 中显示的人类可读名称                            |
| `ParticipantId`       | `string`                    | 房间范围内的参与者标识符（适用于多人房间）                        |
| `IsEmpty`             | `bool`                      | `true` 如果 `PlayerOrCharacterId` 为 null 或空白字符 |

#### `TranscriptLifecycle`

描述在给定时间点回合文本的稳定程度。

| 值      | 说明                     |
| ------ | ---------------------- |
| `流式传输` | 文本正在主动变化——临时语音识别结果不断到达 |
| `稳定`   | 此时点的文本已锁定，但回合仍未结束      |
| `已完成`  | 回合已完全完成——不会再有后续更新      |

#### `TranscriptUpdateBatch`

传递给 `已更改` 订阅者的载荷。描述自上一批次以来发生了什么变化。

| 属性         | 类型                                      | 说明                                 |
| ---------- | --------------------------------------- | ---------------------------------- |
| `时间线`      | `TranscriptTimelineSnapshot`            | 此批次后的完整时间线状态                       |
| `光标`       | `long`                                  | 此批次后的时间线光标                         |
| `已更改回合`    | `IReadOnlyList<TranscriptTurnSnapshot>` | 所有已更改回合的完整回合快照                     |
| `新增回合 ID`  | `IReadOnlyList<string>`                 | 自上一批次以来新增的回合 ID                    |
| `已更新回合 ID` | `IReadOnlyList<string>`                 | 已更新的回合 ID（文本已变化）                   |
| `已完成回合 ID` | `IReadOnlyList<string>`                 | 已转换为以下状态的回合 ID： `已完成`              |
| `被中断回合 ID` | `IReadOnlyList<string>`                 | 其中回合 ID `WasInterrupted` 变为 `true` |
| `已移除回合 ID` | `IReadOnlyList<string>`                 | 从时间线中移除的回合 ID                      |

### 筛选回合，使用 `TranscriptQuery`

传入一个 `TranscriptQuery` 到 `GetTurns()` 以检索时间线的子集。所有字段都是可选的——省略某个字段会禁用对应筛选条件。

| 字段                    | 类型                           | 默认值         | 说明                    |
| --------------------- | ---------------------------- | ----------- | --------------------- |
| `参与者类型`               | `TranscriptParticipantKind?` | `null` （任意） | 仅限于 `玩家` 或 `角色` 回合    |
| `PlayerOrCharacterId` | `string`                     | `null` （任意） | 按 ID 限定为特定玩家或角色       |
| `ParticipantId`       | `string`                     | `null` （任意） | 按房间范围内的参与者 ID 限定（多用户） |
| `包含活动回合`              | `bool`                       | `true`      | 结果中包含进行中的回合           |
| `包含已提交回合`             | `bool`                       | `true`      | 结果中包含已完成的回合           |

**示例——所有已提交的角色回合：**

```csharp
var query = new TranscriptQuery
{
    ParticipantKind = TranscriptParticipantKind.Character,
    IncludeActiveTurns = false,
    IncludeCommittedTurns = true
};

IReadOnlyList<TranscriptTurnSnapshot> characterTurns =
    ConvaiManager.ActiveManager.Transcripts.GetTurns(query);
```

### 使用示例

#### 会话后报告——导出已提交回合

企业入职模拟会在受训者完成入职对话后生成一份会话转录：

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

public class SessionReporter : MonoBehaviour
{
    public string GenerateReport()
    {
        var timeline = ConvaiManager.ActiveManager.Transcripts.CurrentTimeline;
        var sb = new StringBuilder();
        sb.AppendLine("=== 会话转录 ===");

        foreach (TranscriptTurnSnapshot turn in timeline.CommittedTurns)
        {
            string speaker = turn.Participant.Kind == TranscriptParticipantKind.Character
                ? $"[AI] {turn.Participant.DisplayName}"
                : $"[受训者] {turn.Participant.DisplayName}";

            sb.AppendLine($"{turn.StartedAtUtc:HH:mm:ss}  {speaker}: {turn.DisplayText}");
        }

        return sb.ToString();
    }
}
```

在运行时，调用 `GenerateReport()` 在会话结束后生成带时间戳、按顺序排列的转录，供受训者或主管审阅。

#### 实时分析——跟踪玩家词数

一项沟通技巧模拟会实时跟踪受训者说了多少词，并在对话进行时在复盘 HUD 上更新实时计数器：

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

public class WordCountTracker : MonoBehaviour
{
    [SerializeField] private TMP_Text _wordCountLabel;

    private void OnEnable()
    {
        ConvaiManager.ActiveManager.Transcripts.Changed += OnTranscriptChanged;
    }

    private void OnDisable()
    {
        ConvaiManager.ActiveManager.Transcripts.Changed -= OnTranscriptChanged;
    }

    private void OnTranscriptChanged(TranscriptUpdateBatch batch)
    {
        bool playerTurnChanged = false;
        foreach (TranscriptTurnSnapshot turn in batch.ChangedTurns)
        {
            if (turn.Participant.Kind == TranscriptParticipantKind.Player)
            {
                playerTurnChanged = true;
                break;
            }
        }

        if (!playerTurnChanged) return;

        int wordCount = 0;
        var query = new TranscriptQuery { ParticipantKind = TranscriptParticipantKind.Player };

        foreach (TranscriptTurnSnapshot turn in ConvaiManager.ActiveManager.Transcripts.GetTurns(query))
            wordCount += turn.DisplayText.Split(' ', System.StringSplitOptions.RemoveEmptyEntries).Length;

        _wordCountLabel.text = $"已说词数：{wordCount}";
    }
}
```

#### 中断检测——训练评分扣分

客户服务培训模拟会在 AI 角色的回合被中断时对受训者进行扣分——这表示受训者插话盖过了讲师：

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

public class InterruptionDetector : MonoBehaviour
{
    [SerializeField] private string _instructorCharacterId;
    private int _interruptionCount;

    private void OnEnable()
    {
        ConvaiManager.ActiveManager.Transcripts.Changed += OnTranscriptChanged;
    }

    private void OnDisable()
    {
        ConvaiManager.ActiveManager.Transcripts.Changed -= OnTranscriptChanged;
    }

    private void OnTranscriptChanged(TranscriptUpdateBatch batch)
    {
        foreach (string turnId in batch.InterruptedTurnIds)
        {
            if (!batch.Timeline.TurnsById.TryGetValue(turnId, out var turn)) continue;
            if (turn.Participant.PlayerOrCharacterId != _instructorCharacterId) continue;

            _interruptionCount++;
            Debug.Log($"检测到中断。总计：{_interruptionCount}");
        }
    }

    public int GetInterruptionCount() => _interruptionCount;
}
```

### 故障排查

| 症状                                   | 可能原因                                               | 修复                                                              |
| ------------------------------------ | -------------------------------------------------- | --------------------------------------------------------------- |
| `ConvaiManager.Transcripts` 是 `null` | `ConvaiManager` 尚未初始化                              | 在 `OnEnable` 中订阅并检查 `ConvaiManager.IsBootstrapped` 在访问之前        |
| `已更改` 从不触发                           | 未订阅，或在会话结束后才订阅                                     | 在 `OnEnable` 在房间连接之前；在 `OnDisable`                              |
| `CommittedTurns` 会话期间为空              | 会话尚未开始，或所有回合仍处于活动状态                                | 检查 `ActiveTurns` 在活动对话期间； `CommittedTurns` 会随着回合完成而填充           |
| `GetTurns()` 使用筛选条件时返回 0 个结果         | `PlayerOrCharacterId` 或 `ParticipantId` 与任何参与者都不匹配 | 日志 `turn.Participant.PlayerOrCharacterId` 从 `已更改` 处理程序中查找正确的 ID |
| `WasInterrupted` 从不 `true`           | 会话未使用多说话者模式                                        | `WasInterrupted` 当一位说话者与另一位重叠时，由运行时设置——请确认已配置多说话者。              |

### 下一步

你现在已拥有对房间转录时间线的完整读取权限。若要在场景中显示这些数据，请参见 Transcript UI。若要配置运行时启用的视觉模式，请参见 Settings Panel。

{% content-ref url="/pages/49fed70bb2b468bcc6348096af1fc53fdb0656ce" %}
[转录 UI](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/ui-and-presentation/transcript-ui.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 %}


---

# 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:

```
GET https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/ui-and-presentation/transcript-ui/transcript-history-and-queries.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
