docs: 为全部代码补充中文注释并加入 AGENTS 注释规范
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
// WOV 静态前端公共脚本:所有页面共用的 API 封装、渲染函数与事件绑定。
|
||||
|
||||
// Echo 演示节点的预置 manifest,便于管理后台一键填入。
|
||||
const ECHO_MANIFEST = {
|
||||
id: "echo",
|
||||
name: "Echo Node",
|
||||
@@ -14,6 +17,7 @@ const ECHO_MANIFEST = {
|
||||
keep_warm: false,
|
||||
};
|
||||
|
||||
// 演示“视频字幕生成”工作流的 DAG 定义,预填在工作流编排页。
|
||||
const DEMO_WORKFLOW = {
|
||||
name: "视频字幕生成",
|
||||
version: 1,
|
||||
@@ -55,6 +59,7 @@ const DEMO_WORKFLOW = {
|
||||
},
|
||||
};
|
||||
|
||||
// 统一封装 fetch:自动携带 JSON 头、解析响应并在失败时抛出可读错误。
|
||||
async function api(path, options = {}) {
|
||||
const response = await fetch(path, {
|
||||
headers: { "Content-Type": "application/json", ...(options.headers || {}) },
|
||||
@@ -62,12 +67,14 @@ async function api(path, options = {}) {
|
||||
});
|
||||
const data = await response.json().catch(() => null);
|
||||
if (!response.ok) {
|
||||
// FastAPI 的校验错误 detail 可能是数组,统一序列化为字符串展示。
|
||||
const detail = data && data.detail ? JSON.stringify(data.detail) : response.statusText;
|
||||
throw new Error(`${response.status} ${detail}`);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
// 转义用户可控文本,防止 XSS 注入到表格或状态 HTML 中。
|
||||
function escapeHtml(value) {
|
||||
return String(value)
|
||||
.replaceAll("&", "&")
|
||||
@@ -77,7 +84,9 @@ function escapeHtml(value) {
|
||||
.replaceAll("'", "'");
|
||||
}
|
||||
|
||||
// 根据状态生成带语义颜色的徽章 HTML。
|
||||
function badge(status) {
|
||||
// 统一转小写比较,兼容后端返回的不同大小写。
|
||||
const value = String(status).toLowerCase();
|
||||
const className =
|
||||
value === "ready" || value === "completed" || value === "ok"
|
||||
@@ -88,12 +97,14 @@ function badge(status) {
|
||||
return `<span class="badge ${className}">${escapeHtml(status)}</span>`;
|
||||
}
|
||||
|
||||
// 把 ISO 时间格式化为本地时间;非法值原样返回。
|
||||
function formatTime(value) {
|
||||
if (!value) return "";
|
||||
const date = new Date(value);
|
||||
return Number.isNaN(date.getTime()) ? value : date.toLocaleString();
|
||||
}
|
||||
|
||||
// 计算距给定时间的流逝时长,用于任务页展示已运行时间。
|
||||
function formatElapsed(value) {
|
||||
if (!value) return "";
|
||||
const seconds = Math.floor((Date.now() - new Date(value).getTime()) / 1000);
|
||||
@@ -102,6 +113,7 @@ function formatElapsed(value) {
|
||||
return `${Math.floor(seconds / 60)} 分钟`;
|
||||
}
|
||||
|
||||
// 健康检查失败时在页面顶部展示后端不可用提示。
|
||||
async function loadHealth() {
|
||||
try {
|
||||
await api("/health");
|
||||
@@ -113,6 +125,7 @@ async function loadHealth() {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载已注册节点并渲染节点表格与调用下拉框。
|
||||
async function loadNodes() {
|
||||
const nodes = await api("/api/admin/nodes");
|
||||
const tbody = document.getElementById("nodeList");
|
||||
@@ -136,6 +149,7 @@ async function loadNodes() {
|
||||
.join("");
|
||||
}
|
||||
|
||||
// 加载节点实例并渲染实例表格。
|
||||
async function loadInstances() {
|
||||
const instances = await api("/api/admin/node-instances");
|
||||
const tbody = document.getElementById("instanceList");
|
||||
@@ -156,10 +170,12 @@ async function loadInstances() {
|
||||
: '<tr><td colspan="6">暂无实例</td></tr>';
|
||||
}
|
||||
|
||||
// 管理后台页面同时刷新节点与实例两个列表。
|
||||
async function refresh() {
|
||||
await Promise.all([loadNodes(), loadInstances()]);
|
||||
}
|
||||
|
||||
// 注册节点:解析文本域中的 manifest JSON 并提交到后端。
|
||||
async function registerNode() {
|
||||
const raw = document.getElementById("manifestJson").value.trim();
|
||||
try {
|
||||
@@ -172,6 +188,7 @@ async function registerNode() {
|
||||
}
|
||||
}
|
||||
|
||||
// 手动调用节点:读取表单参数并展示返回结果。
|
||||
async function invokeNode() {
|
||||
const nodeId = document.getElementById("invokeNodeId").value;
|
||||
const runId = document.getElementById("invokeRunId").value.trim() || "run_web";
|
||||
@@ -190,6 +207,7 @@ async function invokeNode() {
|
||||
}
|
||||
}
|
||||
|
||||
// 删除节点前不做二次确认,由点击按钮触发。
|
||||
async function deleteNode(nodeId) {
|
||||
try {
|
||||
await api(`/api/admin/nodes/${nodeId}`, { method: "DELETE" });
|
||||
@@ -199,6 +217,7 @@ async function deleteNode(nodeId) {
|
||||
}
|
||||
}
|
||||
|
||||
// 请求后端停止指定节点实例。
|
||||
async function stopInstance(instanceId) {
|
||||
try {
|
||||
await api(`/api/admin/node-instances/${instanceId}/stop`, { method: "POST" });
|
||||
@@ -208,6 +227,7 @@ async function stopInstance(instanceId) {
|
||||
}
|
||||
}
|
||||
|
||||
// 应用中心的 Echo 演示:直接调用 echo 节点并展示结果。
|
||||
async function runEcho() {
|
||||
const text = document.getElementById("echoText").value;
|
||||
const resultBox = document.getElementById("echoResult");
|
||||
@@ -227,6 +247,7 @@ async function runEcho() {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载工作流列表并渲染发布状态与操作按钮。
|
||||
async function loadWorkflows() {
|
||||
const workflows = await api("/api/admin/workflows");
|
||||
const tbody = document.getElementById("workflowList");
|
||||
@@ -250,6 +271,7 @@ async function loadWorkflows() {
|
||||
: '<tr><td colspan="5">暂无工作流</td></tr>';
|
||||
}
|
||||
|
||||
// 加载最近任务并渲染任务表格,失败任务提供重试按钮。
|
||||
async function loadRuns() {
|
||||
const tbody = document.getElementById("runList");
|
||||
if (!tbody) return;
|
||||
@@ -259,6 +281,7 @@ async function loadRuns() {
|
||||
.map((run) => {
|
||||
const percent = Math.round((run.progress || 0) * 100);
|
||||
const error = run.error || "";
|
||||
// RUNNING/QUEUED 附加耗时或排队提示,其余状态只显示徽章。
|
||||
const statusHtml =
|
||||
run.status === "RUNNING"
|
||||
? `${badge(run.status)} <span class="muted">已运行 ${formatElapsed(run.updated_at)}</span>`
|
||||
@@ -285,6 +308,7 @@ async function loadRuns() {
|
||||
: '<tr><td colspan="8">暂无任务</td></tr>';
|
||||
}
|
||||
|
||||
// 请求后端重试失败任务,成功后刷新列表。
|
||||
async function retryRun(runId) {
|
||||
try {
|
||||
const result = await api(`/api/runs/${runId}/retry`, { method: "POST" });
|
||||
@@ -295,6 +319,7 @@ async function retryRun(runId) {
|
||||
}
|
||||
}
|
||||
|
||||
// 创建或更新工作流:解析 DAG JSON 后提交,随后刷新列表。
|
||||
async function createWorkflow() {
|
||||
const workflowId = document.getElementById("workflowId").value.trim();
|
||||
const name = document.getElementById("workflowName").value.trim();
|
||||
@@ -323,6 +348,7 @@ async function createWorkflow() {
|
||||
}
|
||||
}
|
||||
|
||||
// 发布指定工作流,使其出现在用户应用中心。
|
||||
async function publishWorkflow(workflowId) {
|
||||
try {
|
||||
await api(`/api/admin/workflows/${workflowId}/publish`, { method: "POST" });
|
||||
@@ -332,6 +358,7 @@ async function publishWorkflow(workflowId) {
|
||||
}
|
||||
}
|
||||
|
||||
// 删除指定工作流。
|
||||
async function deleteWorkflow(workflowId) {
|
||||
try {
|
||||
await api(`/api/admin/workflows/${workflowId}`, { method: "DELETE" });
|
||||
@@ -341,6 +368,7 @@ async function deleteWorkflow(workflowId) {
|
||||
}
|
||||
}
|
||||
|
||||
// 上传视频并轮询任务进度,完成后渲染下载链接。
|
||||
async function uploadVideo() {
|
||||
const fileInput = document.getElementById("videoFile");
|
||||
const progress = document.getElementById("runProgress");
|
||||
@@ -365,6 +393,7 @@ async function uploadVideo() {
|
||||
}
|
||||
}
|
||||
|
||||
// 每秒轮询一次任务状态,最多 3600 次(约 1 小时),超时后提示查看接口。
|
||||
async function pollRun(runId, progress, downloads) {
|
||||
for (let i = 0; i < 3600; i += 1) {
|
||||
const run = await api(`/api/runs/${runId}`);
|
||||
@@ -383,6 +412,7 @@ async function pollRun(runId, progress, downloads) {
|
||||
progress.textContent = "轮询超时,请到 /api/runs 查看任务状态";
|
||||
}
|
||||
|
||||
// 只展示用户关心的最终产物(中文 SRT 与 ASS)下载入口。
|
||||
function renderArtifacts(runId, artifacts, container) {
|
||||
container.innerHTML = artifacts
|
||||
.filter((item) => ["cn_srt", "ass"].includes(item.name))
|
||||
@@ -395,6 +425,7 @@ function renderArtifacts(runId, artifacts, container) {
|
||||
.join("");
|
||||
}
|
||||
|
||||
// 全局点击委托:按按钮上的 data-* 属性分发到对应操作。
|
||||
document.addEventListener("click", (event) => {
|
||||
const deleteNodeButton = event.target.closest("[data-delete-node]");
|
||||
if (deleteNodeButton) {
|
||||
@@ -422,6 +453,7 @@ document.addEventListener("click", (event) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 页面初始化:预填 manifest/DAG、绑定按钮事件并加载对应页面数据。
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
const manifestBox = document.getElementById("manifestJson");
|
||||
if (manifestBox) {
|
||||
@@ -471,6 +503,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
if (document.getElementById("workflowList")) {
|
||||
await loadWorkflows();
|
||||
}
|
||||
// 任务管理页每 5 秒自动刷新一次状态。
|
||||
if (document.getElementById("runList")) {
|
||||
await loadRuns();
|
||||
setInterval(loadRuns, 5000);
|
||||
|
||||
Reference in New Issue
Block a user