> 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.ActiveManager.Transcripts` 为你的代码提供对当前房间会话中每一轮对话的结构化访问。通过以下方式读取当前状态 `CurrentTimeline` 以及 `GetTurns`，通过以下方式响应实时变化 `Subscribe` 以及 `SubscribeCommitted`，并通过以下方式导出已完成的轮次 `Export`。这个门面不会控制屏幕上显示的内容——请参见 [Transcript UI](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/ui-and-presentation/transcript-ui.md) 。完整 API 参考请参见 [Transcript 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 %}

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

每个 `TranscriptTurn` 都暴露 `DisplayText` （已提交文本加上任何进行中的临时文本）， `Speaker` （一个 `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`            | 该轮次正常完成                      |
| `Interrupted`          | 该轮次因另一位说话者打断而完成              |
| `Corrected`            | 先前传递的该轮次文本被追溯修正              |
| `Removed`              | 该轮次已从时间线中移除； `Turn` 为 `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 范围，请参阅《转录 API 参考》。有关在场景中显示此数据，请参阅《转录 UI》。有关配置运行时处于活动状态的可视模式，请参阅《设置面板》。

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