> 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).

# 转录历史与查询

从代码读取当前对话轮次时间线，实时响应轮次变化，并将会话历史导出为文本、Markdown 或 JSON。

`ConvaiManager.ActiveManager.Transcripts` 让你的代码能够以结构化方式访问当前房间会话中的每一次对话轮次。通过 GetTurns 读取当前状态， `CurrentTimeline` 和 `GetTurns`，通过 Subscribe 对实时变化作出响应， `Subscribe` 和 `SubscribeCommitted`，并通过 Export 导出已完成的轮次。 `Export`。此外观层不会控制屏幕上显示的内容——有关这方面请参见 [转录 UI](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/ui-and-presentation/transcript-ui.md) 。如需完整 API 参考，请参见 [转录 API](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/scripting-reference/transcript-api.md).

### 读取当前转录状态

`ConvaiManager.ActiveManager.Transcripts.CurrentTimeline` 返回一个不可变的 `TranscriptTimeline` ——在你读取它的那一刻，对房间中每个轮次的一致视图。反复读取它的成本很低：在运行时发布变更之前，它会返回同一个实例；只有时间线实际发生变化时才会返回新实例。

{% hint style="info" %}
`ConvaiManager.ActiveManager.Transcripts` 会抛出 `InvalidOperationException` 如果 SDK 尚未完成初始化。对于较早运行的代码（`OnEnable`, `Awake`），请使用 `ConvaiManager.ActiveManager.TryGetTranscripts(out ConvaiTranscripts transcripts)` 代替，它返回 `false` 而不是抛出。
{% endhint %}

| 成员               | 类型                                            | 说明                                        |
| ---------------- | --------------------------------------------- | ----------------------------------------- |
| `游标`             | `long`                                        | 单调递增计数器。每当运行时发布更新时都会变化                    |
| `ActiveTurns`    | `IReadOnlyList<TranscriptTurn>`               | 仍在进行中的轮次——尚未提交或中断                         |
| `CommittedTurns` | `IReadOnlyList<TranscriptTurn>`               | 已完成的轮次（`Committed` 或 `被打断` 状态）            |
| `TurnsById`      | `IReadOnlyDictionary<string, TranscriptTurn>` | 按以下内容快速查找： `TranscriptTurn.Id`，涵盖活动和已提交轮次 |
| `Turns`          | `IReadOnlyList<TranscriptTurn>`               | 按以下顺序排列的每个轮次（活动和已提交） `RoomSequence`       |

每个 `TranscriptTurn` 公开 `DisplayText` （已提交文本加上任何进行中的临时文本）， `说话者` （一个 `TranscriptSpeaker` 使用 `类型`, `Id`, `DisplayName`, `ParticipantId`), `StartedAtUtc`, `WasInterrupted`，以及 `Revision` ——一个整数，每当该轮次的存储数据发生变化时就递增一次。请使用 `turn.IsCommitted` 来检查轮次是否已结束，而不是直接比较 `State` 。

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

