using System; using System.Collections.Generic; using System.IO; using AI.Models; using AI.Models.Store; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; namespace AI.Service { /// /// 会话文件持久化 DTO(YAML 格式:元数据 + entries) /// public class SessionFileDto { public string SessionId { get; set; } = string.Empty; public string Title { get; set; } = "新会话"; public DateTime CreatedAt { get; set; } public DateTime UpdatedAt { get; set; } public string WorkflowMode { get; set; } = "Ask"; public List Entries { get; set; } = new(); } /// /// 会话存储的 YAML 文件持久化:按会话 ID 保存到目录下的 {sessionId}.yaml,支持加载全部会话。 /// public class SessionStorage { private readonly string _directory; private static readonly ISerializer _serializer = new SerializerBuilder() .WithNamingConvention(CamelCaseNamingConvention.Instance) .Build(); private static readonly IDeserializer _deserializer = new DeserializerBuilder() .WithNamingConvention(CamelCaseNamingConvention.Instance) .IgnoreUnmatchedProperties() .Build(); public SessionStorage(string? baseDirectory = null) { _directory = string.IsNullOrWhiteSpace(baseDirectory) ? Path.Combine(AppContext.BaseDirectory, "Sessions") : Path.Combine(baseDirectory, "Sessions"); } /// /// 确保会话目录存在 /// public void EnsureDirectory() { if (!Directory.Exists(_directory)) Directory.CreateDirectory(_directory); } /// /// 获取指定会话的 YAML 文件路径 /// public string GetFilePath(string sessionId) { EnsureDirectory(); return Path.Combine(_directory, $"{sessionId}.yaml"); } /// /// 将会话保存为 YAML 文件 /// public void Save(ChatSession session) { if (session == null) return; var dto = new SessionFileDto { SessionId = session.Id, Title = session.Title ?? "新会话", CreatedAt = session.CreatedAt, UpdatedAt = session.UpdatedAt, WorkflowMode = session.WorkflowMode.ToString(), Entries = ConversationEntryMapper.ToDtoList(session.Store.Entries) }; var yaml = _serializer.Serialize(dto); var path = GetFilePath(session.Id); File.WriteAllText(path, yaml); } /// /// 从 YAML 文件加载一个会话;文件不存在或解析失败时返回 null /// public ChatSession? Load(string sessionId) { var path = GetFilePath(sessionId); if (!File.Exists(path)) { return null; } try { var yaml = File.ReadAllText(path); return LoadFromYaml(yaml); } catch { return null; } } /// /// 从 YAML 字符串反序列化并构造 ChatSession(用于单文件加载或测试) /// public ChatSession? LoadFromYaml(string yaml) { if (string.IsNullOrWhiteSpace(yaml)) { return null; } try { var dto = _deserializer.Deserialize(yaml); if (dto == null) { return null; } var store = new ConversationStore(); store.LoadFromEntries(dto.Entries ?? new List()); var mode = WorkflowMode.Ask; if (!string.IsNullOrEmpty(dto.WorkflowMode) && Enum.TryParse(dto.WorkflowMode, true, out var m)) { mode = m; } return new ChatSession( dto.SessionId, dto.Title, dto.CreatedAt, dto.UpdatedAt, mode, store); } catch { return null; } } /// /// 从会话目录加载所有会话文件,返回按 UpdatedAt 倒序的会话列表 /// public IEnumerable LoadAll() { EnsureDirectory(); var list = new List(); foreach (var path in Directory.EnumerateFiles(_directory, "*.yaml")) { try { var yaml = File.ReadAllText(path); var session = LoadFromYaml(yaml); if (session != null) { list.Add(session); } } catch { // 单文件失败则跳过 } } list.Sort((a, b) => b.UpdatedAt.CompareTo(a.UpdatedAt)); return list; } /// /// 删除指定会话的持久化文件 /// public void Delete(string sessionId) { var path = GetFilePath(sessionId); if (File.Exists(path)) File.Delete(path); } } }