Files
xinjian0101-continuity-dire…/js/continuity_director.js
xinjian0101 2f3428b6dd feat: release Continuity Director v0.8.20 after 20 iterations
Merge 20 sequential plugin iterations covering lifecycle validation, frontend smoke tests, six reliability nodes, bilingual help pages, installable ZIP packaging, and expanded CI.
2026-06-19 15:32:07 +08:00

38 lines
13 KiB
JavaScript

import { app } from "../../scripts/app.js";
const NAME="continuity-director.dashboard", VER="0.8.20", KEY="continuity-director.language";
const groups={
locks:[["CDProjectLock","projectLock","pi-folder"],["CDCharacterLock","characterLock","pi-user"],["CDSceneLock","sceneLock","pi-map-marker"],["CDShotLock","shotLock","pi-video"],["CDManifestBuilder","manifest","pi-box"]],
directing:[["CDBatchDirector","batchDirector","pi-sitemap"],["CDReferenceHandoff","referenceHandoff","pi-link"]],
quality:[["CDQualityGate","qualityGate","pi-shield"],["CDTakeRanker","takeRanker","pi-sort-amount-down"],["CDContinuityReport","continuityReport","pi-search"]],
runtime:[["CDExecutionPlan","executionPlan","pi-bolt"]],
collaboration:[["CDAuditEvent","auditEvent","pi-history"],["CDThreeWayMerge","threeWayMerge","pi-code-branch"]],
export:[["CDExportPackage","exportPackage","pi-download"]],
reliability:[["CDVerifyPackage","verifyPackage","pi-check-circle"],["CDMigratePayload","migratePayload","pi-sync"],["CDRetryPolicy","retryPolicy","pi-refresh"],["CDQueueCheckpoint","queueCheckpoint","pi-save"],["CDIdempotencyKey","idempotencyKey","pi-key"],["CDEnvironmentLock","environmentLock","pi-lock"]]
};
const en={title:"Continuity Director",subtitle:"Production continuity control",overview:"Overview",nodes:"Nodes",guide:"Guide",language:"Language",cdNodes:"CD nodes",connections:"Connections",projectState:"Project state",ready:"Ready",empty:"Not configured",quickStart:"Quick start",quickStartHint:"Add the core production chain to the current workflow.",addStarter:"Add starter chain",fitNodes:"Focus CD nodes",nodeLibrary:"Node library",pipeline:"Recommended pipeline",health:"Workflow health",noNodes:"No Continuity Director nodes in this workflow.",active:"Active",locks:"Continuity locks",directing:"Directing",quality:"Quality",runtime:"Runtime",collaboration:"Collaboration",export:"Export",reliability:"Reliability",step1:"Lock project, characters, scenes, and shots.",step2:"Build a manifest and expand storyboard takes.",step3:"Rank takes and block failed quality gates.",step4:"Create execution waves, audit decisions, and export.",docs:"Open documentation",added:"Node added",starterAdded:"Starter chain added",projectLock:"Project Lock",characterLock:"Character Lock",sceneLock:"Scene Lock",shotLock:"Shot Lock",manifest:"Manifest Builder",batchDirector:"Batch Director",referenceHandoff:"Reference Handoff",qualityGate:"Quality Gate",takeRanker:"Take Ranker",continuityReport:"Continuity Report",executionPlan:"Execution Plan",auditEvent:"Audit Event",threeWayMerge:"Three-Way Merge",exportPackage:"Export Package",verifyPackage:"Verify Package",migratePayload:"Migrate Payload",retryPolicy:"Retry Policy",queueCheckpoint:"Queue Checkpoint",idempotencyKey:"Idempotency Key",environmentLock:"Environment Lock"};
const zh={title:"连续性导演",subtitle:"AI 视频生产连续性控制台",overview:"总览",nodes:"节点",guide:"流程指南",language:"语言",cdNodes:"插件节点",connections:"连接数",projectState:"项目状态",ready:"可用",empty:"未配置",quickStart:"快速开始",quickStartHint:"向当前工作流添加一套核心生产链。",addStarter:"添加入门链",fitNodes:"聚焦插件节点",nodeLibrary:"节点库",pipeline:"推荐流程",health:"工作流状态",noNodes:"当前工作流还没有连续性导演节点。",active:"已启用",locks:"连续性锁",directing:"导演编排",quality:"质量控制",runtime:"运行计划",collaboration:"协作审计",export:"导出",reliability:"可靠性",step1:"锁定项目、角色、场景和镜头。",step2:"生成清单,并将分镜扩展为多个 Take。",step3:"对 Take 排名,并阻止未通过质量门的结果。",step4:"生成执行波次、记录审计事件并导出。",docs:"打开文档",added:"节点已添加",starterAdded:"入门链已添加",projectLock:"项目锁",characterLock:"角色锁",sceneLock:"场景锁",shotLock:"镜头锁",manifest:"清单构建器",batchDirector:"批量导演",referenceHandoff:"参考帧承接",qualityGate:"质量门",takeRanker:"Take 排名",continuityReport:"连续性报告",executionPlan:"执行计划",auditEvent:"审计事件",threeWayMerge:"三方合并",exportPackage:"导出生产包",verifyPackage:"校验生产包",migratePayload:"迁移数据结构",retryPolicy:"重试策略",queueCheckpoint:"队列断点",idempotencyKey:"幂等键",environmentLock:"环境锁"};
const lang=()=>["en","zh-CN","bilingual"].includes(localStorage.getItem(KEY))?localStorage.getItem(KEY):"en";
const t=(k,l=lang())=>l==="bilingual"?`${en[k]||k} / ${zh[k]||en[k]||k}`:l==="zh-CN"?(zh[k]||en[k]||k):(en[k]||k);
const el=(tag,c,text)=>{const n=document.createElement(tag);if(c)n.className=c;if(text!==undefined)n.textContent=text;return n};
const ic=n=>el("i",`pi ${n||"pi-circle"}`);
function css(){if(document.querySelector("link[data-cd-style]"))return;const l=document.createElement("link");l.rel="stylesheet";l.href=new URL("./continuity_director.css",import.meta.url).href;l.dataset.cdStyle="1";document.head.append(l)}
function isCD(n){return String(n?.comfyClass||n?.type||"").startsWith("CD")}
function stats(){const nodes=(app.graph?._nodes||[]).filter(isCD),ids=new Set(nodes.map(n=>n.id));let links=0;for(const n of nodes)for(const o of n.outputs||[])for(const id of o.links||[]){const x=app.graph?.links?.[id];if(x&&ids.has(x.target_id))links++}return{nodes,links}}
function toast(a,b=""){app.extensionManager?.toast?.add?.({severity:"success",summary:a,detail:b,life:2000})}
function center(){const c=app.canvas;if(Array.isArray(c?.graph_mouse))return [...c.graph_mouse];const s=c?.ds?.scale||1,o=c?.ds?.offset||[0,0];return[(c?.canvas?.width||1200)/2/s-o[0],(c?.canvas?.height||800)/2/s-o[1]]}
function add(type,pos){const n=window.LiteGraph?.createNode?.(type);if(!n||!app.graph)return null;n.pos=pos||center();app.graph.add(n);app.canvas?.setDirty?.(true,true);toast(t("added"),n.title||type);return n}
function starter(){const b=center(),spec=[["CDProjectLock",0,0],["CDCharacterLock",0,250],["CDSceneLock",0,500],["CDShotLock",360,250],["CDManifestBuilder",720,120],["CDBatchDirector",1080,120],["CDExecutionPlan",1440,120],["CDExportPackage",1800,120]],m=new Map;for(const[x,a,c]of spec)m.set(x,add(x,[b[0]+a,b[1]+c]));const c=(a,ao,z,zi)=>m.get(a)?.connect?.(ao,m.get(z),zi);c("CDProjectLock",0,"CDShotLock",0);c("CDSceneLock",0,"CDShotLock",1);c("CDCharacterLock",0,"CDShotLock",2);c("CDProjectLock",0,"CDManifestBuilder",0);c("CDCharacterLock",0,"CDManifestBuilder",1);c("CDSceneLock",0,"CDManifestBuilder",2);c("CDShotLock",0,"CDManifestBuilder",3);c("CDManifestBuilder",0,"CDBatchDirector",0);c("CDBatchDirector",0,"CDExecutionPlan",0);c("CDManifestBuilder",0,"CDExportPackage",0);c("CDBatchDirector",0,"CDExportPackage",1);c("CDExecutionPlan",0,"CDExportPackage",2);toast(t("starterAdded"))}
function focus(){const n=stats().nodes;if(!n.length)return;if(n.length===1)return app.canvas?.centerOnNode?.(n[0]);app.canvas?.selectNodes?.(n);app.canvas?.fitViewToSelection?.();app.canvas?.setDirty?.(true,true)}
function button(label,kind,fn,icon){const b=el("button",`cd-button ${kind}`);b.append(ic(icon),document.createTextNode(label));b.onclick=fn;return b}
function header(host,l){const h=el("header","cd-header"),brand=el("div","cd-brand"),mark=el("div","cd-mark");mark.innerHTML='<span class="cd-ring"></span><span class="cd-core"></span>';const copy=el("div","cd-brand-copy");copy.append(el("strong","",t("title",l)),el("span","",t("subtitle",l)));brand.append(mark,copy);const s=document.createElement("select");s.className="cd-language";for(const[v,x]of[["en","English"],["zh-CN","中文"],["bilingual","EN / 中文"]]){const o=document.createElement("option");o.value=v;o.textContent=x;o.selected=l===v;s.append(o)}s.onchange=()=>{localStorage.setItem(KEY,s.value);render(host)};h.append(brand,s);return h}
function metric(icon,label,value,state=""){const x=el("div",`cd-metric ${state}`),v=el("div","cd-metric-icon"),c=el("div","cd-metric-copy");v.append(ic(icon));c.append(el("b","",String(value)),el("span","",label));x.append(v,c);return x}
function overview(l){const s=stats(),v=el("section","cd-view"),ms=el("div","cd-metrics");ms.append(metric("pi-box",t("cdNodes",l),s.nodes.length),metric("pi-share-alt",t("connections",l),s.links),metric(s.nodes.length?"pi-check-circle":"pi-minus-circle",t("projectState",l),t(s.nodes.length?"ready":"empty",l),s.nodes.length?"ok":"idle"));const q=el("div","cd-card"),qcopy=el("div");qcopy.append(el("h3","",t("quickStart",l)),el("p","",t("quickStartHint",l)));const acts=el("div","cd-actions");acts.append(button(t("addStarter",l),"primary",starter,"pi-plus"),button(t("fitNodes",l),"secondary",focus,"pi-expand"));q.append(qcopy,acts);const health=el("div","cd-card");health.append(el("h3","",t("health",l)));const row=el("div","cd-health"),dot=el("span",`cd-dot ${s.nodes.length?"ok":"idle"}`);row.append(dot,el("span","",s.nodes.length?t("active",l):t("noNodes",l)));health.append(row);v.append(ms,q,health);return v}
function library(l){const v=el("section","cd-view"),head=el("div","cd-section-head");head.append(el("h3","",t("nodeLibrary",l)),el("span","cd-chip",`v${VER}`));v.append(head);for(const[k,list]of Object.entries(groups)){const g=el("div","cd-group"),gh=el("div","cd-group-head");gh.append(el("span","",t(k,l)),el("span","cd-chip",String(list.length)));g.append(gh);for(const[type,label,icon]of list){const b=el("button","cd-node-button"),i=el("span","cd-node-icon"),c=el("span","cd-node-copy");i.append(ic(icon));c.append(el("b","",t(label,l)),el("small","",type));b.append(i,c,ic("pi-plus"));b.onclick=()=>add(type);g.append(b)}v.append(g)}return v}
function guide(l){const v=el("section","cd-view"),line=el("div","cd-timeline");v.append(el("h3","",t("pipeline",l)));["step1","step2","step3","step4"].forEach((k,i)=>{const r=el("div","cd-step");r.append(el("span","cd-step-no",String(i+1)),el("p","",t(k,l)));line.append(r)});v.append(line,button(t("docs",l),"secondary",()=>window.open("https://github.com/xinjian0101/continuity-director","_blank","noopener,noreferrer"),"pi-external-link"));return v}
function render(host){if(!host)return;const l=lang(),root=el("div","cd-dashboard"),tabs=el("nav","cd-tabs"),main=el("main","cd-content"),views={overview:overview(l),nodes:library(l),guide:guide(l)};host.replaceChildren();host.classList.add("cd-host");root.append(header(host,l));let active=host.dataset.cdTab||"overview";const show=id=>{active=id;host.dataset.cdTab=id;tabs.querySelectorAll("button").forEach(b=>b.classList.toggle("active",b.dataset.tab===id));main.replaceChildren(views[id])};for(const[id,icon]of[["overview","pi-chart-bar"],["nodes","pi-th-large"],["guide","pi-compass"]]){const b=el("button","cd-tab");b.dataset.tab=id;b.append(ic(icon),document.createTextNode(t(id,l)));b.onclick=()=>show(id);tabs.append(b)}const foot=el("footer","cd-footer",`Continuity Director v${VER} · 20 nodes`);root.append(tabs,main,foot);host.append(root);show(active)}
function fallback(){if(document.querySelector(".cd-launcher"))return;const b=el("button","cd-launcher","CD"),p=el("aside","cd-floating");p.hidden=true;b.onclick=()=>{p.hidden=!p.hidden;if(!p.hidden)render(p)};document.body.append(b,p)}
function style(n){if(!isCD(n))return;const type=n.comfyClass||n.type||"",p=type.includes("Quality")||type.includes("Report")||type.includes("Ranker")?["#0f766e","#102f2f"]:type.includes("Audit")||type.includes("Merge")?["#7c3aed","#281b3d"]:type.includes("Verify")||type.includes("Migrate")||type.includes("Retry")||type.includes("Checkpoint")||type.includes("Idempotency")||type.includes("Environment")?["#0369a1","#102a43"]:type.includes("Export")?["#c2410c","#3b2015"]:["#1d4ed8","#172554"];n.color=p[0];n.bgcolor=p[1];n.shape=window.LiteGraph?.ROUND_SHAPE??n.shape;n.size=[Math.max(n.size?.[0]||260,280),n.size?.[1]||180]}
app.registerExtension({name:NAME,aboutPageBadges:[{label:`Continuity Director v${VER}`,url:"https://github.com/xinjian0101/continuity-director",icon:"pi pi-video"}],commands:[{id:"continuity-director.add-starter",label:"Continuity Director: Add starter chain",icon:"pi pi-plus",function:starter},{id:"continuity-director.focus",label:"Continuity Director: Focus nodes",icon:"pi pi-expand",function:focus}],menuCommands:[{path:["Continuity Director"],commands:["continuity-director.add-starter","continuity-director.focus"]}],keybindings:[{combo:{key:"D",ctrl:true,shift:true},commandId:"continuity-director.add-starter"}],async setup(){css();try{if(app.extensionManager?.registerSidebarTab)app.extensionManager.registerSidebarTab({id:"continuity-director",icon:"pi pi-video",title:"Continuity Director",tooltip:"Continuity Director",type:"custom",render});else fallback()}catch(e){console.warn("[Continuity Director] sidebar fallback",e);fallback()}},nodeCreated:style,loadedGraphNode:style});