feat: DevTools综合升级 — 记忆查询 + 会话监看 + WebUI侧边栏重构
- docs: 17个文件重命名为 YYYY-MM-DD.HH-mm-SS-内容.md 格式 - config: 管理员凭据移至 backend/.env (ADMIN_USERNAME/PASSWORD) - gateway: 新增 SessionState 会话追踪 + GET /api/v1/admin/sessions - devtools: 新增7个代理端点 (dashboard/sessions/memory) - devtools: WebUI重构为侧边栏 + 5面板 (仪表盘/记忆/会话/服务/性能)
This commit is contained in:
@@ -3,8 +3,29 @@ package ws
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SessionState 会话状态
|
||||
type SessionState struct {
|
||||
SessionID string `json:"session_id"`
|
||||
UserID string `json:"user_id"`
|
||||
State string `json:"state"` // idle, thinking, streaming, error
|
||||
ConnectedAt time.Time `json:"connected_at"`
|
||||
LastActivity time.Time `json:"last_activity"`
|
||||
MessageCount int `json:"message_count"`
|
||||
RecentMessages []SessionMessage `json:"recent_messages,omitempty"`
|
||||
}
|
||||
|
||||
// SessionMessage 会话消息记录
|
||||
type SessionMessage struct {
|
||||
Role string `json:"role"` // user, assistant, system
|
||||
Content string `json:"content"` // 截断到 100 字符
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
const maxRecentMessages = 20
|
||||
|
||||
// Hub WebSocket连接池
|
||||
type Hub struct {
|
||||
mu sync.RWMutex
|
||||
@@ -15,6 +36,9 @@ type Hub struct {
|
||||
|
||||
// 按用户ID索引的客户端映射
|
||||
userClients map[string]map[*Client]bool
|
||||
|
||||
// 会话状态追踪 (sessionID -> SessionState)
|
||||
sessions map[string]*SessionState
|
||||
}
|
||||
|
||||
// NewHub 创建WebSocket Hub
|
||||
@@ -25,6 +49,7 @@ func NewHub() *Hub {
|
||||
register: make(chan *Client),
|
||||
unregister: make(chan *Client),
|
||||
userClients: make(map[string]map[*Client]bool),
|
||||
sessions: make(map[string]*SessionState),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +66,18 @@ func (h *Hub) Run() {
|
||||
h.userClients[client.UserID] = make(map[*Client]bool)
|
||||
}
|
||||
h.userClients[client.UserID][client] = true
|
||||
|
||||
// 会话状态追踪:如果该session尚未存在则创建
|
||||
if _, exists := h.sessions[client.SessionID]; !exists {
|
||||
h.sessions[client.SessionID] = &SessionState{
|
||||
SessionID: client.SessionID,
|
||||
UserID: client.UserID,
|
||||
State: "idle",
|
||||
ConnectedAt: time.Now(),
|
||||
LastActivity: time.Now(),
|
||||
MessageCount: 0,
|
||||
}
|
||||
}
|
||||
h.mu.Unlock()
|
||||
|
||||
log.Printf("[WS] 客户端连接: user=%s session=%s (当前连接数: %d)",
|
||||
@@ -59,6 +96,20 @@ func (h *Hub) Run() {
|
||||
delete(h.userClients, client.UserID)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查该session是否还有其他连接,没有则移除会话状态
|
||||
hasOtherConn := false
|
||||
if clients, ok := h.userClients[client.UserID]; ok {
|
||||
for c := range clients {
|
||||
if c.SessionID == client.SessionID {
|
||||
hasOtherConn = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasOtherConn {
|
||||
delete(h.sessions, client.SessionID)
|
||||
}
|
||||
}
|
||||
h.mu.Unlock()
|
||||
|
||||
@@ -135,3 +186,80 @@ func (h *Hub) UserClientCount(userID string) int {
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetActiveSessions 返回所有活跃会话的列表
|
||||
func (h *Hub) GetActiveSessions() []*SessionState {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
|
||||
result := make([]*SessionState, 0, len(h.sessions))
|
||||
for _, s := range h.sessions {
|
||||
// 返回副本避免外部修改
|
||||
cp := *s
|
||||
// 不包含 recent_messages 在列表接口中
|
||||
cp.RecentMessages = nil
|
||||
result = append(result, &cp)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetSession 返回指定会话的详细信息(含最近消息)
|
||||
func (h *Hub) GetSession(sessionID string) *SessionState {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
|
||||
s, ok := h.sessions[sessionID]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 返回副本
|
||||
cp := *s
|
||||
if s.RecentMessages != nil {
|
||||
cp.RecentMessages = make([]SessionMessage, len(s.RecentMessages))
|
||||
copy(cp.RecentMessages, s.RecentMessages)
|
||||
}
|
||||
return &cp
|
||||
}
|
||||
|
||||
// UpdateSessionState 更新会话状态
|
||||
func (h *Hub) UpdateSessionState(sessionID, state string) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
if s, ok := h.sessions[sessionID]; ok {
|
||||
s.State = state
|
||||
s.LastActivity = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
// RecordMessage 记录消息到会话
|
||||
func (h *Hub) RecordMessage(sessionID, role, content string) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
s, ok := h.sessions[sessionID]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
s.MessageCount++
|
||||
s.LastActivity = time.Now()
|
||||
|
||||
// 截断内容到 100 字符
|
||||
runes := []rune(content)
|
||||
if len(runes) > 100 {
|
||||
content = string(runes[:100]) + "..."
|
||||
}
|
||||
|
||||
s.RecentMessages = append(s.RecentMessages, SessionMessage{
|
||||
Role: role,
|
||||
Content: content,
|
||||
Timestamp: time.Now().UnixMilli(),
|
||||
})
|
||||
|
||||
// 只保留最近 N 条消息
|
||||
if len(s.RecentMessages) > maxRecentMessages {
|
||||
s.RecentMessages = s.RecentMessages[len(s.RecentMessages)-maxRecentMessages:]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user