后端: - 新增 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>
66 lines
3.3 KiB
Python
66 lines
3.3 KiB
Python
"""ORM models for generated HTML pages and user favorites.
|
|
|
|
These back the "HTML 页面定时任务" feature: a scheduled task of kind
|
|
``html_page`` renders a full HTML document each run. Every rendered page is
|
|
recorded in :class:`ScheduledHtmlPageRow`; a user may bookmark one into
|
|
:class:`HtmlPageFavoriteRow`, which also stores a reusable style/layout
|
|
summary so a later task can be generated "in the style of" the favorite.
|
|
|
|
Both tables are brand new, so ``Base.metadata.create_all`` creates them on the
|
|
next startup without touching any existing schema.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import UTC, datetime
|
|
|
|
from sqlalchemy import String, Text
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from deerflow.persistence.base import Base
|
|
from deerflow.persistence.types import BeijingDateTime, PortableJSON, PortableLongText
|
|
|
|
|
|
class ScheduledHtmlPageRow(Base):
|
|
"""One HTML page produced by a single run of an ``html_page`` task."""
|
|
|
|
__tablename__ = "scheduled_html_pages"
|
|
|
|
id: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
task_id: Mapped[str] = mapped_column(String(64), index=True)
|
|
run_id: Mapped[str] = mapped_column(String(64), index=True)
|
|
user_id: Mapped[str] = mapped_column(String(64), index=True)
|
|
title: Mapped[str] = mapped_column(String(255))
|
|
# Full HTML document — can be large, so LONGTEXT on MySQL.
|
|
html_content: Mapped[str] = mapped_column(PortableLongText())
|
|
html_file_path: Mapped[str | None] = mapped_column(Text)
|
|
data_snapshot_json: Mapped[dict | None] = mapped_column(PortableJSON(), default=dict)
|
|
prompt_snapshot_json: Mapped[dict | None] = mapped_column(PortableJSON(), default=dict)
|
|
reference_favorite_id: Mapped[str | None] = mapped_column(String(64), index=True)
|
|
style_summary: Mapped[str | None] = mapped_column(Text)
|
|
layout_summary: Mapped[str | None] = mapped_column(Text)
|
|
created_at: Mapped[datetime] = mapped_column(BeijingDateTime(), default=lambda: datetime.now(UTC))
|
|
updated_at: Mapped[datetime] = mapped_column(BeijingDateTime(), default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC))
|
|
|
|
|
|
class HtmlPageFavoriteRow(Base):
|
|
"""A user-bookmarked HTML page, with a reusable style/layout summary."""
|
|
|
|
__tablename__ = "html_page_favorites"
|
|
|
|
id: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
user_id: Mapped[str] = mapped_column(String(64), index=True)
|
|
page_id: Mapped[str] = mapped_column(String(64), index=True)
|
|
title: Mapped[str] = mapped_column(String(255))
|
|
description: Mapped[str | None] = mapped_column(Text)
|
|
tags_json: Mapped[list | None] = mapped_column(PortableJSON(), default=list)
|
|
# Snapshot of the page HTML at favorite time, decoupled from the source run
|
|
# so deleting the run never breaks the favorite reference.
|
|
html_snapshot: Mapped[str] = mapped_column(PortableLongText())
|
|
style_summary: Mapped[str | None] = mapped_column(Text)
|
|
layout_summary: Mapped[str | None] = mapped_column(Text)
|
|
content_structure_summary: Mapped[str | None] = mapped_column(Text)
|
|
reuse_prompt: Mapped[str | None] = mapped_column(Text)
|
|
created_at: Mapped[datetime] = mapped_column(BeijingDateTime(), default=lambda: datetime.now(UTC))
|
|
updated_at: Mapped[datetime] = mapped_column(BeijingDateTime(), default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC))
|