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