fix: 前端消息拆分+动作消息样式+DevTools自主思考状态保持+记忆表名修复

- 侧边栏底部 "昔涟 AI" 改为 "昔涟"
- 暂时禁用消息朗读按钮
- 修复前端 response 处理器:支持 gateway 发送的 content+role+msg_type 字段,
  使动作消息(括号内容)正确拆分为独立的 ActionMessageBubble 显示
- 修复 DevTools 自主思考面板:5秒自动刷新后展开的思考日志不再自动折叠
- 修复 memory-service 表名不一致:memory_entries → memories,
  解决 DevTools 记忆管理页面查不到 admin 用户记忆的问题
- 修复 sessionStore 解析历史消息时 msgType 未定义引用

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-23 10:50:42 +08:00
parent 26a5c69aba
commit 31be1b71eb
14 changed files with 442 additions and 154 deletions
@@ -529,7 +529,8 @@ func (h *MemoryHandler) handleThinkingStats(w http.ResponseWriter, r *http.Reque
return
}
stats, err := h.svc.GetThinkingStats(r.Context())
userID := r.URL.Query().Get("user_id")
stats, err := h.svc.GetThinkingStats(r.Context(), userID)
if err != nil {
log.Printf("[memory-handler] 获取思考日志统计失败: %v", err)
writeError(w, http.StatusInternalServerError, err.Error())
@@ -537,6 +538,9 @@ func (h *MemoryHandler) handleThinkingStats(w http.ResponseWriter, r *http.Reque
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"stats": stats,
"total_logs": stats.TotalLogs,
"total_tool_calls": stats.TotalToolCalls,
"avg_content_length": stats.AvgContentLen,
"latest_at": stats.LatestAt,
})
}
@@ -316,6 +316,6 @@ func (svc *MemoryService) GetThinkingLogByID(ctx context.Context, id string) (*m
}
// GetThinkingStats 获取思考日志统计
func (svc *MemoryService) GetThinkingStats(ctx context.Context) (*model.ThinkingStats, error) {
return svc.store.GetThinkingStats(ctx)
func (svc *MemoryService) GetThinkingStats(ctx context.Context, userID string) (*model.ThinkingStats, error) {
return svc.store.GetThinkingStats(ctx, userID)
}
+38 -33
View File
@@ -144,7 +144,7 @@ func (s *Store) getDB() *sql.DB {
func (s *Store) migrate() error {
queries := []string{
`CREATE EXTENSION IF NOT EXISTS vector`,
`CREATE TABLE IF NOT EXISTS memory_entries (
`CREATE TABLE IF NOT EXISTS memories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id VARCHAR(64) NOT NULL,
content TEXT NOT NULL,
@@ -163,23 +163,23 @@ func (s *Store) migrate() error {
expires_at TIMESTAMPTZ
)`,
// 向后兼容:补充旧版表中可能缺失的列
`ALTER TABLE memory_entries ADD COLUMN IF NOT EXISTS importance INT DEFAULT 5`,
`ALTER TABLE memory_entries ADD COLUMN IF NOT EXISTS summary TEXT DEFAULT ''`,
`ALTER TABLE memory_entries ADD COLUMN IF NOT EXISTS keywords TEXT DEFAULT '[]'`,
`ALTER TABLE memory_entries ADD COLUMN IF NOT EXISTS session_id VARCHAR(64) DEFAULT ''`,
`ALTER TABLE memory_entries ADD COLUMN IF NOT EXISTS source TEXT DEFAULT 'conversation'`,
`ALTER TABLE memory_entries ADD COLUMN IF NOT EXISTS access_count INT DEFAULT 0`,
`ALTER TABLE memory_entries ADD COLUMN IF NOT EXISTS last_access TIMESTAMPTZ DEFAULT NOW()`,
`ALTER TABLE memory_entries ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT NOW()`,
`ALTER TABLE memory_entries ADD COLUMN IF NOT EXISTS expires_at TIMESTAMPTZ`,
`CREATE INDEX IF NOT EXISTS idx_me_user_id ON memory_entries(user_id)`,
`CREATE INDEX IF NOT EXISTS idx_me_category ON memory_entries(category)`,
`CREATE INDEX IF NOT EXISTS idx_me_priority ON memory_entries(priority)`,
`CREATE INDEX IF NOT EXISTS idx_me_importance ON memory_entries(importance)`,
`CREATE INDEX IF NOT EXISTS idx_me_user_priority ON memory_entries(user_id, priority DESC)`,
`CREATE INDEX IF NOT EXISTS idx_me_user_importance ON memory_entries(user_id, importance DESC)`,
`CREATE INDEX IF NOT EXISTS idx_me_source ON memory_entries(source)`,
`CREATE INDEX IF NOT EXISTS idx_me_category_importance ON memory_entries(category, importance DESC)`,
`ALTER TABLE memories ADD COLUMN IF NOT EXISTS importance INT DEFAULT 5`,
`ALTER TABLE memories ADD COLUMN IF NOT EXISTS summary TEXT DEFAULT ''`,
`ALTER TABLE memories ADD COLUMN IF NOT EXISTS keywords TEXT DEFAULT '[]'`,
`ALTER TABLE memories ADD COLUMN IF NOT EXISTS session_id VARCHAR(64) DEFAULT ''`,
`ALTER TABLE memories ADD COLUMN IF NOT EXISTS source TEXT DEFAULT 'conversation'`,
`ALTER TABLE memories ADD COLUMN IF NOT EXISTS access_count INT DEFAULT 0`,
`ALTER TABLE memories ADD COLUMN IF NOT EXISTS last_access TIMESTAMPTZ DEFAULT NOW()`,
`ALTER TABLE memories ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT NOW()`,
`ALTER TABLE memories ADD COLUMN IF NOT EXISTS expires_at TIMESTAMPTZ`,
`CREATE INDEX IF NOT EXISTS idx_me_user_id ON memories(user_id)`,
`CREATE INDEX IF NOT EXISTS idx_me_category ON memories(category)`,
`CREATE INDEX IF NOT EXISTS idx_me_priority ON memories(priority)`,
`CREATE INDEX IF NOT EXISTS idx_me_importance ON memories(importance)`,
`CREATE INDEX IF NOT EXISTS idx_me_user_priority ON memories(user_id, priority DESC)`,
`CREATE INDEX IF NOT EXISTS idx_me_user_importance ON memories(user_id, importance DESC)`,
`CREATE INDEX IF NOT EXISTS idx_me_source ON memories(source)`,
`CREATE INDEX IF NOT EXISTS idx_me_category_importance ON memories(category, importance DESC)`,
`CREATE TABLE IF NOT EXISTS thinking_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id VARCHAR(64) NOT NULL DEFAULT 'admin',
@@ -220,7 +220,7 @@ func (s *Store) Save(ctx context.Context, entry *model.MemoryEntry) error {
entry.Importance = 5
}
query := `INSERT INTO memory_entries (user_id, content, summary, category, priority, importance, keywords, session_id, source, embedding, expires_at)
query := `INSERT INTO memories (user_id, content, summary, category, priority, importance, keywords, session_id, source, embedding, expires_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
RETURNING id, created_at`
@@ -250,7 +250,7 @@ func (s *Store) GetByID(ctx context.Context, id string) (*model.MemoryEntry, err
query := `SELECT id, user_id, content, summary, category, priority, importance, keywords,
session_id, source, access_count, last_access, created_at, updated_at, expires_at
FROM memory_entries WHERE id = $1`
FROM memories WHERE id = $1`
entry := &model.MemoryEntry{}
var category, keywordsRaw string
@@ -288,7 +288,7 @@ func (s *Store) Query(ctx context.Context, q model.MemoryQuery) ([]model.MemoryE
query := `SELECT id, user_id, content, summary, category, priority, importance, keywords,
session_id, source, access_count, last_access, created_at, updated_at, expires_at
FROM memory_entries WHERE user_id = $1`
FROM memories WHERE user_id = $1`
args := []interface{}{q.UserID}
argIdx := 2
@@ -328,7 +328,7 @@ func (s *Store) Delete(ctx context.Context, id string) error {
if db == nil {
return errDBNotReady
}
_, err := db.ExecContext(ctx, `DELETE FROM memory_entries WHERE id = $1`, id)
_, err := db.ExecContext(ctx, `DELETE FROM memories WHERE id = $1`, id)
return err
}
@@ -339,7 +339,7 @@ func (s *Store) PurgeExpired(ctx context.Context) (int64, error) {
return 0, errDBNotReady
}
result, err := db.ExecContext(ctx,
`DELETE FROM memory_entries WHERE expires_at IS NOT NULL AND expires_at < NOW()`)
`DELETE FROM memories WHERE expires_at IS NOT NULL AND expires_at < NOW()`)
if err != nil {
return 0, err
}
@@ -361,7 +361,7 @@ func (s *Store) SearchByVector(ctx context.Context, userID string, embedding []f
query := `SELECT id, user_id, content, summary, category, priority, importance, keywords,
session_id, source, access_count, last_access, created_at, updated_at, expires_at,
1 - (embedding <=> $1) AS similarity
FROM memory_entries
FROM memories
WHERE user_id = $2 AND embedding IS NOT NULL
ORDER BY embedding <=> $1
LIMIT $3`
@@ -407,7 +407,7 @@ func (s *Store) SearchByKeyword(ctx context.Context, userID, keyword string, lim
query := `SELECT id, user_id, content, summary, category, priority, importance, keywords,
session_id, source, access_count, last_access, created_at, updated_at, expires_at
FROM memory_entries
FROM memories
WHERE user_id = $1 AND (content ILIKE $2 OR summary ILIKE $2 OR keywords ILIKE $2)
ORDER BY priority DESC, importance DESC
LIMIT $3`
@@ -429,7 +429,7 @@ func (s *Store) Update(ctx context.Context, entry *model.MemoryEntry) error {
return errDBNotReady
}
query := `UPDATE memory_entries SET content = $1, summary = $2, category = $3, priority = $4,
query := `UPDATE memories SET content = $1, summary = $2, category = $3, priority = $4,
importance = $5, keywords = $6, source = $7, updated_at = NOW()
WHERE id = $8`
@@ -448,7 +448,7 @@ func (s *Store) GetCategories(ctx context.Context, userID string) (map[string]in
}
rows, err := db.QueryContext(ctx,
`SELECT category, COUNT(*) FROM memory_entries WHERE user_id = $1 GROUP BY category ORDER BY category`,
`SELECT category, COUNT(*) FROM memories WHERE user_id = $1 GROUP BY category ORDER BY category`,
userID,
)
if err != nil {
@@ -478,7 +478,7 @@ func (s *Store) Count(ctx context.Context, userID string) (int, error) {
var count int
err := db.QueryRowContext(ctx,
`SELECT COUNT(*) FROM memory_entries WHERE user_id = $1`,
`SELECT COUNT(*) FROM memories WHERE user_id = $1`,
userID,
).Scan(&count)
if err != nil {
@@ -573,7 +573,7 @@ func (s *Store) DecayMemories(ctx context.Context, userID string) (int, int, err
}
result1, err := db.ExecContext(ctx, `
UPDATE memory_entries SET priority = GREATEST(priority - 1, 0), updated_at = NOW()
UPDATE memories SET priority = GREATEST(priority - 1, 0), updated_at = NOW()
WHERE user_id = $1
AND access_count < 3
AND last_access < NOW() - INTERVAL '30 days'
@@ -588,7 +588,7 @@ func (s *Store) DecayMemories(ctx context.Context, userID string) (int, int, err
decayed1, _ := result1.RowsAffected()
result2, err := db.ExecContext(ctx, `
DELETE FROM memory_entries
DELETE FROM memories
WHERE user_id = $1
AND priority = 0
AND access_count = 0
@@ -615,7 +615,7 @@ func (s *Store) incrementAccess(ctx context.Context, id string) {
return
}
db.ExecContext(ctx,
`UPDATE memory_entries SET access_count = access_count + 1, last_access = NOW() WHERE id = $1`, id)
`UPDATE memories SET access_count = access_count + 1, last_access = NOW() WHERE id = $1`, id)
}
// Close 关闭数据库连接
@@ -754,21 +754,26 @@ func (s *Store) GetThinkingLogByID(ctx context.Context, id string) (*model.Think
}
// GetThinkingStats 获取思考日志统计信息
func (s *Store) GetThinkingStats(ctx context.Context) (*model.ThinkingStats, error) {
func (s *Store) GetThinkingStats(ctx context.Context, userID string) (*model.ThinkingStats, error) {
db := s.getDB()
if db == nil {
return nil, errDBNotReady
}
var args []interface{}
query := `SELECT
COALESCE(COUNT(*), 0),
COALESCE(SUM(tool_call_count), 0),
COALESCE(AVG(content_length), 0),
COALESCE(MAX(created_at)::TEXT, '')
FROM thinking_logs`
if userID != "" {
query += ` WHERE user_id = $1`
args = append(args, userID)
}
stats := &model.ThinkingStats{}
err := db.QueryRowContext(ctx, query).Scan(
err := db.QueryRowContext(ctx, query, args...).Scan(
&stats.TotalLogs, &stats.TotalToolCalls,
&stats.AvgContentLen, &stats.LatestAt,
)