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>
This commit is contained in:
AdministratorandClaude Opus 4.8 committed 2026-06-02 23:46:20 +08:00
1 parent e1e9be8daa
commit d9afaca51f
22 files changed
+1996 -107

No files matched your search

@@ -2,12 +2,15 @@ import { apiFetch } from "@/core/api";
import { getBackendBaseURL } from "@/core/config";
import type {
HtmlPageFavorite,
PublishedScheduledTask,
ScheduledTask,
ScheduledTaskDeliveryProfile,
ScheduledTaskExecutionContext,
ScheduledTaskResultFileContent,
ScheduledTaskRun,
ScheduledTaskRunFileUpdateResponse,
ScheduledTaskRunHtml,
ScheduledTaskRunMessagesResponse,
ScheduledTaskSubscription,
SchedulerThread,
@@ -52,6 +55,7 @@ export async function createScheduledTask(data: {
timezone?: string | null;
execution_agent_name?: string | null;
require_markdown?: boolean;
execution_context?: ScheduledTaskExecutionContext | null;
}) {
return parseResponse<ScheduledTask>(
await apiFetch(baseUrl(), {
@@ -212,6 +216,7 @@ export async function updateScheduledTask(
enabled: boolean;
execution_agent_name: string | null;
require_markdown: boolean;
execution_context: ScheduledTaskExecutionContext | null;
}>,
) {
return parseResponse<ScheduledTask>(
@@ -368,3 +373,47 @@ export async function adminTriggerScheduledTask(taskId: string) {
}),
);
}
// --- HTML page tasks ---
/** Fetch the generated HTML document for an ``html_page`` run. */
export async function getScheduledTaskRunHtml(runId: string) {
return parseResponse<ScheduledTaskRunHtml>(
await apiFetch(`${baseUrl()}/runs/${encodeURIComponent(runId)}/html`),
);
}
const favoritesUrl = () => `${getBackendBaseURL()}/api/html-page-favorites`;
export async function listHtmlPageFavorites() {
return parseResponse<HtmlPageFavorite[]>(await apiFetch(favoritesUrl()));
}
export async function getHtmlPageFavorite(favoriteId: string) {
return parseResponse<HtmlPageFavorite>(
await apiFetch(`${favoritesUrl()}/${encodeURIComponent(favoriteId)}`),
);
}
export async function createHtmlPageFavorite(data: {
page_id: string;
title?: string | null;
description?: string | null;
tags?: string[];
}) {
return parseResponse<HtmlPageFavorite>(
await apiFetch(favoritesUrl(), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
}),
);
}
export async function deleteHtmlPageFavorite(favoriteId: string) {
return parseResponse<{ success: boolean }>(
await apiFetch(`${favoritesUrl()}/${encodeURIComponent(favoriteId)}`, {
method: "DELETE",
}),
);
}
@@ -10,14 +10,18 @@ import {
adminListScheduledTaskRuns,
adminTriggerScheduledTask,
adminUpdateScheduledTask,
createHtmlPageFavorite,
createScheduledTask,
deleteHtmlPageFavorite,
deleteScheduledTask,
deleteScheduledTaskRun,
getSchedulerThread,
getScheduledTaskDeliveryProfile,
getScheduledTaskRun,
getScheduledTaskRunHtml,
getScheduledTaskRunMessages,
listAllScheduledTasks,
listHtmlPageFavorites,
listPublishedScheduledTasks,
listScheduledTaskRuns,
listScheduledTasks,
@@ -112,6 +116,42 @@ export function useScheduledTaskRunMessages(
});
}
/** Generated HTML for an ``html_page`` run; polls while the run is in flight. */
export function useScheduledTaskRunHtml(
runId?: string | null,
options: { enabled?: boolean; live?: boolean } = {},
) {
return useQuery({
queryKey: ["scheduled-tasks", "runs", runId, "html"],
queryFn: () => getScheduledTaskRunHtml(runId!),
enabled: !!runId && (options.enabled ?? true),
refetchOnWindowFocus: false,
retry: false,
refetchInterval: options.live ? 5000 : false,
});
}
/** The current user's bookmarked HTML pages. */
export function useHtmlPageFavorites(enabled: boolean = true) {
return useQuery({
queryKey: ["html-page-favorites"],
queryFn: listHtmlPageFavorites,
enabled,
refetchOnWindowFocus: false,
});
}
/** Create / delete HTML page favorites. */
export function useHtmlPageFavoriteActions() {
const queryClient = useQueryClient();
const invalidate = () =>
queryClient.invalidateQueries({ queryKey: ["html-page-favorites"] });
return {
create: useMutation({ mutationFn: createHtmlPageFavorite, onSuccess: invalidate }),
remove: useMutation({ mutationFn: deleteHtmlPageFavorite, onSuccess: invalidate }),
};
}
export function useScheduledTaskDeliveryProfile() {
return useQuery({
queryKey: ["scheduled-tasks", "delivery-profile"],
+59 -6
View File
@@ -11,6 +11,26 @@ export type ScheduledTaskSubscription = {
updated_at?: string | null;
};
/** Task kinds. A bare/absent kind is the regular agent task. */
export type ScheduledTaskKind = "agent" | "html_page";
/** Config for an ``html_page`` task, persisted inside ``execution_context``.
* Data gathering is delegated to the agent's own search skills, so there is no
* explicit search query / data source — the page prompt drives everything. */
export type HtmlPageConfig = {
page_prompt?: string | null;
negative_prompt?: string | null;
reference_favorite_id?: string | null;
model_name?: string | null;
};
export type ScheduledTaskExecutionContext = {
require_markdown?: boolean;
task_kind?: ScheduledTaskKind;
html_config?: HtmlPageConfig;
[key: string]: unknown;
};
export type ScheduledTask = {
task_id: string;
name: string;
@@ -21,11 +41,11 @@ export type ScheduledTask = {
timezone: string;
enabled: boolean;
/**
* Free-form execution context persisted with the task. ``require_markdown``
* lives here (rather than a dedicated column) so the feature needs no schema
* migration.
* Free-form execution context persisted with the task. ``require_markdown``,
* ``task_kind`` and ``html_config`` live here (rather than dedicated columns)
* so the feature needs no schema migration.
*/
execution_context?: { require_markdown?: boolean; [key: string]: unknown } | null;
execution_context?: ScheduledTaskExecutionContext | null;
next_run_at?: string | null;
last_run_at?: string | null;
last_status?: string | null;
@@ -70,7 +90,7 @@ export type ScheduledTaskResultFile = {
path?: string | null;
mime_type?: string | null;
size?: number | null;
kind?: "markdown" | "attachment" | "artifact" | null;
kind?: "markdown" | "attachment" | "artifact" | "html" | null;
};
export type ScheduledTaskResultFileContent = {
@@ -86,9 +106,42 @@ export type ScheduledTaskResultFileContent = {
export type ScheduledTaskRunResult = {
title: string;
content: string;
format: "markdown" | "text" | "json" | "mixed";
format: "markdown" | "text" | "json" | "mixed" | "html";
files: ScheduledTaskResultFile[];
created_at: string;
/** Present for ``html_page`` runs: the full generated HTML document. */
html?: string | null;
style_summary?: string | null;
layout_summary?: string | null;
};
/** Response of the run/latest HTML preview endpoints. */
export type ScheduledTaskRunHtml = {
run_id: string;
task_id?: string | null;
title?: string | null;
html?: string | null;
style_summary?: string | null;
layout_summary?: string | null;
page_id?: string | null;
created_at?: string | null;
};
/** A user-bookmarked generated HTML page. */
export type HtmlPageFavorite = {
id: string;
user_id: string;
page_id: string;
title: string;
description?: string | null;
tags?: string[];
html_snapshot?: string;
style_summary?: string | null;
layout_summary?: string | null;
content_structure_summary?: string | null;
reuse_prompt?: string | null;
created_at?: string | null;
updated_at?: string | null;
};
export type ScheduledTaskRun = {