chore(v2): 从兴邦预审v2白名单抽取独立可运行基座
按 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>
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
"""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}
|
||||
Reference in New Issue
Block a user