# -*- coding: utf-8 -*- """ 会话管理接口 —— 聊天历史查询、会话列表、删除会话、权限模式管理 """ from fastapi import APIRouter, Depends from pydantic import BaseModel, Field from api.auth import get_current_user from db.chat_store import ( get_messages, get_sessions, delete_session, get_session_mode, set_session_mode, get_latest_context_usage, pin_session, is_session_pinned, update_session_title, ) router = APIRouter() class ModeRequest(BaseModel): """权限模式切换请求""" mode: str = Field(..., description="权限模式", examples=["plan", "full"]) class TitleRequest(BaseModel): """会话标题编辑请求""" title: str = Field(..., description="新标题", max_length=100, examples=["15216工作面需风量计算"]) @router.get("/chat/history/{session_id}") async def get_chat_history( session_id: str, limit: int = 100, offset: int = 0, user_info: dict = Depends(get_current_user), ): """查询指定会话的聊天历史 Args: session_id: 会话ID limit: 返回条数上限,默认100 offset: 偏移量,默认0 Returns: { "session_id": "...", "messages": [...], "total": N } """ messages = get_messages(session_id, limit=limit, offset=offset) # 计算总数 all_msgs = get_messages(session_id, limit=10000) total = len(all_msgs) return { "session_id": session_id, "messages": messages, "total": total, } @router.get("/sessions") async def list_sessions( limit: int = 20, offset: int = 0, user_info: dict = Depends(get_current_user), ): """查询当前用户的会话列表(按更新时间倒序) Returns: [{ "session_id": "...", "title": "...", "message_count": N, ... }, ...] """ user_name = user_info.get("username", "admin") sessions = get_sessions(limit=limit, offset=offset, user_name=user_name) return {"sessions": sessions, "total": len(sessions)} @router.delete("/sessions/{session_id}") async def remove_session( session_id: str, user_info: dict = Depends(get_current_user), ): """删除指定会话及其所有消息""" delete_session(session_id) return {"status": "ok", "message": f"会话 {session_id} 已删除"} # ── 权限模式管理 ── @router.get("/sessions/{session_id}/mode") async def get_mode( session_id: str, user_info: dict = Depends(get_current_user), ): """获取会话的权限模式""" return { "session_id": session_id, "mode": get_session_mode(session_id), } @router.put("/sessions/{session_id}/mode") async def set_mode( session_id: str, req: ModeRequest, user_info: dict = Depends(get_current_user), ): """设置会话的权限模式 mode 取值: - plan: 计划模式(先生成执行计划,用户确认后再执行) - full: 完全访问(无限制自动执行) """ valid = {"plan", "full"} if req.mode not in valid: return { "error": f"无效的模式: {req.mode}", "valid_modes": sorted(valid), } set_session_mode(session_id, req.mode) return { "session_id": session_id, "mode": req.mode, "message": f"已切换为「{req.mode}」模式", } # ── 会话置顶 ── @router.put("/sessions/{session_id}/pin") async def toggle_pin( session_id: str, user_info: dict = Depends(get_current_user), ): """切换会话置顶状态(置顶 ⇄ 取消置顶)。 Returns: { "session_id": "...", "pinned": true, "message": "..." } """ pinned = not is_session_pinned(session_id) pin_session(session_id, pinned) return { "session_id": session_id, "pinned": pinned, "message": "已置顶" if pinned else "已取消置顶", } # ── 会话标题编辑 ── @router.put("/sessions/{session_id}/title") async def edit_title( session_id: str, req: TitleRequest, user_info: dict = Depends(get_current_user), ): """编辑会话标题。 Args: req.title: 新标题,最长100字 """ title = req.title.strip() if not title: return {"error": "标题不能为空"} if len(title) > 100: return {"error": f"标题过长({len(title)}字),最多100字"} update_session_title(session_id, title) return { "session_id": session_id, "title": title, "message": "标题已更新", } # ── 上下文容量查询 ── @router.get("/sessions/{session_id}/context") async def get_context_usage( session_id: str, user_info: dict = Depends(get_current_user), ): """查询会话最近一次的上下文用量详情。 Returns: { "session_id": "...", "total_limit": 131072, "current_usage": 12345, "usage_pct": 9.4, "breakdown": { "messages": 5000, "mcp": 4000, "skills": 800, "system_prompt": 600, "other": 2745 }, "updated_at": "2026-08-05 14:30:00" } 若无记录则返回 null 值。 """ row = get_latest_context_usage(session_id) if not row: return { "session_id": session_id, "total_limit": None, "current_usage": None, "usage_pct": None, "breakdown": None, "updated_at": None, } total_limit = row.get("total_limit", 131072) current = row.get("current_usage", 0) pct = round(current / total_limit * 100, 1) if total_limit else 0 return { "session_id": session_id, "total_limit": total_limit, "current_usage": current, "usage_pct": pct, "breakdown": { "messages": row.get("messages_tokens", 0), "mcp": row.get("mcp_tokens", 0), "skills": row.get("skills_tokens", 0), "system_prompt": row.get("system_prompt_tokens", 0), "other": row.get("other_tokens", 0), }, "updated_at": row.get("updated_at"), }