Files
comfyui_o1key/tests/test_o1key_video_generator_frontend.mjs
T
Jony ba920f2b66 Publish current ComfyUI O1Key code baseline
Replace the prior release tree with the current plugin, frontend, tests, and documentation. Document retired node IDs and the public Gitea update source.
2026-09-24 19:56:48 +08:00

143 lines
9.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const here = path.dirname(fileURLToPath(import.meta.url));
const source = fs.readFileSync(path.join(here, "..", "web", "js", "o1keyVideoGenerator.js"), "utf8");
assert.match(source, /const GENERATOR = "O1keyVideoGenerator"/);
assert.match(source, /const RESULT = "O1keyVideoResult"/);
assert.match(source, /const SAVE_VIDEO = "SaveVideo"/);
assert.match(source, /const SAVE_IMAGE = "SaveImage"/);
assert.match(source, /import \{ openReferenceImageEditor \} from "\.\/o1keyReferenceImageEditor\.js"/);
assert.match(source, /import "\.\/o1keyImageGenerator\.js"/);
assert.doesNotMatch(source, /LiteGraph\.createNode\(RESULT\)/);
assert.match(source, /LiteGraph\.createNode\(SAVE_VIDEO\)/);
assert.match(source, /request\.return_last_frame \? LiteGraph\.createNode\(SAVE_IMAGE\) : null/);
assert.match(source, /source\.connect\(0, saveVideo, 0\)/);
assert.match(source, /source\.connect\(1, saveImage, 0\)/);
assert.match(source, /function createNativeOutputNodes\(source, request, batchId\)/);
assert.match(source, /function dispatchNativePreview\(node, descriptor, animated = false\)/);
assert.match(source, /api\.dispatchCustomEvent\("executed"/);
assert.match(source, /output\.animated = \[true\]/);
assert.match(source, /async function submitNativeJob\(source, targets, request, batchId\)/);
assert.match(source, /result_node_id: Number\(targets\.saveVideo\.id\)/);
assert.match(source, /createNativeOutputNodes\(node, request, batchId\)/);
assert.match(source, /function recoverNativeSaveVideo\(node\)/);
assert.match(source, /recoverNativeSaveVideo\(node\)/);
assert.match(source, /String\(result\.properties\?\.o1keyVideoBatchId \|\| ""\) === batchId/);
assert.match(source, /requestJson\("\/o1key\/video\/jobs"/);
assert.match(source, /api\.addEventListener\("o1key\.video_job"/);
assert.match(source, /run\.disabled = false/);
assert.doesNotMatch(source, /独立任务 · 可连续提交 · 提交即运行|o1vg-summary/);
assert.doesNotMatch(source, /最多并行|等待后台槽位/);
assert.match(source, /value: "overseas_hc", label: "海外"/);
assert.doesNotMatch(source, /label: "海外 HC"/);
assert.match(source, /const ASSET_CREATION_OPTIONS/);
assert.match(source, /value: "auto", label: "自动创建"/);
assert.match(source, /value: "manual", label: "手动"/);
assert.match(source, /makeField\("素材创建", assetCreation\)/);
assert.match(source, /assetCreationField\.style\.display = isSeedance \? "grid" : "none"/);
assert.match(source, /assetCreation\._trigger\.disabled = !assetModeAvailable/);
assert.match(source, /imageIdField\.style\.display = manualAssets \? "grid" : "none"/);
assert.match(source, /videoIdField\.style\.display = manualAssets && activeMode === "multimodal"/);
assert.match(source, /asset_creation_mode: manualAssets \? "manual" : "auto"/);
assert.match(source, /assets: manualAssets \? manualRequestAssets : \{\}/);
assert.match(source, /function requestForRetry\(node\)/);
assert.match(source, /request\.asset_creation_mode !== "auto"/);
assert.match(source, /return \{ \.\.\.request, asset_creation_mode: "manual", media: \{\}, assets \}/);
assert.match(source, /resolved_assets: detail\.resolved_assets \|\| null/);
assert.match(source, /const STAGE_LABELS = \{ starting: "正在启动"/);
assert.match(source, /STAGE_LABELS\[detail\.stage\] \|\| detail\.stage/);
assert.match(source, /const SEEDANCE_REFERENCE_LIMITS = Object\.freeze/);
assert.match(source, /minDimension: 300/);
assert.match(source, /maxDimension: 6000/);
assert.match(source, /minVideoPixels: 407696/);
assert.match(source, /maxVideoPixels: 8295044/);
assert.match(source, /async function validateReferenceImageFile\(file\)/);
assert.match(source, /async function validateReferenceVideoFile\(file\)/);
assert.match(source, /if \(kind === "image"\) await validateReferenceImageFile\(file\); else if \(kind === "video"\) await validateReferenceVideoFile\(file\)/);
assert.match(source, /宽高 3006000px · 比例 0\.42\.5/);
assert.match(source, /title: "参考视频", shortTitle: "视频", hint: "40\.77万~829\.50万像素"/);
assert.doesNotMatch(source, /参考图至少需要 480p|≥ 41万像素/);
assert.match(source, /window\.o1keyCanvasImagePicker/);
assert.match(source, /canvasPick\.append\(el\("b", "", "▣"\), el\("span", "", "画布取图"\)\)/);
assert.match(source, /actions\.append\(canvasPick\)/);
assert.match(source, /const file = await picker\.readFile\(descriptor\)/);
assert.match(source, /media\[key\] = multiple \? \[\.\.\.\(media\[key\] \|\| \[\]\), uploaded\] : uploaded/);
assert.doesNotMatch(source, /真人素材 ID/);
// Keep the video node on the same visual component language as the image node.
assert.match(source, /\.o1vg-field/);
assert.match(source, /\.o1vg-select-trigger/);
assert.match(source, /\.o1vg-thumb/);
assert.match(source, /\.o1vg-generate/);
assert.match(source, /\.o1vg-prompt-write/);
assert.match(source, /\.o1vg-prompt-wrap\{[^\n]*height:auto;min-height:142px;flex:1 1 142px/);
assert.match(source, /\.o1vg-prompt\{[\s\S]*?min-height:142px;max-height:none;resize:none/);
assert.match(source, /promptWrite\.title = "AI帮写"/);
assert.match(source, /promptWrite\.append\(promptWriteIcon, el\("span", "", "AI帮写"\)\)/);
assert.match(source, /requestJson\("\/o1key\/video\/prompt-write"/);
assert.match(source, /setPromptWriteStatus\(node, "AI帮写中…", "busy"\)/);
assert.match(source, /role: "首帧"/);
assert.match(source, /role: index === 0 \? "首帧" : "尾帧"/);
assert.match(source, /reference_video_count:/);
assert.match(source, /reference_audio_count:/);
assert.match(source, /function makeVideoPreview\(item, title\)/);
assert.match(source, /video\.preload = "metadata"/);
assert.match(source, /video\.currentTime = previewTime/);
assert.match(source, /video\.src = descriptorUrl\(item\)/);
assert.match(source, /releaseVideoPreviews\(track\)/);
assert.match(source, /video\.removeAttribute\("src"\)/);
assert.match(source, /if \(kind === "video"\) tile\.append\(makeVideoPreview\(item, title\)\)/);
assert.doesNotMatch(source, /drawImage\([^)]*video|canvas\.toDataURL/);
assert.match(source, /function makeSelect/);
assert.match(source, /function mediaDropDestination/);
assert.match(source, /function moveMediaItem/);
assert.match(source, /function editMediaImage/);
assert.match(source, /openReferenceImageEditor\(\{/);
assert.match(source, /addEventListener\("dragstart"/);
assert.match(source, /addEventListener\("dragover"/);
assert.match(source, /addEventListener\("drop"/);
assert.match(source, /\.o1vg-thumb-edit/);
assert.match(source, /\.o1vg-thumb-index/);
assert.match(source, /function makeMultimodalMediaSection\(node\)/);
assert.match(source, /meta\.append\(el\("strong", "", "参考素材"\), hintElement\)/);
assert.match(source, /upload\.append\(el\("b", "", ""\), el\("span", "", "上传"\)\)/);
assert.match(source, /input\.accept = sources\.map\(\(source\) => source\.accept\)\.join\(","\)/);
assert.match(source, /const sourceForFile = \(file\) =>/);
assert.match(source, /addFiles\(files\)/);
const multimodalSource = source.slice(source.indexOf("function makeMultimodalMediaSection"), source.indexOf("function nativeSavePrefix"));
assert.equal((multimodalSource.match(/actions\.append\(/g) || []).length, 1);
assert.doesNotMatch(multimodalSource, /canvasPick/);
assert.doesNotMatch(multimodalSource, /o1vg-empty/);
assert.doesNotMatch(multimodalSource, /添加\$\{source\.shortTitle\}/);
assert.doesNotMatch(multimodalSource, /sourceInput|source\.input/);
assert.match(source, /\.o1vg-multimodal-assets \.o1vg-add\{min-width:58px;height:29px;gap:4px;padding:0 10px;[^}]*font-size:11px;font-weight:600/);
assert.match(source, /mediaHost\.append\(firstSection, lastSection, multimodalSection\)/);
assert.match(source, /multimodalSection\.style\.display = !manualAssets && activeMode === "multimodal" \? "flex" : "none"/);
assert.match(source, /automaticHeights = \{ text: 679, first_frame: 824, first_last_frame: 964, multimodal: 824 \}/);
assert.match(source, /widget\(node, "generation_mode"\)\?\.value \|\| "multimodal"/);
assert.match(source, /const restore = \(\) =>/);
assert.match(source, /mode\.value = value\("generation_mode", "multimodal"\)/);
assert.match(source, /firstSection\._render\?\.\(\)/);
assert.match(source, /multimodalSection\._render\?\.\(\)/);
assert.match(source, /node\._o1vgRestore = restore/);
assert.match(source, /nodeType\.prototype\.onConfigure = function \(\)/);
assert.match(source, /this\._o1vgRestore\?\.\(\)/);
assert.match(source, /loadedGraphNode\(node\).*node\._o1vgRestore\?\.\(\)/s);
assert.doesNotMatch(source, /const imagesSection = makeMediaSection/);
assert.doesNotMatch(source, /const videosSection = makeMediaSection/);
assert.doesNotMatch(source, /const audiosSection = makeMediaSection/);
assert.match(source, /function applyGeneratorDefaultSize/);
assert.match(source, /node\.setSize\?\.\(\[560, 679\]\)/);
assert.match(source, /target\.hidden = true/);
assert.match(source, /target\.options\.hidden = true/);
assert.doesNotMatch(source, /document\.createElement\("select"\)/);
// A click creates an independent O1Key job; it must never enter ComfyUI's native queue.
assert.doesNotMatch(source, /queuePrompt|app\.queuePrompt|fetchApi\("\/prompt"/);
console.log("o1key video generator frontend checks passed");