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.
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import vm from "node:vm";
|
||||
|
||||
|
||||
const sourcePath = new URL("../web/js/videoTrim.js", import.meta.url);
|
||||
const source = fs.readFileSync(sourcePath, "utf8").replace(/^import .*;\s*$/gm, "");
|
||||
|
||||
class Element {
|
||||
constructor(tagName) {
|
||||
this.tagName = tagName.toUpperCase();
|
||||
this.children = [];
|
||||
this.listeners = new Map();
|
||||
this.style = {};
|
||||
this.textContent = "";
|
||||
this.className = "";
|
||||
this.classList = { add() {}, remove() {} };
|
||||
this.paused = true;
|
||||
this.currentTime = 0;
|
||||
}
|
||||
append(...children) { this.children.push(...children); }
|
||||
appendChild(child) { this.children.push(child); }
|
||||
replaceChildren(...children) { this.children = [...children]; }
|
||||
addEventListener(type, listener) { this.listeners.set(type, listener); }
|
||||
removeAttribute(name) { delete this[name]; }
|
||||
querySelector(selector) { return selector === "span" ? this._span ?? null : null; }
|
||||
pause() { this.paused = true; }
|
||||
load() {}
|
||||
remove() { this.removed = true; }
|
||||
set innerHTML(value) {
|
||||
this._innerHTML = value;
|
||||
if (this.tagName === "BUTTON" && value.includes("<span>")) {
|
||||
this._span = new Element("span");
|
||||
}
|
||||
}
|
||||
get innerHTML() { return this._innerHTML ?? ""; }
|
||||
}
|
||||
|
||||
const createdElements = [];
|
||||
const elementsById = new Map();
|
||||
const document = {
|
||||
head: {
|
||||
appendChild(element) {
|
||||
if (element.id) elementsById.set(element.id, element);
|
||||
},
|
||||
},
|
||||
body: {
|
||||
appendChild(element) { createdElements.push(element); },
|
||||
},
|
||||
createElement(tagName) {
|
||||
const element = new Element(tagName);
|
||||
createdElements.push(element);
|
||||
return element;
|
||||
},
|
||||
getElementById(id) { return elementsById.get(id) ?? null; },
|
||||
};
|
||||
|
||||
let registeredExtension;
|
||||
const app = {
|
||||
registerExtension(extension) { registeredExtension = extension; },
|
||||
};
|
||||
const api = {
|
||||
apiURL(value) { return value; },
|
||||
async fetchApi(path) {
|
||||
assert.equal(path, "/o1key/input_dir");
|
||||
return {
|
||||
ok: true,
|
||||
async json() { return { path: "F:/o1key_windows_portable/ComfyUI/input" }; },
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const context = vm.createContext({
|
||||
app,
|
||||
api,
|
||||
console,
|
||||
document,
|
||||
FormData,
|
||||
URLSearchParams,
|
||||
isFinite,
|
||||
requestAnimationFrame: (callback) => callback(),
|
||||
setTimeout,
|
||||
});
|
||||
vm.runInContext(source, context, { filename: sourcePath.pathname });
|
||||
|
||||
const callbackReceiver = { kind: "comfy-widget-component" };
|
||||
let callbackArguments;
|
||||
function originalFixedCallback(...args) {
|
||||
assert.equal(this, callbackReceiver);
|
||||
callbackArguments = args;
|
||||
return "original-result";
|
||||
}
|
||||
|
||||
class VideoTrimNode {
|
||||
constructor() {
|
||||
this.size = [300, 300];
|
||||
this.widgets = [
|
||||
{ name: "视频路径", value: "" },
|
||||
{ name: "开始时间", value: 0 },
|
||||
{ name: "结束时间", value: 0 },
|
||||
{ name: "固定时长", value: 0, callback: originalFixedCallback },
|
||||
];
|
||||
}
|
||||
addDOMWidget(name, type, element, options) {
|
||||
const widget = { name, type, element, options };
|
||||
this.widgets.push(widget);
|
||||
return widget;
|
||||
}
|
||||
computeSize() { return [this.size[0], 480]; }
|
||||
setSize(size) { this.size = size; }
|
||||
setDirtyCanvas() {}
|
||||
}
|
||||
|
||||
await registeredExtension.beforeRegisterNodeDef(VideoTrimNode, { name: "O1keyVideoTrim" });
|
||||
const node = new VideoTrimNode();
|
||||
node.onNodeCreated();
|
||||
|
||||
const pathWidget = node.widgets.find((widget) => widget.name === "视频路径");
|
||||
const fixedWidget = node.widgets.find((widget) => widget.name === "固定时长");
|
||||
assert.equal(pathWidget.hidden, true, "视频路径必须作为内部字段隐藏");
|
||||
assert.equal(pathWidget.options.hidden, true, "Nodes 2.0 必须收到正式隐藏标记");
|
||||
assert.equal(pathWidget.computeSize, undefined, "不得用负高度隐藏路径控件");
|
||||
|
||||
fixedWidget.value = 4;
|
||||
const callbackResult = fixedWidget.callback.call(
|
||||
callbackReceiver,
|
||||
4,
|
||||
"canvas",
|
||||
node,
|
||||
[10, 20],
|
||||
{ type: "change" },
|
||||
);
|
||||
assert.equal(callbackResult, "original-result");
|
||||
assert.deepEqual(callbackArguments, [4, "canvas", node, [10, 20], { type: "change" }]);
|
||||
|
||||
pathWidget.value = "F:/o1key_windows_portable/ComfyUI/input/clips/demo.mp4";
|
||||
await node._o1vtRefreshPath(pathWidget.value);
|
||||
const preview = createdElements.find((element) => element.tagName === "VIDEO");
|
||||
assert.equal(preview.src, "/view?filename=demo.mp4&type=input&subfolder=clips");
|
||||
assert.equal(preview.style.display, "block");
|
||||
|
||||
pathWidget.value = "D:/external/demo.mp4";
|
||||
await node._o1vtRefreshPath(pathWidget.value);
|
||||
assert.equal(preview.src, undefined, "input 目录外路径不能暴露为 /view 地址");
|
||||
assert.equal(preview.style.display, "none");
|
||||
|
||||
node.onRemoved();
|
||||
const fileInput = createdElements.find((element) => element.tagName === "INPUT");
|
||||
assert.equal(fileInput.removed, true);
|
||||
|
||||
console.log("video trim frontend tests passed");
|
||||
Reference in New Issue
Block a user