按 docs/v2/05-implementation-plan.md §1 候选迁移表与 claude_use/T00 白名单, 从 55e2cdb 抽取 224 个文件(2.48 MB)到独立目录 F:/mini-OpenClaw-v2。 - 排除:凭据(.env/server-deploy.conf/旧DELIVERY.md)、运行数据(xingbang_data 等 4 个 SQLite)、依赖(venv/node_modules 符号链接)、归档(_backup*/test-evidence/ ori_file)、Office 临时文件 - 目标目录独立 git 仓库,初始恢复点;Gitea 远程与分支待 OQ-06 Q2 定案 - 未部署、未启动服务、未调用模型 Co-Authored-By: Claude Code <noreply@anthropic.com>
124 lines
4.4 KiB
Python
124 lines
4.4 KiB
Python
"""Session CRUD API — list / create / rename / delete / raw messages / generate title."""
|
|
|
|
import os
|
|
import uuid
|
|
from pathlib import Path
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
from pydantic import BaseModel
|
|
|
|
from graph.session_manager import session_manager
|
|
from graph.prompt_builder import build_system_prompt
|
|
|
|
router = APIRouter()
|
|
|
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
|
|
|
|
# ── Request models ──────────────────────────────────────────
|
|
|
|
class RenameRequest(BaseModel):
|
|
title: str
|
|
|
|
|
|
# ── Endpoints ───────────────────────────────────────────────
|
|
|
|
@router.get("/sessions")
|
|
async def list_sessions():
|
|
"""List all sessions with title and metadata."""
|
|
sessions = session_manager.list_sessions()
|
|
return {"sessions": sessions}
|
|
|
|
|
|
@router.post("/sessions")
|
|
async def create_session():
|
|
"""Create a new empty session."""
|
|
session_id = f"session-{uuid.uuid4().hex[:12]}"
|
|
meta = session_manager.create_session(session_id)
|
|
return meta
|
|
|
|
|
|
@router.put("/sessions/{session_id}")
|
|
async def rename_session(session_id: str, req: RenameRequest):
|
|
"""Rename an existing session."""
|
|
try:
|
|
session_manager.rename_session(session_id, req.title)
|
|
except FileNotFoundError:
|
|
raise HTTPException(status_code=404, detail="Session not found")
|
|
return {"id": session_id, "title": req.title}
|
|
|
|
|
|
@router.delete("/sessions/{session_id}")
|
|
async def delete_session(session_id: str):
|
|
"""Delete a session."""
|
|
session_manager.delete_session(session_id)
|
|
return {"status": "deleted", "id": session_id}
|
|
|
|
|
|
@router.get("/sessions/{session_id}/messages")
|
|
async def get_raw_messages(session_id: str):
|
|
"""Get complete raw messages including system prompt."""
|
|
data = session_manager.get_raw_messages(session_id)
|
|
system_prompt = build_system_prompt(BASE_DIR)
|
|
# Prepend system prompt as the first message
|
|
all_messages = [{"role": "system", "content": system_prompt}] + data.get("messages", [])
|
|
return {"session_id": session_id, "title": data.get("title", ""), "messages": all_messages}
|
|
|
|
|
|
@router.get("/sessions/{session_id}/history")
|
|
async def get_session_history(session_id: str):
|
|
"""Get conversation history for display (no system prompt, includes tool_calls)."""
|
|
messages = session_manager.load_session(session_id)
|
|
return {"session_id": session_id, "messages": messages}
|
|
|
|
|
|
@router.post("/sessions/{session_id}/generate-title")
|
|
async def generate_title(session_id: str):
|
|
"""Use DeepSeek to generate a short title from the first conversation turn."""
|
|
messages = session_manager.load_session(session_id)
|
|
if not messages:
|
|
raise HTTPException(status_code=400, detail="No messages to generate title from")
|
|
|
|
# Get the first user message and first assistant reply
|
|
first_user = ""
|
|
first_assistant = ""
|
|
for msg in messages:
|
|
if msg["role"] == "user" and not first_user:
|
|
first_user = msg["content"][:200]
|
|
elif msg["role"] == "assistant" and not first_assistant:
|
|
first_assistant = msg["content"][:200]
|
|
if first_user and first_assistant:
|
|
break
|
|
|
|
if not first_user:
|
|
raise HTTPException(status_code=400, detail="No user message found")
|
|
|
|
try:
|
|
from langchain_deepseek import ChatDeepSeek
|
|
from langchain_core.messages import HumanMessage as HM
|
|
|
|
llm = ChatDeepSeek(
|
|
model=os.getenv("DEEPSEEK_MODEL", "deepseek-chat"),
|
|
api_key=os.getenv("DEEPSEEK_API_KEY"),
|
|
api_base=os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com"),
|
|
temperature=0.3,
|
|
)
|
|
|
|
prompt = (
|
|
f"根据以下对话内容,生成一个不超过10个字的中文标题,只输出标题文本,不要加引号或标点。\n\n"
|
|
f"用户: {first_user}\n"
|
|
f"助手: {first_assistant}"
|
|
)
|
|
|
|
result = await llm.ainvoke([HM(content=prompt)])
|
|
title = result.content.strip().strip('"\'""''')[:20]
|
|
|
|
session_manager.update_title(session_id, title)
|
|
return {"session_id": session_id, "title": title}
|
|
|
|
except Exception as e:
|
|
# Fallback: use first few chars of user message
|
|
fallback_title = first_user[:10].strip()
|
|
session_manager.update_title(session_id, fallback_title)
|
|
return {"session_id": session_id, "title": fallback_title}
|