You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kev/Drawer/AI/Models/Store/ConversationStore.cs

134 lines
4.4 KiB
C#

1 month ago
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.SemanticKernel.ChatCompletion;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace AI.Models.Store
{
/// <summary>
/// 会话存储序列化用的 DTOYAML 根为 entries 列表)
/// </summary>
public class ConversationStoreDto
{
public List<ConversationEntryDto> Entries { get; set; } = new();
}
/// <summary>
/// 单条存储条目的 DTO
/// </summary>
public class ConversationEntryDto
{
public string Kind { get; set; } = string.Empty;
public string? Role { get; set; }
public string? Content { get; set; }
public string? Type { get; set; }
public string? Payload { get; set; }
}
/// <summary>
/// 会话级结构化存储,作为 UI 与 Prompt 构建的唯一事实来源。
/// 持有有序的 ConversationEntry 列表,支持追加文本与特殊消息。
/// 每个 ChatSession 持有一份 ConversationStore 实例。
/// </summary>
public class ConversationStore
{
private readonly List<ConversationEntry> _entries = new();
private static readonly ISerializer _yamlSerializer = new SerializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.Build();
private static readonly IDeserializer _yamlDeserializer = new DeserializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.IgnoreUnmatchedProperties()
.Build();
/// <summary>
/// 有序条目列表(只读视图)
/// </summary>
public IReadOnlyList<ConversationEntry> Entries => _entries;
/// <summary>
/// 存储内容变更时触发(用于持久化层按需保存)
/// </summary>
public event EventHandler? StoreChanged;
/// <summary>
/// 追加一条文本消息
/// </summary>
public void AppendText(AuthorRole role, string content)
{
_entries.Add(new TextConversationEntry(role, content ?? string.Empty));
StoreChanged?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// 追加一条特殊消息type + YAML 载荷)
/// </summary>
public void AppendSpecial(string type, string yamlPayload)
{
_entries.Add(new SpecialConversationEntry(type ?? string.Empty, yamlPayload ?? string.Empty));
StoreChanged?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// 清空存储(用于测试或重置)
/// </summary>
public void Clear()
{
_entries.Clear();
}
/// <summary>
/// 移除最后一条条目(用于回滚等)
/// </summary>
/// <returns>是否移除了条目</returns>
public bool RemoveLast()
{
if (_entries.Count == 0) return false;
_entries.RemoveAt(_entries.Count - 1);
return true;
}
/// <summary>
/// 条目数量
/// </summary>
public int Count => _entries.Count;
/// <summary>
/// 序列化为 YAML 字符串(仅 entries 列表,用于嵌入会话文件或单独观察)
/// </summary>
public string ToYaml()
{
var dto = new ConversationStoreDto
{
Entries = ConversationEntryMapper.ToDtoList(_entries)
};
return _yamlSerializer.Serialize(dto);
}
/// <summary>
/// 从 YAML 字符串加载并替换当前存储内容(先清空再按序追加)
/// </summary>
public void LoadFromYaml(string yaml)
{
if (string.IsNullOrWhiteSpace(yaml))
{
Clear();
return;
}
var dto = _yamlDeserializer.Deserialize<ConversationStoreDto>(yaml);
LoadFromEntries(dto?.Entries);
}
/// <summary>
/// 从 DTO 条目列表加载并替换当前存储(先清空再按序追加),用于会话文件反序列化
/// </summary>
public void LoadFromEntries(IEnumerable<ConversationEntryDto>? entries)
{
_entries.Clear();
_entries.AddRange(ConversationEntryMapper.ToEntryList(entries));
}
}
}