using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.ChatCompletion; using System; using System.Linq; using AI.Models.Store; using AI.Utils; namespace AI.Models { /// /// 表示一个聊天会话,包含会话信息和历史记录。 /// 会话存储(Store)为唯一事实来源;History 仅作兼容,发给 LLM 的历史由 GetHistoryForLlm 从 Store 生成。 /// public class ChatSession { /// /// 会话唯一标识符 /// public string Id { get; } /// /// 会话标题(通常从第一条用户消息生成) /// public string Title { get; set; } /// /// 会话创建时间 /// public DateTime CreatedAt { get; } /// /// 最后更新时间 /// public DateTime UpdatedAt { get; private set; } /// /// 对话历史记录(兼容保留;发给 LLM 的请求应使用 GetHistoryForLlm 从 Store 生成) /// public ChatHistory History { get; } /// /// 会话级结构化存储,唯一事实来源。UI 与 Prompt 视图均由此派生。 /// public ConversationStore Store { get; } /// /// 消息数量 /// public int MessageCount => History.Count; /// /// 用户消息数量(用于判断是否是第一条用户消息) /// private int _userMessageCount = 0; /// /// 当前会话的工作模式 /// public WorkflowMode WorkflowMode { get; set; } = WorkflowMode.Ask; /// /// 初始化新的聊天会话 /// /// 会话ID,如果为null则自动生成GUID /// 会话标题 public ChatSession(string? id = null, string? title = null) { Id = id ?? Guid.NewGuid().ToString(); Title = title ?? "新会话"; CreatedAt = DateTime.Now; UpdatedAt = DateTime.Now; History = new ChatHistory(); Store = new ConversationStore(); } /// /// 从持久化数据还原的会话(用于加载已保存的会话文件) /// public ChatSession(string id, string title, DateTime createdAt, DateTime updatedAt, WorkflowMode workflowMode, ConversationStore store) { Id = id ?? Guid.NewGuid().ToString(); Title = title ?? "新会话"; CreatedAt = createdAt; UpdatedAt = updatedAt; History = new ChatHistory(); Store = store ?? new ConversationStore(); WorkflowMode = workflowMode; _userMessageCount = Store.Entries.OfType().Count(t => t.Role == AuthorRole.User); } /// /// 更新会话的最后更新时间 /// public void UpdateTimestamp() { UpdatedAt = DateTime.Now; } /// /// 添加用户消息(同时写入 Store,作为真相源) /// public void AddUserMessage(string message) { History.AddUserMessage(message); Store.AppendText(AuthorRole.User, message); _userMessageCount++; UpdateTimestamp(); if (_userMessageCount == 1 && Title == "新会话") { Title = message.Length > 30 ? message.Substring(0, 30) + "..." : message; } } /// /// 添加助手消息(同时写入 Store) /// public void AddAssistantMessage(string message) { History.AddAssistantMessage(message); Store.AppendText(AuthorRole.Assistant, message); UpdateTimestamp(); } /// /// 向会话存储追加一条特殊消息(不写入 History)。供 ViewModel 在展示表单/参数集/表格/列匹配时调用。 /// public void AppendSpecialEntry(string type, string yamlPayload) { Store.AppendSpecial(type, yamlPayload ?? string.Empty); UpdateTimestamp(); } /// /// 从会话存储生成发给 LLM 的 ChatHistory:仅包含 User/Assistant 文本及可选的 System(guidePrompt)。 /// 特殊消息不追加原始 YAML,可替换为一句短占位(如「[已展示表单:xxx]」)。 /// /// 可选;若提供则在开头插入一条 System 消息 /// 供 KernelService 使用的 ChatHistory,不含原始 YAML public ChatHistory GetHistoryForLlm(string? guidePrompt = null) { var history = new ChatHistory(); if (!string.IsNullOrWhiteSpace(guidePrompt)) { history.AddSystemMessage(guidePrompt); } foreach (var entry in Store.Entries) { switch (entry) { case Store.TextConversationEntry text: AddTextEntry(history, text, guidePrompt); break; case Store.SpecialConversationEntry special: // 卡片展示记录仅用于 UI 重建,不注入 LLM 历史,避免模型从占位文本中学到错误的输出模式 break; } } return history; } /// /// 将 Store 中的一条文本条目按角色加入 ChatHistory,供 GetHistoryForLlm 使用。 /// private static void AddTextEntry(ChatHistory history, Store.TextConversationEntry text, string? guidePrompt) { if (text.Role == AuthorRole.System) { if (string.IsNullOrWhiteSpace(guidePrompt)) { history.AddSystemMessage(text.Content); } } else if (text.Role == AuthorRole.User) { history.AddUserMessage(text.Content); } else if (text.Role == AuthorRole.Assistant) { history.AddAssistantMessage(text.Content); } } /// /// 添加系统消息 /// public void AddSystemMessage(string message) { History.AddSystemMessage(message); Store.AppendText(AuthorRole.System, message); UpdateTimestamp(); } /// /// 仅在需要时确保 History 中存在一条 System(guidePrompt)。不再写入 formsYaml 或摘要。 /// 发给 LLM 的 System 由 GetHistoryForLlm(guidePrompt) 在生成时插入,调用方传入 guidePrompt 即可。 /// /// 引导提示词 /// 已忽略,保留参数以兼容现有调用 public void EnsureFormsYamlInSystem(string guidePrompt, string formsYaml) { if (string.IsNullOrWhiteSpace(guidePrompt)) { return; } if (History.Count == 0 || History[0].Role != AuthorRole.System) { History.Insert(0, new ChatMessageContent(AuthorRole.System, guidePrompt)); } UpdateTimestamp(); } } }