feat(定时任务): 内置「模板数据转换器」按 HTML 模板填充 JSON 生成大屏
- 模板填充引擎 template_fill.py:模板注册表 + schema 骨架 + 结构校验 + 行级安全填充; 模板自带 DATA 即 schema 来源。no.1 可转换填充(含 MANIFEST 更新时间刷新), no.2 暂为「只展示」(convertible=False) 原样输出已有数据 - 内置 agent template-json-builder(seed + SOUL),随 lifespan 注入,自动出现在 定时任务「执行智能体」下拉 - 调度器新增模板填充执行分支 _run_template_fill_attempt:源 JSON→大模型转换→ 结构校验→失败回喂重试;空源/只展示模板直接用模板默认数据,绝不失败 - API:GET /api/scheduled-tasks/html-templates - 前端:选中该 agent 后条件渲染「HTML模板」下拉 + .json 上传,只展示型模板隐藏 JSON 输入 - 文档同步(前后端 CLAUDE.md) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
1a2ef0a537
commit
cd33aaeded
17 files changed
+1511
-57
No files matched your search
@@ -7,7 +7,8 @@
|
||||
* 任务深链配置读出 rwfx 的 4 个历史按钮,3qfx/xdfx 为空)——这样真实跳转地址通过实时
|
||||
* config 进种子,避免写死;一旦保存过,后端即唯一数据源。
|
||||
*
|
||||
* 按钮两类(``linkType``):``url`` 外链(``window.open``,可选追加 ``?id=任务id``)/
|
||||
* 按钮两类(``linkType``):``url``(外链 ``window.open``;**或应用内路由**——target 以 ``/`` 开头
|
||||
* 时按内部路由处理:``self`` 走 react-router 同窗导航、``blank`` 拼 hash 路由新标签;可选追加 ``?{idParam}=id``)/
|
||||
* ``business`` 内部切换到另一业务(复用 [[task-deeplink]] 的 ``resolveTaskDeeplinkTarget``)。
|
||||
*/
|
||||
|
||||
@@ -20,6 +21,9 @@ import {
|
||||
resolveTaskDeeplinkTarget,
|
||||
} from "@/core/auth/task-deeplink";
|
||||
|
||||
/** 「大屏绘制」独立页应用内路由(深链「上一步」默认指向它,跳转追加 ?taskId=)。 */
|
||||
export const DASHBOARD_AGENT_ROUTE = "/page/strategy/dashboard-agent";
|
||||
|
||||
/** 任务工作区业务码(与路由 taskBase 一一对应)。 */
|
||||
export type TaskBusiness = "rwfx" | "3qfx" | "xdfx";
|
||||
|
||||
@@ -94,7 +98,8 @@ export const TASK_BUSINESS_TARGETS: { code: TaskBusiness; label: string }[] = [
|
||||
|
||||
/**
|
||||
* 代码种子默认按钮(仅在后端整张表为空时使用):
|
||||
* - rwfx:历史 4 个按钮(编辑/下一步/3q详情,地址取自实时任务深链配置)+ 切换到三情/行动分析;
|
||||
* - rwfx:历史按钮(编辑/下一步/3q详情,地址取自实时任务深链配置)+「上一步」+ 切换到三情/行动分析;
|
||||
* 「上一步」紧邻「下一步」左侧(底部栏 prev 组在 next 组前),默认指向应用内大屏绘制页(?taskId=任务id);
|
||||
* - 3qfx / xdfx:互相切换到另外两个业务。
|
||||
* 即「三个业务都带切换按钮」。已保存过则用页面「导入默认」把缺失项补进来。
|
||||
*/
|
||||
@@ -147,6 +152,22 @@ export function defaultTaskButtons(cfg: TaskDeeplinkConfig): TaskButton[] {
|
||||
enabled: true,
|
||||
sortOrder: 0,
|
||||
},
|
||||
{
|
||||
// 上一步:放在「下一步」左边(底部快捷跳转栏,prev 组渲染在 next 组之前)。
|
||||
// 默认指向**应用内**「大屏绘制」页 /page/strategy/dashboard-agent(内部路由,以 / 开头),
|
||||
// 携带 ?taskId=任务id(大屏页按 taskId 取数);openMode=self → 同窗 react-router 导航。
|
||||
id: "rwfx-prev",
|
||||
business: "rwfx",
|
||||
label: "上一步",
|
||||
linkType: "url",
|
||||
target: DASHBOARD_AGENT_ROUTE,
|
||||
appendTaskId: true,
|
||||
idParam: "taskId",
|
||||
idKind: "task",
|
||||
openMode: "self",
|
||||
enabled: true,
|
||||
sortOrder: 1,
|
||||
},
|
||||
{
|
||||
id: "rwfx-next",
|
||||
business: "rwfx",
|
||||
@@ -158,7 +179,7 @@ export function defaultTaskButtons(cfg: TaskDeeplinkConfig): TaskButton[] {
|
||||
idKind: "task",
|
||||
openMode: "blank",
|
||||
enabled: true,
|
||||
sortOrder: 1,
|
||||
sortOrder: 2,
|
||||
},
|
||||
{
|
||||
id: "rwfx-detail3q",
|
||||
@@ -171,11 +192,11 @@ export function defaultTaskButtons(cfg: TaskDeeplinkConfig): TaskButton[] {
|
||||
idKind: "task",
|
||||
openMode: "blank",
|
||||
enabled: true,
|
||||
sortOrder: 2,
|
||||
sortOrder: 3,
|
||||
},
|
||||
sw("rwfx-switch3q", "rwfx", "切换到三情分析", "3qfx", 3),
|
||||
sw("rwfx-switchxd", "rwfx", "切换到行动分析", "xdfx", 4),
|
||||
pz("rwfx-purpose", "rwfx", 5),
|
||||
sw("rwfx-switch3q", "rwfx", "切换到三情分析", "3qfx", 4),
|
||||
sw("rwfx-switchxd", "rwfx", "切换到行动分析", "xdfx", 5),
|
||||
pz("rwfx-purpose", "rwfx", 6),
|
||||
// 3qfx(三情分析):切换到任务/行动分析
|
||||
sw("3qfx-switchrw", "3qfx", "切换到任务分析", "rwfx", 0),
|
||||
sw("3qfx-switchxd", "3qfx", "切换到行动分析", "xdfx", 1),
|
||||
@@ -237,7 +258,18 @@ export async function runTaskButton(btn: TaskButton, ctx: RunTaskButtonCtx): Pro
|
||||
const id = btn.idKind === "action" ? ctx.actionId || ctx.taskId : ctx.taskId;
|
||||
const param = (btn.idParam || "id").trim() || "id";
|
||||
const url = btn.appendTaskId && id ? appendQueryParam(base, param, id) : base;
|
||||
// 打开方式:self=当前窗口(嵌入态即当前 iframe);否则 blank=新窗口(默认)。
|
||||
// 应用内部路由(以单个 / 开头,如「上一步」→ 大屏绘制页):
|
||||
// self → 走 react-router 同窗导航(不刷新、保留 SPA);blank → 拼成当前源的 hash 路由新标签打开。
|
||||
if (base.startsWith("/") && !base.startsWith("//")) {
|
||||
if (btn.openMode === "blank") {
|
||||
const full = `${window.location.origin}${window.location.pathname}#${url}`;
|
||||
window.open(full, "_blank", "noopener,noreferrer");
|
||||
} else {
|
||||
ctx.navigate(url);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 外链:self=当前窗口(嵌入态即当前 iframe);否则 blank=新窗口(默认)。
|
||||
if (btn.openMode === "self") {
|
||||
window.location.assign(url);
|
||||
} else {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { getBackendBaseURL } from "@/core/config";
|
||||
|
||||
import type {
|
||||
HtmlPageFavorite,
|
||||
HtmlTemplateOption,
|
||||
PublishedScheduledTask,
|
||||
ScheduledTask,
|
||||
ScheduledTaskDeliveryProfile,
|
||||
@@ -32,6 +33,14 @@ export async function getSchedulerThread() {
|
||||
return parseResponse<SchedulerThread>(await apiFetch(`${baseUrl()}/thread`));
|
||||
}
|
||||
|
||||
/** 可选的 HTML 模板清单(模板数据转换器填充用)。 */
|
||||
export async function listHtmlTemplates() {
|
||||
const data = await parseResponse<{ templates: HtmlTemplateOption[] }>(
|
||||
await apiFetch(`${baseUrl()}/html-templates`),
|
||||
);
|
||||
return data.templates ?? [];
|
||||
}
|
||||
|
||||
export interface ListScheduledTasksParams {
|
||||
search?: string;
|
||||
tagIds?: string[];
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
deleteScheduledTask,
|
||||
deleteScheduledTaskRun,
|
||||
getSchedulerThread,
|
||||
listHtmlTemplates,
|
||||
getScheduledTaskDeliveryProfile,
|
||||
getScheduledTaskRun,
|
||||
getScheduledTaskRunHtml,
|
||||
@@ -45,6 +46,17 @@ export function useSchedulerThread() {
|
||||
});
|
||||
}
|
||||
|
||||
/** 可选的 HTML 模板清单(模板数据转换器填充用)。模板是后端静态注册,长缓存即可。 */
|
||||
export function useHtmlTemplates(enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: ["scheduled-tasks", "html-templates"],
|
||||
queryFn: listHtmlTemplates,
|
||||
enabled,
|
||||
staleTime: 5 * 60_000,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
}
|
||||
|
||||
/** A task is currently executing — used to drive live polling of the task list. */
|
||||
function taskIsLive(status?: string | null): boolean {
|
||||
return status === "running" || status === "pending";
|
||||
|
||||
@@ -24,8 +24,27 @@ export type HtmlPageConfig = {
|
||||
model_name?: string | null;
|
||||
};
|
||||
|
||||
/** 选中内置「模板数据转换器」agent 后,要把源 JSON 填充进的 HTML 模板。
|
||||
* 持久化在 ``execution_context.template_config`` 里(无独立列)。 */
|
||||
export type TemplateFillConfig = {
|
||||
template_id?: string | null;
|
||||
/** 可选:覆盖转换所用模型(留空走 agent.model / 系统默认)。 */
|
||||
model_name?: string | null;
|
||||
};
|
||||
|
||||
/** ``GET /api/scheduled-tasks/html-templates`` 返回的一条模板选项。 */
|
||||
export type HtmlTemplateOption = {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string | null;
|
||||
/** false = 暂只「直接展示已有数据」,不需要也不会用源 JSON 转换填充。 */
|
||||
convertible?: boolean;
|
||||
};
|
||||
|
||||
export type ScheduledTaskExecutionContext = {
|
||||
require_markdown?: boolean;
|
||||
/** 模板填充模式:选中「模板数据转换器」+ 选模板后填充进固定 HTML 模板。 */
|
||||
template_config?: TemplateFillConfig;
|
||||
/**
|
||||
* The unified "必须生成 HTML" switch. The task form no longer splits "普通任务 /
|
||||
* HTML 页面" into separate kinds — instead ``require_html`` and
|
||||
|
||||
Reference in new issue
Block a user