public class SessionReporter : MonoBehaviour
{
    public string BuildSessionReport()
    {
        TranscriptTimeline timeline = ConvaiManager.ActiveManager.Transcripts.CurrentTimeline;
        var sb = new StringBuilder();

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

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

#### 查找单个轮次

使用 `GetTurn(string turnId)` 用于按 ID 进行 O(1) 查找，或 `GetLatestTurn(TranscriptParticipantRef)` 用于获取某个特定玩家或角色的最新轮次。

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

// 通过其稳定 ID 查找轮次
TranscriptTurn turn = ConvaiManager.ActiveManager.Transcripts.GetTurn(turnId);

// 查找来自特定角色的最新轮次
var characterRef = new TranscriptParticipantRef(
    TranscriptParticipantKind.Character,
    playerOrCharacterId: characterId,
    displayName: characterName);

TranscriptTurn latest = ConvaiManager.ActiveManager.Transcripts.GetLatestTurn(characterRef);
```

这两个方法都会返回 `null` 当不存在匹配的轮次时。

### 使用以下方式筛选轮次： `TranscriptQuery`

将一个 `TranscriptQuery` 为 `GetTurns(query)` 以检索时间线的一个子集。省略查询，或将某个字段留空，将禁用对应筛选。结果按 `RoomSequence`.

| 字段                      | 类型                           | 默认          | 说明                             |
| ----------------------- | ---------------------------- | ----------- | ------------------------------ |
| `ParticipantKind`       | `TranscriptParticipantKind?` | `null` （任意） | 仅限制为 `Player` 或 `Character` 轮次 |
| `PlayerOrCharacterId`   | `string`                     | `null` （任意） | 按 ID 限制为某个特定玩家或角色              |
| `ParticipantId`         | `string`                     | `null` （任意） | 按房间作用域的参与者 ID 限制（多用户房间）        |
| `IncludeActiveTurns`    | `bool`                       | `true`      | 在结果中包含进行中的轮次                   |
| `IncludeCommittedTurns` | `bool`                       | `true`      | 在结果中包含已完成的轮次                   |

`TranscriptQuery.ParticipantKind` 使用 `TranscriptParticipantKind` (`Player` 或 `Character`）。这是一个独立于 `TranscriptSpeakerType` (`Player`, `Character`，或 `System`）的枚举，它出现在 `TranscriptSpeaker` 和 `TranscriptSubscriptionOptions` 本页下方。

```csharp
using Convai.Domain.Models;
using Convai.Runtime.Components;
using System.Collections.Generic;

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

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

### 响应实时转录变化

调用 `Subscribe(callback, options)` 以接收一个 `TranscriptChange` 每当匹配的轮次发生变化时。 `Subscribe` 返回一个 `IDisposable` ——释放它即可取消订阅，通常在 `OnDisable`.

`TranscriptChange.Kind` 会告诉你发生了什么； `TranscriptChange.Turn` 是更新后的轮次，或者 `null` 当 `Kind` 是 `Removed` （读取 `TurnId` ）在这种情况下。

| `TranscriptChangeKind` | 含义                         |
| ---------------------- | -------------------------- |
| `Added`                | 该轮次首次出现在时间线中               |
| `Updated`              | 轮次仍处于活动状态时，其文本发生了变化（流式或稳定） |
| `Committed`            | 轮次正常完成                     |
| `被打断`                  | 轮次因其他发言者打断而完成              |
| `Corrected`            | 先前发送的该轮次文本被追溯更正            |
| `Removed`              | 该轮次已从时间线中移除； `轮次` 是 `null` |

`TranscriptSubscriptionOptions` 控制哪些轮次和变更类型会到达你的回调：

| 字段                | 类型                       | 默认          | 说明                                       |
| ----------------- | ------------------------ | ----------- | ---------------------------------------- |
| `ReplayExisting`  | `bool`                   | `false`     | 在实时更新开始前，立即为每个当前匹配的轮次调用回调                |
| `IncludeActive`   | `bool`                   | `true`      | 包含尚未完成的轮次变更                              |
| `IncludeTerminal` | `bool`                   | `true`      | 包含已提交、中断或更正的轮次变更                         |
| `SpeakerType`     | `TranscriptSpeakerType?` | `null` （任意） | 仅限制为 `Player`, `Character`，或 `System` 轮次 |
| `SpeakerId`       | `string`                 | `null` （任意） | 按 ID 限制为某个特定玩家或角色                        |
| `ParticipantId`   | `string`                 | `null` （任意） | 按房间作用域的参与者 ID 限制（多用户房间）                  |

`SubscribeCommitted(callback, options)` 是对以下内容的便捷封装： `Subscribe` 它强制设置 `IncludeActive = false` 和 `IncludeTerminal = true`，因此回调只会在轮次完成后触发——当你只关心已完成历史时使用，例如聊天记录或评分系统。

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

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

    private ConvaiTranscripts _transcripts;
    private IDisposable _subscription;
    private int _wordCount;

    private void OnEnable()
    {
        ConvaiManager manager = ConvaiManager.ActiveManager;
        if (manager == null || !manager.TryGetTranscripts(out _transcripts)) return;

        _subscription = _transcripts.SubscribeCommitted(
            OnPlayerTurnCommitted,
            new TranscriptSubscriptionOptions { SpeakerType = TranscriptSpeakerType.Player });
    }

    private void OnDisable()
    {
        _subscription?.Dispose();
        _subscription = null;
    }

    private void OnPlayerTurnCommitted(TranscriptChange change)
    {
        if (change.Turn == null) return;
        if (change.Kind != TranscriptChangeKind.Committed && change.Kind != TranscriptChangeKind.Interrupted) return;

        _wordCount += change.Turn.DisplayText.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
        _wordCountLabel.text = $"已说单词数：{_wordCount}";
    }
}
```

对于需要立即查看之前轮次的后加入观看者，请将 `ReplayExisting = true` 设置在传递给 `Subscribe` ——在实时更新开始前，匹配的轮次会先向你的回调传递一次。

### 导出并清空转录历史

`Export(TranscriptExportFormat format)` 将按以下顺序序列化每个已提交轮次： `RoomSequence`，并合并为一个字符串。它读取自 `CurrentTimeline.CommittedTurns` ——活动轮次绝不会包含在内。

| `TranscriptExportFormat` | 输出                                                      |
| ------------------------ | ------------------------------------------------------- |
| `PlainText`              | 每个轮次一行： `"{speaker}: {turn.DisplayText}"`               |
| `Markdown`               | 每个轮次一行： `"**{speaker}:** {turn.DisplayText}"`，轮次之间留一个空行 |
| `Json`                   | 一个带缩进的 JSON 数组，由 `TranscriptTurn` 对象                    |

该 `{speaker}` 的值是 `turn.Speaker.DisplayName`，或 `turn.Speaker.Type` 当 `DisplayName` 为空。

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

public class SessionExporter : MonoBehaviour
{
    public void ExportSessionToDisk(string filePath)
    {
        string json = ConvaiManager.ActiveManager.Transcripts.Export(TranscriptExportFormat.Json);
        File.WriteAllText(filePath, json);
    }
}
```

调用 `Clear()` 以从规范房间历史中移除每个轮次：

```csharp
ConvaiManager.ActiveManager.Transcripts.Clear();
```

{% hint style="warning" %}
`Clear()` 会清除规范房间历史， `CurrentTimeline`, `GetTurns`，以及 `Export` 所有读取都来自该历史——这与清空聊天面板的可视显示不同，而且无法撤销。
{% endhint %}

### 故障排除

| 症状                                                                        | 可能原因                                                                                                            | 修复方法                                                                                                          | 验证                                               |
| ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
| `ConvaiManager.ActiveManager.Transcripts` 会抛出 `InvalidOperationException` | 在以下操作之前访问： `ConvaiManager` 完成初始化                                                                                | 使用 `ConvaiManager.ActiveManager.TryGetTranscripts(out var transcripts)`，或检查 `ConvaiManager.IsBootstrapped` 首先 | `TryGetTranscripts` 返回 `true` 和 `transcripts` 非空 |
| `Subscribe` 回调从不触发                                                        | 在房间会话已经结束后才订阅，或者 `TranscriptSubscriptionOptions` 筛选条件排除了所有轮次                                                    | 请在房间连接之前或刚连接后订阅；确认 `SpeakerType`, `SpeakerId`，以及 `ParticipantId` 与实际参与者匹配                                     | 订阅后，回调会在下一次轮次变化时触发                               |
| `GetTurns()` 在设置了查询时返回空列表                                                 | `PlayerOrCharacterId` 或 `ParticipantId` 不匹配任何参与者，或者两者都 `IncludeActiveTurns` 和 `IncludeCommittedTurns` 是 `false` | 日志 `turn.Speaker.Id` 和 `turn.Speaker.ParticipantId` 来自未过滤的 `Subscribe` 回调以找到正确的值                              | `GetTurns()` 使用更正后的查询会返回非空列表                     |
| `change.Turn` 是 `null` 在一个 `Subscribe` 回调的协程                              | `change.Kind` 是 `TranscriptChangeKind.Removed`                                                                  | 读取 `change.TurnId` 而不是 `change.Turn` 当 `Kind` 是 `Removed`                                                     | 在移除时，回调不再抛出空引用异常                                 |
| `Export(TranscriptExportFormat.PlainText)` 返回空字符串                         | 尚无轮次提交                                                                                                          | `Export` 仅仅读取 `CurrentTimeline.CommittedTurns`；请等待轮次完成，或检查 `CommittedTurns.Count` 首先                          | `CurrentTimeline.CommittedTurns.Count` 在导出前是否大于零 |

### 下一步

你现在已拥有对房间转录时间线、实时变更通知以及导出支持的完整读取权限。有关本页背后的完整 API 表面，请参见 Transcript API 参考。若要在场景中显示这些数据，请参见 Transcript UI。若要配置运行时启用的可视模式，请参见 Settings Panel。

{% content-ref url="/pages/1a33f148fac3ddf7352a86abb1f67ce91973142c" %}
[转录 API](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/scripting-reference/transcript-api.md)
{% endcontent-ref %}

{% 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 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/transcript-history-and-queries.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.
