package server import ( "encoding/json" "net/http" "os" "strconv" "chat-agent-system/internal/app/chatflow" "chat-agent-system/internal/ctxmgr" ) // handleGetChatWindow 获取指定会话最近 N 日的消息窗口 // GET /api/v1/chat/messages/window?session_id=&days=&before_day= func (s *Server) handleGetChatWindow(w http.ResponseWriter, r *http.Request) { if s.chatLog == nil { writeSafeError(w, r, http.StatusServiceUnavailable, "context manager not initialized") return } user, ok := s.requireAuthenticatedUser(w, r) if !ok { return } sessionID := r.URL.Query().Get("session_id") if sessionID == "" { writeSafeError(w, r, http.StatusBadRequest, "missing session_id query param") return } daysStr := r.URL.Query().Get("days") days, _ := strconv.Atoi(daysStr) if days <= 0 { days = ctxmgr.DefaultWindowDays } res, err := s.chatLog.GetWindow(r.Context(), s.chatPrincipal(user, chatflow.ActionReadWindow), ctxmgr.ChatWindowQuery{ SessionID: sessionID, Days: days, BeforeDay: r.URL.Query().Get("before_day"), }) if err != nil { s.writeChatLogError(w, err) return } writeJSONResponse(w, http.StatusOK, res) } // handleGetChatDayMessages 获取指定日消息 (支持 anchor_id 上下文补齐或游标分页) // GET /api/v1/chat/messages/day?session_id=&date=&anchor_id=&before_id=&after_id=&limit= func (s *Server) handleGetChatDayMessages(w http.ResponseWriter, r *http.Request) { if s.chatLog == nil { writeSafeError(w, r, http.StatusServiceUnavailable, "context manager not initialized") return } user, ok := s.requireAuthenticatedUser(w, r) if !ok { return } sessionID := r.URL.Query().Get("session_id") if sessionID == "" { writeSafeError(w, r, http.StatusBadRequest, "missing session_id query param") return } date := r.URL.Query().Get("date") if date == "" { date = r.URL.Query().Get("day_key") } anchorIDStr := r.URL.Query().Get("anchor_id") anchorID, _ := strconv.ParseInt(anchorIDStr, 10, 64) if date == "" && anchorID <= 0 { writeSafeError(w, r, http.StatusBadRequest, "missing date/day_key or anchor_id query param") return } beforeID, _ := strconv.ParseInt(r.URL.Query().Get("before_id"), 10, 64) afterID, _ := strconv.ParseInt(r.URL.Query().Get("after_id"), 10, 64) limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) if limit <= 0 { limit = 100 } res, err := s.chatLog.GetDay(r.Context(), s.chatPrincipal(user, chatflow.ActionReadDay), chatflow.DayQuery{ SessionID: sessionID, Date: date, AnchorID: anchorID, BeforeID: beforeID, AfterID: afterID, Limit: limit, }) if err != nil { s.writeChatLogError(w, err) return } writeJSONResponse(w, http.StatusOK, res) } // handleListChatDates 列出会话有消息的全部日期 (用于日历打点) // GET /api/v1/chat/dates?session_id= func (s *Server) handleListChatDates(w http.ResponseWriter, r *http.Request) { if s.chatLog == nil { writeSafeError(w, r, http.StatusServiceUnavailable, "context manager not initialized") return } user, ok := s.requireAuthenticatedUser(w, r) if !ok { return } sessionID := r.URL.Query().Get("session_id") if sessionID == "" { writeSafeError(w, r, http.StatusBadRequest, "missing session_id query param") return } res, err := s.chatLog.DateStats(r.Context(), s.chatPrincipal(user, chatflow.ActionReadStats), sessionID) if err != nil { s.writeChatLogError(w, err) return } writeJSONResponse(w, http.StatusOK, res) } // handleListChatCategories 列出会话各分类消息统计 (用于分类筛选条) // GET /api/v1/chat/categories?session_id= func (s *Server) handleListChatCategories(w http.ResponseWriter, r *http.Request) { if s.chatLog == nil { writeSafeError(w, r, http.StatusServiceUnavailable, "context manager not initialized") return } user, ok := s.requireAuthenticatedUser(w, r) if !ok { return } sessionID := r.URL.Query().Get("session_id") if sessionID == "" { writeSafeError(w, r, http.StatusBadRequest, "missing session_id query param") return } res, err := s.chatLog.CategoryStats(r.Context(), s.chatPrincipal(user, chatflow.ActionReadStats), sessionID) if err != nil { s.writeChatLogError(w, err) return } writeJSONResponse(w, http.StatusOK, res) } // handleImportChatMessages 一次性批量回填历史消息 (仅允许在 dev/demo 环境下调用) // POST /api/v1/chat/messages/import func (s *Server) handleImportChatMessages(w http.ResponseWriter, r *http.Request) { if s.ctxMgr() == nil { writeSafeError(w, r, http.StatusServiceUnavailable, "context manager not initialized") return } // 安全门禁:生产环境默认禁止调用,仅在显式 dev 或 ENABLE_DEV_IMPORT 时开放 isDev := s.cfg.Env == "dev" || s.cfg.Env == "development" || os.Getenv("ENABLE_DEV_IMPORT") == "true" if !isDev { writeSafeError(w, r, http.StatusForbidden, "chat messages import endpoint is only available in dev/demo environment") return } type importMessageItem struct { SessionID string `json:"session_id"` UserID int64 `json:"user_id"` Role string `json:"role"` Content string `json:"content"` Intent string `json:"intent"` Branch string `json:"branch"` Metadata map[string]any `json:"metadata"` DayKey string `json:"day_key"` CreatedAt string `json:"created_at"` } var items []importMessageItem if err := json.NewDecoder(r.Body).Decode(&items); err != nil { writeDomainError(w, r, http.StatusBadRequest, err) return } imported := 0 for _, item := range items { if item.SessionID == "" || item.Content == "" { continue } if item.Metadata == nil { item.Metadata = make(map[string]any) } if item.DayKey != "" { item.Metadata["day_key"] = item.DayKey } if item.CreatedAt != "" { item.Metadata["created_at"] = item.CreatedAt } _, err := s.ctxMgr().RecordUserMessageWithID( r.Context(), item.SessionID, item.UserID, item.Role, item.Content, item.Intent, item.Branch, item.Metadata, ) if err == nil { imported++ } } writeJSONResponse(w, http.StatusOK, map[string]any{ "imported_count": imported, "total": len(items), }) }