feat(ai-writing): 研究模板独立实体 + 配置页 + 内置范例 + 点击直接进入写作(0616 #5)
后端: - 新持久化包 research_templates(表 ai_writing_research_templates,create_all 建表,注册 models/__init__),deps 接 research_template_store - 路由 /api/ai-writing/research-templates CRUD(在 ai_writing.py) - 内置范例 网摘/周报/要讯(DEFAULT_RESEARCH_TEMPLATES + seed_defaults,表空时由 list 接口幂等播种) - tests/test_research_templates.py(9 passed) 前端: - api/ai-writing-research-templates.ts + hooks/useResearchTemplates.ts - 配置页 ResearchTemplatesConfigPage(/page/canvas/research-templates)CRUD - ResearchSidebar 研究模板改用研究模板实体;点击回填整张表单并直接 startWriting 进入写作;顶部齿轮进配置页 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
0e4caa5a66
commit
ba5aeefe98
12 files changed
+1141
-44
No files matched your search
@@ -168,6 +168,10 @@ async def langgraph_runtime(app: FastAPI) -> AsyncGenerator[None, None]:
|
||||
|
||||
app.state.article_type_store = make_article_type_store(sf)
|
||||
|
||||
from deerflow.persistence.research_templates import make_research_template_store
|
||||
|
||||
app.state.research_template_store = make_research_template_store(sf)
|
||||
|
||||
from deerflow.persistence.memory_config import make_user_memory_config_store
|
||||
|
||||
app.state.memory_config_store = make_user_memory_config_store(sf)
|
||||
@@ -423,6 +427,11 @@ def get_article_type_store(request: Request):
|
||||
return getattr(request.app.state, "article_type_store", None)
|
||||
|
||||
|
||||
def get_research_template_store(request: Request):
|
||||
"""Return the research template store, or None when not configured."""
|
||||
return getattr(request.app.state, "research_template_store", None)
|
||||
|
||||
|
||||
def get_memory_config_store(request: Request):
|
||||
"""Return the per-user memory config store."""
|
||||
val = getattr(request.app.state, "memory_config_store", None)
|
||||
|
||||
@@ -90,6 +90,48 @@ class ArticleTypeResponse(BaseModel):
|
||||
updated_at: str
|
||||
|
||||
|
||||
class ResearchTemplateRequest(BaseModel):
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
article_type_key: Optional[str] = None
|
||||
outline: Optional[str] = None
|
||||
material_source: str = "general"
|
||||
word_count: int = 800
|
||||
audience: Optional[str] = None
|
||||
strict_mode: bool = False
|
||||
topic: Optional[str] = None
|
||||
sort_order: int = 0
|
||||
|
||||
|
||||
class UpdateResearchTemplateRequest(BaseModel):
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
article_type_key: Optional[str] = None
|
||||
outline: Optional[str] = None
|
||||
material_source: Optional[str] = None
|
||||
word_count: Optional[int] = None
|
||||
audience: Optional[str] = None
|
||||
strict_mode: Optional[bool] = None
|
||||
topic: Optional[str] = None
|
||||
sort_order: Optional[int] = None
|
||||
|
||||
|
||||
class ResearchTemplateResponse(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
article_type_key: Optional[str] = None
|
||||
outline: Optional[str] = None
|
||||
material_source: str
|
||||
word_count: int
|
||||
audience: Optional[str] = None
|
||||
strict_mode: bool
|
||||
topic: Optional[str] = None
|
||||
sort_order: int
|
||||
created_at: str
|
||||
updated_at: str
|
||||
|
||||
|
||||
class SessionResponse(BaseModel):
|
||||
id: str
|
||||
user_id: Optional[str] = None
|
||||
@@ -268,6 +310,94 @@ async def delete_article_type(article_type_id: str, request: Request):
|
||||
return {"success": True}
|
||||
|
||||
|
||||
# ── 研究模板(课题研究左栏一键预填表单,需求 0616 #5)────────────────────────────
|
||||
|
||||
|
||||
@router.get("/research-templates", response_model=list[ResearchTemplateResponse])
|
||||
async def list_research_templates(request: Request):
|
||||
"""列出所有研究模板,按 sort_order 升序。
|
||||
|
||||
新机器/空表时自动写入内置范例模板(网摘/周报/要讯),幂等仅在表为空时执行。
|
||||
"""
|
||||
from app.gateway.deps import get_research_template_store
|
||||
|
||||
store = get_research_template_store(request)
|
||||
if store is None:
|
||||
return []
|
||||
try:
|
||||
await store.seed_defaults()
|
||||
except Exception:
|
||||
logger.exception("ai_writing: failed to seed default research templates")
|
||||
return await store.list()
|
||||
|
||||
|
||||
@router.post("/research-templates", response_model=ResearchTemplateResponse, status_code=201)
|
||||
async def create_research_template(body: ResearchTemplateRequest, request: Request):
|
||||
"""新增研究模板。"""
|
||||
from app.gateway.deps import get_research_template_store
|
||||
|
||||
store = get_research_template_store(request)
|
||||
if store is None:
|
||||
raise HTTPException(status_code=503, detail="Research template store not available")
|
||||
return await store.create(
|
||||
name=body.name,
|
||||
description=body.description,
|
||||
article_type_key=body.article_type_key,
|
||||
outline=body.outline,
|
||||
material_source=body.material_source,
|
||||
word_count=body.word_count,
|
||||
audience=body.audience,
|
||||
strict_mode=body.strict_mode,
|
||||
topic=body.topic,
|
||||
sort_order=body.sort_order,
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/research-templates/{template_id}", response_model=ResearchTemplateResponse)
|
||||
async def update_research_template(template_id: str, body: UpdateResearchTemplateRequest, request: Request):
|
||||
"""更新研究模板字段(只传需要修改的字段)。"""
|
||||
from app.gateway.deps import get_research_template_store
|
||||
|
||||
store = get_research_template_store(request)
|
||||
if store is None:
|
||||
raise HTTPException(status_code=503, detail="Research template store not available")
|
||||
|
||||
kwargs = {}
|
||||
for field in (
|
||||
"name",
|
||||
"description",
|
||||
"article_type_key",
|
||||
"outline",
|
||||
"material_source",
|
||||
"word_count",
|
||||
"audience",
|
||||
"strict_mode",
|
||||
"topic",
|
||||
"sort_order",
|
||||
):
|
||||
val = getattr(body, field)
|
||||
if val is not None:
|
||||
kwargs[field] = val
|
||||
|
||||
updated = await store.update(template_id, **kwargs)
|
||||
if updated is None:
|
||||
raise HTTPException(status_code=404, detail="Research template not found")
|
||||
return updated
|
||||
|
||||
|
||||
@router.delete("/research-templates/{template_id}")
|
||||
async def delete_research_template(template_id: str, request: Request):
|
||||
"""删除研究模板。"""
|
||||
from app.gateway.deps import get_research_template_store
|
||||
|
||||
store = get_research_template_store(request)
|
||||
if store is None:
|
||||
raise HTTPException(status_code=503, detail="Research template store not available")
|
||||
deleted = await store.delete(template_id)
|
||||
if not deleted:
|
||||
raise HTTPException(status_code=404, detail="Research template not found")
|
||||
return {"success": True}
|
||||
|
||||
|
||||
# ── 管理接口 ──────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
Reference in new issue
Block a user