feat: add workflow orchestration and media governance
This commit is contained in:
+38
-3
@@ -2,7 +2,7 @@ import { mkdir, readFile } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import { DatabaseSync } from "node:sqlite";
|
||||
import { randomBytes, scryptSync } from "node:crypto";
|
||||
import { sampleProject } from "../src/data/sampleProject.js";
|
||||
import { sampleProject, workflowTemplates } from "../src/data/sampleProject.js";
|
||||
import { apiClientStorageMarker, hashApiClientKey } from "./api-client-secrets.mjs";
|
||||
|
||||
const serverRoot = resolve(import.meta.dirname);
|
||||
@@ -219,6 +219,7 @@ function seedRoles() {
|
||||
["project:create", "创建项目"],
|
||||
["project:manage", "管理项目设置"],
|
||||
["project:members:manage", "管理项目成员"],
|
||||
["workflow:manage", "管理生产流程模板和版本"],
|
||||
["task:view", "查看项目协作任务"],
|
||||
["task:manage", "创建、分派和管理项目协作任务"],
|
||||
["task:complete", "更新本人负责的协作任务状态"],
|
||||
@@ -257,10 +258,10 @@ function seedRoles() {
|
||||
org_admin: [
|
||||
"organization:manage", "organization:members:invite", "workspace:create", "workspace:manage",
|
||||
"workspace:members:manage", "project:create", "project:manage", "project:members:manage",
|
||||
"task:view", "task:manage", "task:complete", "model:manage", "usage:view", "billing:manage", "quota:manage", "qa:review", "delivery:approve", "delivery:view", "compliance:manage", "audit:view", "queue:manage", "voice:approve",
|
||||
"workflow:manage", "task:view", "task:manage", "task:complete", "model:manage", "usage:view", "billing:manage", "quota:manage", "qa:review", "delivery:approve", "delivery:view", "compliance:manage", "audit:view", "queue:manage", "voice:approve",
|
||||
"system:settings:view", "service:health:view", "organization:roles:manage"
|
||||
],
|
||||
producer: ["project:create", "project:manage", "project:members:manage", "task:view", "task:manage", "task:complete", "script:read", "job:create", "job:prioritize", "usage:view", "delivery:approve", "delivery:view", "voice:approve"],
|
||||
producer: ["project:create", "project:manage", "project:members:manage", "workflow:manage", "task:view", "task:manage", "task:complete", "script:read", "job:create", "job:prioritize", "usage:view", "delivery:approve", "delivery:view", "voice:approve"],
|
||||
writer: ["script:read", "script:edit", "task:view", "task:complete", "job:create"],
|
||||
art_director: ["asset:edit", "prompt:edit", "task:view", "task:complete", "job:create"],
|
||||
voice_editor: ["voice:edit", "voice:approve", "task:view", "task:complete", "job:create"],
|
||||
@@ -384,8 +385,11 @@ function seedSystemGovernance() {
|
||||
["app.timezone", "general", "Asia/Shanghai", "string", "默认时区", 0],
|
||||
["deployment.mode", "deployment", "private-local", "string", "部署模式", 0],
|
||||
["deployment.local_runner_only", "deployment", true, "boolean", "默认只允许本地 Runner", 0],
|
||||
["storage.provider", "storage", "local-filesystem", "string", "媒体文件存储提供方;生产可切换为 S3-compatible 适配层", 0],
|
||||
["storage.root_path", "storage", resolve(projectRoot, "storage"), "path", "资产和生成产物根目录", 0],
|
||||
["storage.max_upload_mb", "storage", 1024, "number", "单文件最大上传大小", 0],
|
||||
["storage.retention_days", "storage", 30, "number", "无引用临时媒体的最短保留天数", 0],
|
||||
["storage.cleanup_unreferenced_only", "storage", true, "boolean", "清理时只允许处理未被生产证据引用的临时文件", 0],
|
||||
["queue.max_concurrency", "queue", 2, "number", "全局并发任务数", 0],
|
||||
["generation.single_frame_only", "generation", true, "boolean", "一图一画面策略", 0],
|
||||
["generation.require_actual_last_frame", "generation", true, "boolean", "视频片段必须使用真实末帧连续", 0],
|
||||
@@ -433,6 +437,36 @@ function seedSystemGovernance() {
|
||||
}
|
||||
}
|
||||
|
||||
function workflowStepDefinition(label, index) {
|
||||
const normalized = String(label || "");
|
||||
let jobKind = "";
|
||||
let requiresShot = false;
|
||||
if (/静态图|分镜图|关键帧|插画/.test(normalized)) { jobKind = "单画面关键帧"; requiresShot = true; }
|
||||
else if (/图生视频|视频生成|成片/.test(normalized)) { jobKind = "首尾帧图生视频"; requiresShot = true; }
|
||||
else if (/旁白|配音|TTS/.test(normalized)) { jobKind = "固定 TTS 配音"; requiresShot = true; }
|
||||
else if (/ASR|字幕|对齐/.test(normalized)) { jobKind = "ASR 台词校验"; requiresShot = true; }
|
||||
else if (/合成/.test(normalized)) jobKind = "剪辑合成清单";
|
||||
return { key: `step-${index + 1}`, label: normalized, jobKind, requiresShot, dependsOnPrevious: index > 0 };
|
||||
}
|
||||
|
||||
function seedWorkflowTemplates() {
|
||||
const timestamp = now();
|
||||
for (const template of workflowTemplates) {
|
||||
const status = template.status === "主流程" || template.status === "可用" ? "active" : "draft";
|
||||
const steps = template.steps.map((label, index) => workflowStepDefinition(label, index));
|
||||
const gates = [
|
||||
{ key: "single-frame", label: "一图一画面", blocking: true },
|
||||
{ key: "continuity-lock", label: "角色 / 场景 / 道具连续性", blocking: true },
|
||||
{ key: "voice-subtitle-asr", label: "声音 / 字幕 / ASR 对齐", blocking: true },
|
||||
{ key: "clip-bridge", label: "片段衔接 / 实际末帧", blocking: true }
|
||||
];
|
||||
insertIgnore(
|
||||
"INSERT OR IGNORE INTO workflow_templates(id, organization_id, workspace_id, template_key, version_number, name, category, status, description, steps_json, gates_json, default_adapter_id, created_by, created_at, updated_at) VALUES (?, NULL, NULL, ?, 1, ?, ?, ?, ?, ?, ?, 'owned-model-platform', 'u-owner', ?, ?)",
|
||||
[`workflow-global-${template.id}-v1`, template.id, template.name, template.id, status, template.warning || "", JSON.stringify(steps), JSON.stringify(gates), timestamp, timestamp]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function seedUserNotifications() {
|
||||
const timestamp = now();
|
||||
insertIgnore(
|
||||
@@ -583,6 +617,7 @@ withTransaction(() => {
|
||||
seedMembersAndProjects();
|
||||
seedModels();
|
||||
seedSystemGovernance();
|
||||
seedWorkflowTemplates();
|
||||
seedProductionGraph();
|
||||
seedUserNotifications();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user