feat: 扩展 Session Manager 接口,新增 ListByUser、UpdateTitle 方法
- Manager.Create 签名新增 userID 参数
- 新增 ConversationSummary 类型和 ListByUser 分页查询
- 新增 UpdateTitle 方法
- MemoryManager 实现:ListByUser 遍历+过滤+排序,UpdateTitle,自动标题生成
- RedisManager 实现:user:{id}:sessions 索引,ListByUser 通过 SMEMBERS 查询
- AppendMessage 自动更新标题(首条 user 消息时,取前 20 字符)
- 更新 ws handler、api/session.go、orchestrator mock 的 Create 调用
This commit is contained in:
@@ -2,6 +2,7 @@ package session
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -95,8 +96,8 @@ func (m *MemoryManager) isExpired(entry *sessionEntry) bool {
|
||||
return time.Since(entry.lastActive) > m.ttl
|
||||
}
|
||||
|
||||
// Create 创建新会话。
|
||||
func (m *MemoryManager) Create(_ context.Context, config models.SessionConfig) (string, error) {
|
||||
// Create 创建新会话。userID 为空表示匿名会话。
|
||||
func (m *MemoryManager) Create(_ context.Context, userID string, config models.SessionConfig) (string, error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
@@ -105,14 +106,17 @@ func (m *MemoryManager) Create(_ context.Context, config models.SessionConfig) (
|
||||
m.sessions[id] = &sessionEntry{
|
||||
session: models.Session{
|
||||
ID: id,
|
||||
UserID: userID,
|
||||
Title: models.DefaultSessionTitle,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
Config: config,
|
||||
},
|
||||
history: make([]models.Message, 0),
|
||||
lastActive: now,
|
||||
}
|
||||
|
||||
logger.Log.Debugw("session created", "session", id)
|
||||
logger.Log.Debugw("session created", "session", id, "user_id", userID)
|
||||
return id, nil
|
||||
}
|
||||
|
||||
@@ -147,6 +151,76 @@ func (m *MemoryManager) UpdateConfig(_ context.Context, sessionID string, patch
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateTitle 更新会话标题。
|
||||
func (m *MemoryManager) UpdateTitle(_ context.Context, sessionID string, title string) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
entry, ok := m.sessions[sessionID]
|
||||
if !ok || m.isExpired(entry) {
|
||||
return ErrSessionNotFound
|
||||
}
|
||||
|
||||
entry.session.Title = title
|
||||
entry.session.UpdatedAt = time.Now()
|
||||
entry.lastActive = time.Now()
|
||||
|
||||
logger.Log.Debugw("session title updated", "session", sessionID, "title", title)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListByUser 获取用户的对话列表(分页,按 UpdatedAt 降序)。
|
||||
func (m *MemoryManager) ListByUser(_ context.Context, userID string, page, size int) ([]ConversationSummary, int, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
// 收集该用户的所有 session
|
||||
var list []ConversationSummary
|
||||
for _, entry := range m.sessions {
|
||||
if entry.session.UserID != userID {
|
||||
continue
|
||||
}
|
||||
if m.isExpired(entry) {
|
||||
continue
|
||||
}
|
||||
summary := ConversationSummary{
|
||||
ID: entry.session.ID,
|
||||
Title: entry.session.Title,
|
||||
MessageCount: len(entry.history),
|
||||
UpdatedAt: entry.lastActive,
|
||||
}
|
||||
if len(entry.history) > 0 {
|
||||
summary.LastMessage = entry.history[len(entry.history)-1].Content
|
||||
}
|
||||
list = append(list, summary)
|
||||
}
|
||||
|
||||
// 按 UpdatedAt 降序排序
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].UpdatedAt.After(list[j].UpdatedAt)
|
||||
})
|
||||
|
||||
total := len(list)
|
||||
|
||||
// 分页
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if size <= 0 {
|
||||
size = 20
|
||||
}
|
||||
start := (page - 1) * size
|
||||
if start >= total {
|
||||
return []ConversationSummary{}, total, nil
|
||||
}
|
||||
end := start + size
|
||||
if end > total {
|
||||
end = total
|
||||
}
|
||||
|
||||
return list[start:end], total, nil
|
||||
}
|
||||
|
||||
// GetHistory 获取最近 N 轮对话历史。
|
||||
func (m *MemoryManager) GetHistory(_ context.Context, sessionID string, limit int) ([]models.Message, error) {
|
||||
m.mu.RLock()
|
||||
@@ -179,15 +253,31 @@ func (m *MemoryManager) AppendMessage(_ context.Context, sessionID string, msg m
|
||||
|
||||
entry.history = append(entry.history, msg)
|
||||
|
||||
// 自动更新标题:首条 user 消息时,如果标题为默认值,自动更新为消息前 20 字符
|
||||
if msg.Role == "user" && entry.session.Title == models.DefaultSessionTitle {
|
||||
entry.session.Title = generateTitle(msg.Content)
|
||||
}
|
||||
|
||||
// 超过上限时裁剪,保留最新的 maxHistory 条
|
||||
if len(entry.history) > m.maxHistory {
|
||||
entry.history = entry.history[len(entry.history)-m.maxHistory:]
|
||||
}
|
||||
|
||||
entry.lastActive = time.Now()
|
||||
now := time.Now()
|
||||
entry.lastActive = now
|
||||
entry.session.UpdatedAt = now
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateTitle 从首条消息生成对话标题(取前 20 个字符)。
|
||||
func generateTitle(firstMessage string) string {
|
||||
runes := []rune(firstMessage)
|
||||
if len(runes) > 20 {
|
||||
return string(runes[:20]) + "…"
|
||||
}
|
||||
return firstMessage
|
||||
}
|
||||
|
||||
// SetActiveRequest 标记当前正在处理的请求 ID。
|
||||
func (m *MemoryManager) SetActiveRequest(_ context.Context, sessionID string, requestID string) error {
|
||||
m.mu.Lock()
|
||||
|
||||
Reference in New Issue
Block a user