|
|
|
|
|
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>
|
|
|
|
|
|
/// 会话存储序列化用的 DTO(YAML 根为 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));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|