Files
AdministratorandClaude Opus 4.8 adcc5a358b feat(scheduled-tasks): 新增 HTML 页面定时任务(生成/预览/收藏/技能)
后端:
- 新增 task_kind=html_page 任务类型,与普通定时任务并存,配置存 execution_context(无需迁移)
- 执行流水线:智能体检索数据 -> 选定模型渲染 HTML -> 清洗/校验 -> 失败自动修复 -> 完整性闸门(缺 doctype/html/head/body/title 或残留脚本即失败) -> 归档 index.html -> 落库
- 新增 scheduled_html_pages / html_page_favorites 表与 store;收藏去重(不可重复)、可删除、收藏时提取可复用风格
- HTML 预览接口(鉴权 + 公开)、收藏接口
- 渲染注入已启用的 HTML 技能(metadata.kind=html_page);全程使用用户所选模型,去除硬编码 GLM-5
- 定时任务跳过 TitleMiddleware,避免标题生成回退默认模型

前端:
- 创建/编辑弹窗增加任务类型与 HTML 配置(页面要求/限制/参考收藏页/模型下拉)
- 运行详情页与公开页 iframe 沙箱预览 HTML;收藏按钮(已收藏禁用)
- 参考收藏页可选可删

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 11:35:38 +08:00

94 lines
3.5 KiB
Python

"""HTML page favorites API.
A favorite bookmarks a generated ``scheduled_html_pages`` row and stores a
reusable style/layout summary (extracted via GLM5 at favorite time) so a later
``html_page`` task can be generated "in the style of" the favorite.
All routes are owner-scoped; the underlying logic lives on
``ScheduledTaskService`` so GLM5 access and the page store stay in one place.
"""
from __future__ import annotations
from typing import Any
from fastapi import APIRouter, HTTPException, Request
from pydantic import BaseModel, Field
from app.gateway.deps import get_current_user
# Importing the ORM models here (at app-module load, before the lifespan runs
# ``Base.metadata.create_all``) registers ``scheduled_html_pages`` +
# ``html_page_favorites`` with the metadata so the tables are auto-created on
# startup. Without this the store's models are only imported later (inside
# ``langgraph_runtime``), after create_all has already run — yielding
# "no such table: html_page_favorites".
from deerflow.persistence.html_pages.model import ( # noqa: F401
HtmlPageFavoriteRow,
ScheduledHtmlPageRow,
)
from deerflow.runtime.scheduler import get_scheduled_task_service
from deerflow.runtime.scheduler.service import DuplicateFavoriteError
router = APIRouter(prefix="/api/html-page-favorites", tags=["html-page-favorites"])
class HtmlPageFavoriteCreateRequest(BaseModel):
page_id: str = Field(..., description="scheduled_html_pages.id to bookmark")
title: str | None = None
description: str | None = None
tags: list[str] | None = None
async def _user_id(request: Request) -> str:
user_id = await get_current_user(request)
if not user_id:
raise HTTPException(status_code=401, detail="Authentication required")
return user_id
@router.get("")
async def list_html_page_favorites(request: Request) -> list[dict[str, Any]]:
service = get_scheduled_task_service()
user_id = await _user_id(request)
return await service.list_html_page_favorites(user_id)
@router.post("")
async def create_html_page_favorite(body: HtmlPageFavoriteCreateRequest, request: Request) -> dict[str, Any]:
service = get_scheduled_task_service()
user_id = await _user_id(request)
try:
favorite = await service.create_html_page_favorite(
user_id,
body.page_id,
title=body.title,
description=body.description,
tags=body.tags,
)
except DuplicateFavoriteError:
raise HTTPException(status_code=409, detail="该页面已收藏,请勿重复收藏")
if favorite is None:
raise HTTPException(status_code=404, detail="页面不存在或无权收藏")
return favorite
@router.get("/{favorite_id}")
async def get_html_page_favorite(favorite_id: str, request: Request) -> dict[str, Any]:
service = get_scheduled_task_service()
user_id = await _user_id(request)
favorite = await service.get_html_page_favorite(user_id, favorite_id)
if favorite is None:
raise HTTPException(status_code=404, detail=f"Favorite {favorite_id} not found")
return favorite
@router.delete("/{favorite_id}")
async def delete_html_page_favorite(favorite_id: str, request: Request) -> dict[str, Any]:
service = get_scheduled_task_service()
user_id = await _user_id(request)
deleted = await service.delete_html_page_favorite(user_id, favorite_id)
if not deleted:
raise HTTPException(status_code=404, detail=f"Favorite {favorite_id} not found")
return {"success": True}