## 核心功能 - 双记忆系统合并:picoclaw MEMORY.md + hxclaw 会话摘要 - 独立上下文系统:不依赖 picoclaw session - 向量检索:硅基流动 BGE-M3 API - 三重检测:关键词/向量相似度/命令 ## 数据库 - libSQL (TursoDB) 存储 - sessions + chats 表设计 - 向量存储使用 binary 编码 ## 查询场景 - RecallHistory: 查询所有会话摘要 - RecallTopic: 按话题向量检索 - RecallSession: 指定会话详情 - RecallWithinSession: 会话内检索 ## 导出 - MongoDB 风格:~/.config/hxclaw/export-data.json - chats 嵌套在 sessions 下 - 增量导出,同 session 累加 ## UI 优化 - 合并状态显示(耗时 · 状态 · 消息数) - 颜色设计:金色图标 + 暗绿色/暗红色状态 ## 配置项 - memory.recall: keywords, auto_recall, similarity_threshold - memory.vector: max_search_results - memory.auto_export
This commit is contained in:
@@ -20,6 +20,8 @@ type Config struct {
|
||||
UI UIConfig `yaml:"ui"`
|
||||
// TTS TTS 语音配置
|
||||
TTS TTSConfig `yaml:"tts"`
|
||||
// Memory 聊天记忆体配置
|
||||
Memory MemoryConfig `yaml:"memory"`
|
||||
}
|
||||
|
||||
// StreamingConfig 流式输出配置,控制模拟打字效果的延迟时间
|
||||
@@ -56,6 +58,48 @@ type TTSConfig struct {
|
||||
Auto bool `yaml:"auto"`
|
||||
}
|
||||
|
||||
// MemoryConfig 聊天记忆体配置
|
||||
type MemoryConfig struct {
|
||||
// Enabled 启用开关
|
||||
Enabled bool `yaml:"enabled"`
|
||||
// DBPath 数据库路径
|
||||
DBPath string `yaml:"db_path"`
|
||||
// AutoSession 自动创建 Session
|
||||
AutoSession bool `yaml:"auto_session"`
|
||||
// AutoExport 退出时自动导出
|
||||
AutoExport bool `yaml:"auto_export"`
|
||||
// Vector 向量服务配置
|
||||
Vector VectorConfig `yaml:"vector"`
|
||||
// Recall 检索配置
|
||||
Recall RecallConfig `yaml:"recall"`
|
||||
}
|
||||
|
||||
// VectorConfig 向量服务配置
|
||||
type VectorConfig struct {
|
||||
// APIKey 硅基流动 API Key
|
||||
APIKey string `yaml:"api_key"`
|
||||
// BaseURL API 地址
|
||||
BaseURL string `yaml:"base_url"`
|
||||
// Model 向量模型
|
||||
Model string `yaml:"model"`
|
||||
// Dimension <20><>量维度
|
||||
Dimension int `yaml:"dimension"`
|
||||
// MaxSearchResults 最大检索结果数
|
||||
MaxSearchResults int `yaml:"max_search_results"`
|
||||
}
|
||||
|
||||
// RecallConfig 检索配置
|
||||
type RecallConfig struct {
|
||||
// Keywords 触发关键词列表
|
||||
Keywords []string `yaml:"keywords"`
|
||||
// AutoRecall 是否自动检测相似度
|
||||
AutoRecall bool `yaml:"auto_recall"`
|
||||
// SimilarityThreshold 相似度阈值
|
||||
SimilarityThreshold float64 `yaml:"similarity_threshold"`
|
||||
// MaxResults 最大检索结果数
|
||||
MaxResults int `yaml:"max_results"`
|
||||
}
|
||||
|
||||
var (
|
||||
// defaultCfg 默认配置值,当配置文件不存在或字段为空时使用
|
||||
defaultCfg = Config{
|
||||
@@ -76,6 +120,25 @@ var (
|
||||
Port: 9876,
|
||||
Auto: true,
|
||||
},
|
||||
Memory: MemoryConfig{
|
||||
Enabled: true,
|
||||
DBPath: "",
|
||||
AutoSession: true,
|
||||
AutoExport: true,
|
||||
Vector: VectorConfig{
|
||||
APIKey: "",
|
||||
BaseURL: "https://api.siliconflow.cn/v1",
|
||||
Model: "BAAI/bge-m3",
|
||||
Dimension: 1024,
|
||||
MaxSearchResults: 10,
|
||||
},
|
||||
Recall: RecallConfig{
|
||||
Keywords: []string{"之前", "聊过", "记得", "找找", "曾经", "谈论过", "提过"},
|
||||
AutoRecall: true,
|
||||
SimilarityThreshold: 0.7,
|
||||
MaxResults: 5,
|
||||
},
|
||||
},
|
||||
}
|
||||
cfg *Config // 已合并的配置(用户配置 + 项目配置)
|
||||
cfgLock sync.RWMutex // 配置读写锁
|
||||
@@ -83,8 +146,7 @@ var (
|
||||
|
||||
// 用户配置文件路径常量
|
||||
const (
|
||||
userConfigDir = ".config/hxclaw" // 用户配置目录(相对于用户家目录)
|
||||
userConfigFile = "config.yml" // 用户配置文件名
|
||||
userConfigFile = "config.yml" // 用户配置文件名
|
||||
)
|
||||
|
||||
// LoadProjectConfig 加载并合并项目配置
|
||||
@@ -142,8 +204,8 @@ func createUserConfig(userPath string) error {
|
||||
|
||||
# Markdown 渲染配置
|
||||
markdown:
|
||||
theme: dark # 渲染主题:dark, light, dracula, tokyo-night 等
|
||||
line_width: 0 # 自动换行宽度(-1=禁用,0=自动,>0=固定宽度)
|
||||
theme: dark
|
||||
line_width: 0
|
||||
|
||||
# UI 配置
|
||||
ui:
|
||||
@@ -152,8 +214,25 @@ ui:
|
||||
|
||||
# TTS 语音配置
|
||||
tts:
|
||||
enabled: false # 全局开关(默认关闭)
|
||||
auto: true # AI 回复后自动朗读
|
||||
enabled: false
|
||||
auto: true
|
||||
|
||||
# 聊天记忆体配置
|
||||
memory:
|
||||
enabled: true
|
||||
auto_session: true
|
||||
auto_export: true
|
||||
vector:
|
||||
api_key: ""
|
||||
base_url: "https://api.siliconflow.cn/v1"
|
||||
model: "BAAI/bge-m3"
|
||||
dimension: 1024
|
||||
max_search_results: 10
|
||||
recall:
|
||||
keywords: ["之前", "聊过", "记得", "找找", "曾经", "谈论过", "提过"]
|
||||
auto_recall: true
|
||||
similarity_threshold: 0.7
|
||||
max_results: 5
|
||||
`
|
||||
return os.WriteFile(userPath, []byte(defaultContent), 0644)
|
||||
}
|
||||
@@ -161,7 +240,7 @@ tts:
|
||||
// loadProjectConfig 加载项目级配置文件
|
||||
// 路径优先级:环境变量 HXCLAW_CONFIG 指定路径 > ./project.config.yml
|
||||
func loadProjectConfig() *Config {
|
||||
cfgPath := getProjectConfigPath()
|
||||
cfgPath := GetProjectConfigPath()
|
||||
if cfgPath == "" {
|
||||
return &defaultCfg
|
||||
}
|
||||
@@ -210,6 +289,47 @@ func mergeConfig(userCfg, projCfg *Config) *Config {
|
||||
if projCfg.TTS.Port > 0 {
|
||||
result.TTS.Port = projCfg.TTS.Port
|
||||
}
|
||||
// Memory 配置
|
||||
if projCfg.Memory.Enabled {
|
||||
result.Memory.Enabled = projCfg.Memory.Enabled
|
||||
}
|
||||
if projCfg.Memory.DBPath != "" {
|
||||
result.Memory.DBPath = projCfg.Memory.DBPath
|
||||
}
|
||||
if projCfg.Memory.AutoSession {
|
||||
result.Memory.AutoSession = projCfg.Memory.AutoSession
|
||||
}
|
||||
if projCfg.Memory.AutoExport {
|
||||
result.Memory.AutoExport = projCfg.Memory.AutoExport
|
||||
}
|
||||
if projCfg.Memory.Vector.APIKey != "" {
|
||||
result.Memory.Vector.APIKey = projCfg.Memory.Vector.APIKey
|
||||
}
|
||||
if projCfg.Memory.Vector.BaseURL != "" {
|
||||
result.Memory.Vector.BaseURL = projCfg.Memory.Vector.BaseURL
|
||||
}
|
||||
if projCfg.Memory.Vector.Model != "" {
|
||||
result.Memory.Vector.Model = projCfg.Memory.Vector.Model
|
||||
}
|
||||
if projCfg.Memory.Vector.Dimension > 0 {
|
||||
result.Memory.Vector.Dimension = projCfg.Memory.Vector.Dimension
|
||||
}
|
||||
if projCfg.Memory.Vector.MaxSearchResults > 0 {
|
||||
result.Memory.Vector.MaxSearchResults = projCfg.Memory.Vector.MaxSearchResults
|
||||
}
|
||||
// Recall 配置
|
||||
if len(projCfg.Memory.Recall.Keywords) > 0 {
|
||||
result.Memory.Recall.Keywords = projCfg.Memory.Recall.Keywords
|
||||
}
|
||||
if projCfg.Memory.Recall.AutoRecall {
|
||||
result.Memory.Recall.AutoRecall = projCfg.Memory.Recall.AutoRecall
|
||||
}
|
||||
if projCfg.Memory.Recall.SimilarityThreshold > 0 {
|
||||
result.Memory.Recall.SimilarityThreshold = projCfg.Memory.Recall.SimilarityThreshold
|
||||
}
|
||||
if projCfg.Memory.Recall.MaxResults > 0 {
|
||||
result.Memory.Recall.MaxResults = projCfg.Memory.Recall.MaxResults
|
||||
}
|
||||
}
|
||||
|
||||
// 再应用用户配置(覆盖项目配置)
|
||||
@@ -238,6 +358,48 @@ func mergeConfig(userCfg, projCfg *Config) *Config {
|
||||
if userCfg.TTS.Auto {
|
||||
result.TTS.Auto = userCfg.TTS.Auto
|
||||
}
|
||||
// Memory 配置(用户配置优先)
|
||||
if userCfg.Memory.Enabled {
|
||||
result.Memory.Enabled = userCfg.Memory.Enabled
|
||||
}
|
||||
if userCfg.Memory.DBPath != "" {
|
||||
result.Memory.DBPath = userCfg.Memory.DBPath
|
||||
}
|
||||
if userCfg.Memory.AutoSession {
|
||||
result.Memory.AutoSession = userCfg.Memory.AutoSession
|
||||
}
|
||||
if userCfg.Memory.AutoExport {
|
||||
result.Memory.AutoExport = userCfg.Memory.AutoExport
|
||||
}
|
||||
// 向量 API Key 只能在用户配置中指定
|
||||
if userCfg.Memory.Vector.APIKey != "" {
|
||||
result.Memory.Vector.APIKey = userCfg.Memory.Vector.APIKey
|
||||
}
|
||||
if userCfg.Memory.Vector.BaseURL != "" {
|
||||
result.Memory.Vector.BaseURL = userCfg.Memory.Vector.BaseURL
|
||||
}
|
||||
if userCfg.Memory.Vector.Model != "" {
|
||||
result.Memory.Vector.Model = userCfg.Memory.Vector.Model
|
||||
}
|
||||
if userCfg.Memory.Vector.Dimension > 0 {
|
||||
result.Memory.Vector.Dimension = userCfg.Memory.Vector.Dimension
|
||||
}
|
||||
if userCfg.Memory.Vector.MaxSearchResults > 0 {
|
||||
result.Memory.Vector.MaxSearchResults = userCfg.Memory.Vector.MaxSearchResults
|
||||
}
|
||||
// Recall 配置
|
||||
if len(userCfg.Memory.Recall.Keywords) > 0 {
|
||||
result.Memory.Recall.Keywords = userCfg.Memory.Recall.Keywords
|
||||
}
|
||||
if userCfg.Memory.Recall.AutoRecall {
|
||||
result.Memory.Recall.AutoRecall = userCfg.Memory.Recall.AutoRecall
|
||||
}
|
||||
if userCfg.Memory.Recall.SimilarityThreshold > 0 {
|
||||
result.Memory.Recall.SimilarityThreshold = userCfg.Memory.Recall.SimilarityThreshold
|
||||
}
|
||||
if userCfg.Memory.Recall.MaxResults > 0 {
|
||||
result.Memory.Recall.MaxResults = userCfg.Memory.Recall.MaxResults
|
||||
}
|
||||
}
|
||||
|
||||
return &result
|
||||
@@ -256,21 +418,16 @@ func GetProjectConfig() *Config {
|
||||
|
||||
// getUserConfigPath 获取用户配置文件路径
|
||||
// 路径格式:~/.config/hxclaw/config.yml
|
||||
// 支持 Windows (USERPROFILE) 和 Unix (HOME) 环境变量
|
||||
func getUserConfigPath() string {
|
||||
homeDir := os.Getenv("USERPROFILE")
|
||||
if homeDir == "" {
|
||||
homeDir = os.Getenv("HOME")
|
||||
}
|
||||
if homeDir == "" {
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(homeDir, userConfigDir, userConfigFile)
|
||||
return GetConfigFile()
|
||||
}
|
||||
|
||||
// getProjectConfigPath 获取项目配置文件路径
|
||||
// 优先使用环境变量 HXCLAW_CONFIG 指定的路径,否则使用当前目录的 project.config.yml
|
||||
func getProjectConfigPath() string {
|
||||
// GetUserConfigDir 获取用户配置目录
|
||||
func GetUserConfigDir() string {
|
||||
return GetConfigDir()
|
||||
}
|
||||
|
||||
func GetProjectConfigPath() string {
|
||||
if path := os.Getenv("HXCLAW_CONFIG"); path != "" {
|
||||
return path
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"charm.land/lipgloss/v2"
|
||||
|
||||
"github.com/hxclaw/hxclaw/cmd/hxclaw/internal"
|
||||
"github.com/hxclaw/hxclaw/cmd/hxclaw/internal/memory"
|
||||
"github.com/muesli/termenv"
|
||||
"github.com/sipeed/picoclaw/pkg/agent"
|
||||
"github.com/sipeed/picoclaw/pkg/bus"
|
||||
@@ -20,6 +21,8 @@ import (
|
||||
|
||||
const Logo = "🦐"
|
||||
|
||||
var currentSession *memory.Session
|
||||
|
||||
func main() {
|
||||
if err := internal.LoadProjectConfig(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "错误:加载项目配置失败: %v\n", err)
|
||||
@@ -61,6 +64,26 @@ func main() {
|
||||
"skills_available": startupInfo["skills"].(map[string]any)["available"],
|
||||
})
|
||||
|
||||
memoryCfg := internal.GetProjectConfig().Memory
|
||||
if memoryCfg.Enabled {
|
||||
// 优先使用用户配置中的 db_path,如果没有则使用默认路径
|
||||
dbPath := memoryCfg.DBPath
|
||||
if dbPath == "" {
|
||||
dbPath = memory.GetDefaultDBPath()
|
||||
}
|
||||
fmt.Printf("初始化记忆体,db_path: %s\n", dbPath)
|
||||
if err := memory.Init(memory.WithDBPath(dbPath)); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "警告:初始化记忆体失败: %v,将使用无记忆模式\n", err)
|
||||
} else {
|
||||
fmt.Println("记忆体初始化成功")
|
||||
memory.InitVector(
|
||||
memory.WithAPIKey(memoryCfg.Vector.APIKey),
|
||||
memory.WithBaseURL(memoryCfg.Vector.BaseURL),
|
||||
memory.WithModel(memoryCfg.Vector.Model),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("%s Interactive mode (Ctrl+C to exit)\n\n", Logo)
|
||||
interactiveMode(agentLoop, "cli:default")
|
||||
}
|
||||
@@ -88,6 +111,7 @@ func interactiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
||||
if err != nil {
|
||||
if err == internal.ErrInterrupt || err == internal.ErrEOF {
|
||||
fmt.Println("\n再见!")
|
||||
memory.ExportIfNeeded()
|
||||
return
|
||||
}
|
||||
fmt.Printf("读取输入错误: %v\n", err)
|
||||
@@ -101,6 +125,7 @@ func interactiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
||||
|
||||
if input == "exit" || input == "quit" {
|
||||
fmt.Println("再见!")
|
||||
memory.ExportIfNeeded()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -116,6 +141,21 @@ func interactiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(input, "/new") {
|
||||
handleNewSessionCommand(rl, basePrompt)
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(input, "/memory") {
|
||||
handleMemoryCommand(input)
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(input, "/sessions") {
|
||||
handleSessionsCommand()
|
||||
continue
|
||||
}
|
||||
|
||||
if isTempTTS {
|
||||
enabled := internal.ToggleTTS()
|
||||
if enabled {
|
||||
@@ -139,6 +179,7 @@ func simpleInteractiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
||||
if err != nil {
|
||||
if err == internal.ErrEOF {
|
||||
fmt.Println("\n再见!")
|
||||
memory.ExportIfNeeded()
|
||||
return
|
||||
}
|
||||
fmt.Printf("读取输入错误: %v\n", err)
|
||||
@@ -152,6 +193,7 @@ func simpleInteractiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
||||
|
||||
if input == "exit" || input == "quit" {
|
||||
fmt.Println("再见!")
|
||||
memory.ExportIfNeeded()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -167,6 +209,21 @@ func simpleInteractiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(input, "/new") {
|
||||
handleNewSessionCommand(nil, internal.GetProjectConfig().UI.UserIcon)
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(input, "/memory") {
|
||||
handleMemoryCommand(input)
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(input, "/sessions") {
|
||||
handleSessionsCommand()
|
||||
continue
|
||||
}
|
||||
|
||||
if isTempTTS {
|
||||
internal.ToggleTTS()
|
||||
}
|
||||
@@ -179,6 +236,18 @@ func simpleInteractiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
||||
func runWithStreaming(agentLoop *agent.AgentLoop, input, sessionKey string, tempTTS bool) {
|
||||
startTime := time.Now()
|
||||
|
||||
// 保存原始输入用于后续保存
|
||||
originalInput := input
|
||||
|
||||
// 注入 hxclaw 的上下文摘要
|
||||
memoryCfg := internal.GetProjectConfig().Memory
|
||||
if memoryCfg.Enabled {
|
||||
contextPrompt := memory.GetContextPrompt(input)
|
||||
if contextPrompt != "" {
|
||||
input = contextPrompt + "\n用户新问题: " + input
|
||||
}
|
||||
}
|
||||
|
||||
spinner := internal.NewSpinner("思考中...")
|
||||
spinner.Start()
|
||||
|
||||
@@ -187,7 +256,11 @@ func runWithStreaming(agentLoop *agent.AgentLoop, input, sessionKey string, temp
|
||||
spinner.Stop()
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("错误: %v\n", err)
|
||||
fmt.Printf("警告: %v\n", err)
|
||||
}
|
||||
|
||||
if resp == "" {
|
||||
fmt.Println("(空响应,跳过保存)")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -200,8 +273,16 @@ func runWithStreaming(agentLoop *agent.AgentLoop, input, sessionKey string, temp
|
||||
go internal.SpeakText(resp)
|
||||
}
|
||||
|
||||
// 保存聊天记录到数据库
|
||||
var chatCount int
|
||||
var saveErr error
|
||||
memoryCfg = internal.GetProjectConfig().Memory
|
||||
if memoryCfg.Enabled {
|
||||
chatCount, saveErr = memory.SaveChat(originalInput, resp, !memory.ShouldSkipSummaryUpdate(originalInput))
|
||||
}
|
||||
|
||||
elapsed := time.Since(startTime)
|
||||
printElapsed(elapsed)
|
||||
printElapsed(elapsed, chatCount, saveErr)
|
||||
}
|
||||
|
||||
func clearSpinnerLine() {
|
||||
@@ -242,17 +323,33 @@ func outputLineByLine(text string) {
|
||||
}
|
||||
|
||||
var (
|
||||
iconStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#f0c75e"))
|
||||
textStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#2b2e32"))
|
||||
iconStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#f0c75e"))
|
||||
textStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#2b2e32"))
|
||||
memoryOkStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#4a9e6b")) // 暗绿色
|
||||
memoryErrStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#c75050")) // 暗红色
|
||||
)
|
||||
|
||||
func printElapsed(elapsed time.Duration) {
|
||||
func printElapsed(elapsed time.Duration, chatCount int, saveErr error) {
|
||||
elapsedSec := math.Round(elapsed.Seconds()*10) / 10
|
||||
elapsedStr := formatDuration(elapsedSec)
|
||||
|
||||
icon := iconStyle.Render("▣ ")
|
||||
text := textStyle.Render(fmt.Sprintf("耗时: %s", elapsedStr))
|
||||
fmt.Printf(" %s%s\n\n", icon, text)
|
||||
timeText := textStyle.Render(fmt.Sprintf("耗时: %s", elapsedStr))
|
||||
|
||||
var statusText string
|
||||
if saveErr != nil {
|
||||
statusText = memoryErrStyle.Render("会话保存异常")
|
||||
} else if chatCount > 0 {
|
||||
statusText = memoryOkStyle.Render("会话已保存")
|
||||
}
|
||||
|
||||
memCountText := textStyle.Render(fmt.Sprintf("当前会话 %d 条消息", chatCount))
|
||||
|
||||
if statusText != "" {
|
||||
fmt.Printf(" %s%s · %s · %s\n\n", icon, timeText, statusText, memCountText)
|
||||
} else {
|
||||
fmt.Printf(" %s%s · %s\n\n", icon, timeText, memCountText)
|
||||
}
|
||||
}
|
||||
|
||||
func formatTokens(n int) string {
|
||||
@@ -331,3 +428,88 @@ func handleTTSCommandSimple(input string) {
|
||||
fmt.Println("用法: /tts [on|off|status]")
|
||||
}
|
||||
}
|
||||
|
||||
func handleNewSessionCommand(rl *internal.Readline, basePrompt string) {
|
||||
uuid, err := memory.CreateNewSession()
|
||||
if err != nil {
|
||||
fmt.Printf("创建新会话失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("已创建新会话: %s\n", uuid)
|
||||
currentSession = nil
|
||||
}
|
||||
|
||||
func handleMemoryCommand(input string) {
|
||||
args := strings.Fields(input)
|
||||
if len(args) == 1 || args[1] == "list" {
|
||||
sessions, err := memory.ListSessions()
|
||||
if err != nil {
|
||||
fmt.Printf("查询会话失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
if len(sessions) == 0 {
|
||||
fmt.Println("暂无会话记录")
|
||||
return
|
||||
}
|
||||
fmt.Printf("共有 %d 个会话记录:\n", len(sessions))
|
||||
for _, s := range sessions {
|
||||
summary := s.Summary
|
||||
if summary == "" {
|
||||
summary = "(无摘要)"
|
||||
} else if len(summary) > 50 {
|
||||
summary = summary[:50] + "..."
|
||||
}
|
||||
fmt.Printf(" - %s: %s\n", s.UUID[:8], summary)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch args[1] {
|
||||
case "show":
|
||||
session := memory.GetCurrentSession()
|
||||
if session == nil {
|
||||
latest, err := memory.GetLatestSession()
|
||||
if err != nil || latest == nil {
|
||||
fmt.Println("暂无会话记录")
|
||||
return
|
||||
}
|
||||
session = latest
|
||||
}
|
||||
if session.Summary == "" {
|
||||
fmt.Println("当前会话暂无摘要")
|
||||
return
|
||||
}
|
||||
fmt.Printf("=== 会话摘要 (%s) ===\n%s\n", session.UUID[:8], session.Summary)
|
||||
case "current":
|
||||
if currentSession == nil {
|
||||
fmt.Println("当前无活跃会话")
|
||||
return
|
||||
}
|
||||
fmt.Printf("当前会话: %s\n", currentSession.UUID)
|
||||
fmt.Printf("聊天记录数: %d\n", len(currentSession.ChatIDs))
|
||||
default:
|
||||
fmt.Println("用法: /memory [list|show|current]")
|
||||
}
|
||||
}
|
||||
|
||||
func handleSessionsCommand() {
|
||||
sessions, err := memory.ListSessions()
|
||||
if err != nil {
|
||||
fmt.Printf("查询会话失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
if len(sessions) == 0 {
|
||||
fmt.Println("暂无会话记录")
|
||||
return
|
||||
}
|
||||
fmt.Printf("共有 %d 个会话记录:\n", len(sessions))
|
||||
for _, s := range sessions {
|
||||
summary := s.Summary
|
||||
if summary == "" {
|
||||
summary = "(无摘要)"
|
||||
} else if len(summary) > 30 {
|
||||
summary = summary[:30] + "..."
|
||||
}
|
||||
fmt.Printf(" %s | %d 条消息 | %s\n", s.UUID[:8], len(s.ChatIDs), summary)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user