Add object_info schema (#67)

This commit is contained in:
Chenlei Hu
2024-06-30 10:14:16 -04:00
committed by GitHub
parent 5746b130bb
commit ee6788a35e
7 changed files with 128 additions and 37 deletions

View File

@@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "npm run typecheck && vite build && npm run zipdist",
"build": "npm run typecheck && vite build",
"zipdist": "node scripts/zipdist.js",
"typecheck": "tsc --noEmit",
"test": "npm run build && jest",

View File

@@ -2,6 +2,7 @@ import { app } from "../../scripts/app";
import { api } from "../../scripts/api";
import type { IWidget } from "/types/litegraph";
import type { DOMWidget } from "/scripts/domWidget";
import { ComfyNodeDef } from "/types/apiTypes";
type FolderType = "input" | "output" | "temp";
@@ -120,7 +121,7 @@ app.registerExtension({
app.registerExtension({
name: "Comfy.UploadAudio",
async beforeRegisterNodeDef(nodeType, nodeData) {
async beforeRegisterNodeDef(nodeType, nodeData: ComfyNodeDef) {
if (nodeData?.input?.required?.audio?.[1]?.audio_upload === true) {
nodeData.input.required.upload = ["AUDIOUPLOAD"];
}

View File

@@ -1,10 +1,11 @@
import { app } from "../../scripts/app";
import { ComfyNodeDef } from "/types/apiTypes";
// Adds an upload button to the nodes
app.registerExtension({
name: "Comfy.UploadImage",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
async beforeRegisterNodeDef(nodeType, nodeData: ComfyNodeDef, app) {
if (nodeData?.input?.required?.image?.[1]?.image_upload === true) {
nodeData.input.required.upload = ["IMAGEUPLOAD"];
}

View File

@@ -1,4 +1,4 @@
import { HistoryTaskItem, PendingTaskItem, RunningTaskItem } from "/types/apiTypes";
import { HistoryTaskItem, PendingTaskItem, RunningTaskItem, ComfyNodeDef } from "/types/apiTypes";
interface QueuePromptRequestBody {
@@ -215,7 +215,7 @@ class ComfyApi extends EventTarget {
* Loads node object definitions for the graph
* @returns The node definitions
*/
async getNodeDefs() {
async getNodeDefs(): Promise<Record<string, ComfyNodeDef>> {
const resp = await this.fetchApi("/object_info", { cache: "no-store" });
return await resp.json();
}

View File

@@ -11,6 +11,7 @@ import { applyTextReplacements, addStylesheet } from "./utils";
import type { ComfyExtension } from "/types/comfy";
import type { LGraph, LGraphCanvas, LGraphNode } from "/types/litegraph";
import { type ComfyWorkflow, parseComfyWorkflow } from "../types/comfyWorkflow";
import { ComfyNodeDef } from "/types/apiTypes";
export const ANIM_PREVIEW_WIDGET = "$$comfy_animation_preview"
@@ -1728,7 +1729,7 @@ export class ComfyApp {
}
}
async registerNodeDef(nodeId, nodeData) {
async registerNodeDef(nodeId: string, nodeData: ComfyNodeDef) {
const self = this;
const node = Object.assign(
function ComfyNode() {
@@ -1805,7 +1806,7 @@ export class ComfyApp {
node.category = nodeData.category;
}
async registerNodesFromDefs(defs) {
async registerNodesFromDefs(defs: Record<string, ComfyNodeDef>) {
await this.#invokeExtensionsAsync("addCustomNodeDefs", defs);
// Generate list of known widgets

View File

@@ -2,27 +2,10 @@ import { api } from "./api"
import "./domWidget";
import type { ComfyApp } from "./app";
import type { IWidget, LGraphNode } from "/types/litegraph";
interface InputDataOptions {
display?: string;
default?: any;
label_on?: boolean;
label_off?: boolean;
multiline?: boolean;
// TODO: infer type
dynamicPrompts?: any;
// TODO: infer type
control_after_generate?: any;
// Name of widget.
widget?: string
}
export type InputData = [
string, InputDataOptions
];
import { ComfyNodeDef } from "/types/apiTypes";
export type ComfyWidgetConstructor = (
node: LGraphNode, inputName: string, inputData: InputData, app?: ComfyApp, widgetName?: string) =>
node: LGraphNode, inputName: string, inputData: ComfyNodeDef, app?: ComfyApp, widgetName?: string) =>
{widget: IWidget, minWidth?: number; minHeight?: number };
@@ -39,7 +22,7 @@ export function updateControlWidgetLabel(widget) {
const IS_CONTROL_WIDGET = Symbol();
const HAS_EXECUTED = Symbol();
function getNumberDefaults(inputData, defaultStep, precision, enable_rounding) {
function getNumberDefaults(inputData: ComfyNodeDef, defaultStep, precision, enable_rounding) {
let defaultVal = inputData[1]["default"];
let { min, max, step, round} = inputData[1];
@@ -61,7 +44,7 @@ function getNumberDefaults(inputData, defaultStep, precision, enable_rounding) {
return { val: defaultVal, config: { min, max, step: 10.0 * step, round, precision } };
}
export function addValueControlWidget(node, targetWidget, defaultValue = "randomize", values, widgetName, inputData) {
export function addValueControlWidget(node, targetWidget, defaultValue = "randomize", values, widgetName, inputData: ComfyNodeDef) {
let name = inputData[1]?.control_after_generate;
if(typeof name !== "string") {
name = widgetName;
@@ -73,7 +56,7 @@ export function addValueControlWidget(node, targetWidget, defaultValue = "random
return widgets[0];
}
export function addValueControlWidgets(node, targetWidget, defaultValue = "randomize", options, inputData) {
export function addValueControlWidgets(node, targetWidget, defaultValue = "randomize", options, inputData: ComfyNodeDef) {
if (!defaultValue) defaultValue = "randomize";
if (!options) options = {};
@@ -232,7 +215,7 @@ export function addValueControlWidgets(node, targetWidget, defaultValue = "rando
return widgets;
};
function seedWidget(node, inputName, inputData, app, widgetName) {
function seedWidget(node, inputName, inputData: ComfyNodeDef, app, widgetName) {
const seed = createIntWidget(node, inputName, inputData, app, true);
const seedControl = addValueControlWidget(node, seed.widget, "randomize", undefined, widgetName, inputData);
@@ -240,7 +223,7 @@ function seedWidget(node, inputName, inputData, app, widgetName) {
return seed;
}
function createIntWidget(node, inputName, inputData, app, isSeedInput: boolean = false) {
function createIntWidget(node, inputName, inputData: ComfyNodeDef, app, isSeedInput: boolean = false) {
const control = inputData[1]?.control_after_generate;
if (!isSeedInput && control) {
return seedWidget(node, inputName, inputData, app, typeof control === "string" ? control : undefined);
@@ -329,7 +312,7 @@ export function initWidgets(app) {
export const ComfyWidgets: Record<string, ComfyWidgetConstructor> = {
"INT:seed": seedWidget,
"INT:noise_seed": seedWidget,
FLOAT(node, inputName, inputData, app) {
FLOAT(node, inputName, inputData: ComfyNodeDef, app) {
let widgetType: "number" | "slider" = isSlider(inputData[1]["display"], app);
let precision = app.ui.settings.getSettingValue("Comfy.FloatRoundingPrecision");
let disable_rounding = app.ui.settings.getSettingValue("Comfy.DisableFloatRounding")
@@ -346,7 +329,7 @@ export const ComfyWidgets: Record<string, ComfyWidgetConstructor> = {
}
}, config) };
},
INT(node, inputName, inputData, app) {
INT(node, inputName, inputData: ComfyNodeDef, app) {
return createIntWidget(node, inputName, inputData, app);
},
BOOLEAN(node, inputName, inputData) {
@@ -370,7 +353,7 @@ export const ComfyWidgets: Record<string, ComfyWidgetConstructor> = {
)
};
},
STRING(node, inputName, inputData, app) {
STRING(node, inputName, inputData: ComfyNodeDef, app) {
const defaultVal = inputData[1].default || "";
const multiline = !!inputData[1].multiline;
@@ -386,7 +369,7 @@ export const ComfyWidgets: Record<string, ComfyWidgetConstructor> = {
return res;
},
COMBO(node, inputName, inputData) {
COMBO(node, inputName, inputData: ComfyNodeDef) {
const type = inputData[0];
let defaultValue = type[0];
if (inputData[1] && inputData[1].default) {
@@ -400,7 +383,7 @@ export const ComfyWidgets: Record<string, ComfyWidgetConstructor> = {
}
return res;
},
IMAGEUPLOAD(node: LGraphNode, inputName: string, inputData, app) {
IMAGEUPLOAD(node: LGraphNode, inputName: string, inputData: ComfyNodeDef, app) {
// TODO make image upload handle a custom node type?
// @ts-ignore
const imageWidget = node.widgets.find((w) => w.name === (inputData[1]?.widget ?? "image"));

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { ZodType, z } from "zod";
import { zComfyWorkflow } from "./comfyWorkflow";
const zNodeId = z.number();
@@ -109,9 +109,114 @@ const zHistoryTaskItem = z.object({
const zTaskItem = z.union([zRunningTaskItem, zPendingTaskItem, zHistoryTaskItem]);
// `/queue`
export type RunningTaskItem = z.infer<typeof zRunningTaskItem>;
export type PendingTaskItem = z.infer<typeof zPendingTaskItem>;
// `/history`
export type HistoryTaskItem = z.infer<typeof zHistoryTaskItem>;
export type TaskItem = z.infer<typeof zTaskItem>;
// TODO: validate `/history` `/queue` API endpoint responses.
function inputSpec(spec: [ZodType, ZodType]): ZodType {
const [inputType, inputSpec] = spec;
return z.union([
z.tuple([inputType, inputSpec]),
z.tuple([inputType]),
]);
}
const zIntInputSpec = inputSpec([
z.literal("INT"),
z.object({
min: z.number().optional(),
max: z.number().optional(),
step: z.number().optional(),
default: z.number().optional(),
forceInput: z.boolean().optional(),
}),
]);
const zFloatInputSpec = inputSpec([
z.literal("FLOAT"),
z.object({
min: z.number().optional(),
max: z.number().optional(),
step: z.number().optional(),
round: z.number().optional(),
default: z.number().optional(),
forceInput: z.boolean().optional(),
}),
]);
const zBooleanInputSpec = inputSpec([
z.literal("BOOLEAN"),
z.object({
label_on: z.string().optional(),
label_off: z.string().optional(),
default: z.boolean().optional(),
forceInput: z.boolean().optional(),
})
]);
const zStringInputSpec = inputSpec([
z.literal("STRING"),
z.object({
default: z.string().optional(),
multiline: z.boolean().optional(),
dynamicPrompts: z.boolean().optional(),
forceInput: z.boolean().optional(),
}),
]);
// Dropdown Selection.
const zComboInputSpec = inputSpec([
z.array(z.any()),
z.object({
default: z.any().optional(),
control_after_generate: z.boolean().optional(),
image_upload: z.boolean().optional(),
forceInput: z.boolean().optional(),
}),
]);
const zCustomInputSpec = inputSpec([
z.string(),
z.object({
default: z.any().optional(),
forceInput: z.boolean().optional(),
}),
]);
const zInputSpec = z.union([
zIntInputSpec,
zFloatInputSpec,
zBooleanInputSpec,
zStringInputSpec,
zComboInputSpec,
zCustomInputSpec,
]);
const zComfyNodeDataType = z.string();
const zComfyComboOutput = z.array(z.any());
const zComfyOutputSpec = z.array(z.union([zComfyNodeDataType, zComfyComboOutput]));
const zComfyNodeDef = z.object({
input: z.object({
required: z.record(zInputSpec).optional(),
optional: z.record(zInputSpec).optional(),
}),
output: zComfyOutputSpec,
output_is_list: z.array(z.boolean()),
output_name: z.array(z.string()),
name: z.string(),
display_name: z.string(),
description: z.string(),
category: z.string(),
output_node: z.boolean(),
});
// `/object_info`
export type ComfyNodeDef = z.infer<typeof zComfyNodeDef>;
// TODO: validate `/object_info` API endpoint responses.