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#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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));
}
}
}