Initiliazing Runware ComfyUI Nodes Alpha 0.8
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { promptEnhanceHandler, syncDimensionsNodeHandler, searchNodeHandler, APIKeyHandler } from "./utils.js";
|
||||
import { RUNWARE_NODE_TYPES, RUNWARE_NODE_PROPS, SEARCH_TERMS } from "./types.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "runware.ai",
|
||||
async setup() {
|
||||
// pass
|
||||
},
|
||||
|
||||
async nodeCreated(node) {
|
||||
const nodeClass = node.comfyClass;
|
||||
let crNodeProps = false;
|
||||
if(typeof nodeClass === "string" && Object.values(RUNWARE_NODE_TYPES).includes(nodeClass)) {
|
||||
crNodeProps = RUNWARE_NODE_PROPS[nodeClass];
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
node.bgcolor = crNodeProps.bgColor;
|
||||
if(nodeClass === RUNWARE_NODE_TYPES.APIMANAGER) {
|
||||
APIKeyHandler(node);
|
||||
}
|
||||
|
||||
if(crNodeProps.colorModeOnly === true) return;
|
||||
const nodeWidgets = node.widgets;
|
||||
if(nodeWidgets.length <= 0) return;
|
||||
|
||||
for(const nodeWidget of nodeWidgets) {
|
||||
const widgetName = nodeWidget.name;
|
||||
const widgetType = nodeWidget.type;
|
||||
|
||||
if(crNodeProps.promptEnhancer === true && widgetType === "customtext") {
|
||||
if (widgetName === "positivePrompt" || widgetName === "negativePrompt") {
|
||||
nodeWidget.inputEl.addEventListener('keydown', promptEnhanceHandler);
|
||||
}
|
||||
} else if(crNodeProps.liveDimensions === true && widgetType === "combo" && widgetName === "dimensions") {
|
||||
syncDimensionsNodeHandler(node, nodeWidget);
|
||||
}
|
||||
|
||||
if(crNodeProps.liveSearch === true) {
|
||||
if(widgetType === "text" && SEARCH_TERMS.includes(widgetName)) {
|
||||
searchNodeHandler(node, nodeWidget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,141 @@
|
||||
const DEFAULT_BGCOLOR = "#6c5ce7";
|
||||
|
||||
const DEFAULT_DIMENSIONS_LIST = {
|
||||
"Square (512x512)": "512x512",
|
||||
"Square HD (1024x1024)": "1024x1024",
|
||||
"Portrait 3:4 (768x1024)": "768x1024",
|
||||
"Portrait 9:16 (576x1024)": "576x1024",
|
||||
"Landscape 4:3 (1024x768)": "1024x768",
|
||||
"Landscape 16:9 (1024x576)": "1024x576"
|
||||
};
|
||||
|
||||
const DEFAULT_MODELS_ARCH_LIST = {
|
||||
"All": "all",
|
||||
"FLUX.1-Schnell": "flux1s",
|
||||
"FLUX.1-Dev": "flux1d",
|
||||
"Pony": "pony",
|
||||
"SD 1.5": "sd1x",
|
||||
"SD 1.5 Hyper": "sdhyper",
|
||||
"SD 1.5 LCM": "sd1xlcm",
|
||||
"SD 3": "sd3",
|
||||
"SDXL 1.0": "sdxl",
|
||||
"SDXL 1.0 LCM": "sdxllcm",
|
||||
"SDXL Distilled": "sdxldistilled",
|
||||
"SDXL Hyper": "sdxlhyper",
|
||||
"SDXL Lightning": "sdxllightning",
|
||||
"SDXL Turbo": "sdxlturbo",
|
||||
};
|
||||
|
||||
const DEFAULT_CONTROLNET_CONDITIONING_LIST = {
|
||||
"All": "all",
|
||||
"Canny": "canny",
|
||||
"Depth": "depth",
|
||||
"MLSD": "mlsd",
|
||||
"Normal BAE": "normalbae",
|
||||
"Open Pose": "openpose",
|
||||
"Tile": "tile",
|
||||
"Seg": "seg",
|
||||
"Line Art": "lineart",
|
||||
"Line Art Anime": "lineart_anime",
|
||||
"Shuffle": "shuffle",
|
||||
"Scribble": "scribble",
|
||||
"Soft Edge": "softedge",
|
||||
};
|
||||
|
||||
const SEARCH_TERMS = ["Model Search", "Lora Search", "ControlNet Search"];
|
||||
const MODEL_TYPES_TERMS = ["ModelType", "LoraType", "ControlNetType"];
|
||||
const MODEL_LIST_TERMS = ["ModelList", "LoraList", "ControlNetList"];
|
||||
|
||||
const RUNWARE_NODE_TYPES = {
|
||||
IMAGEINFERENCE: "Runware Image Inference",
|
||||
PHOTOMAKER: "Runware PhotoMaker V2",
|
||||
MODELSEARCH: "Runware Model Search",
|
||||
LORASEARCH: "Runware Lora Search",
|
||||
CONTROLNET: "Runware ControlNet",
|
||||
BGREMOVAL: "Runware Background Removal",
|
||||
UPSCALER: "Runware Image Upscaler",
|
||||
REFINER: "Runware Refiner",
|
||||
LORACOMBINE: "Runware Lora Combine",
|
||||
CONTROLNETCOMBINE: "Runware ControlNet Combine",
|
||||
IMAGEMASKING: "Runware Image Masking",
|
||||
CONTROLNETPREPROCESSING: "Runware ControlNet PreProcessor",
|
||||
APIMANAGER: "Runware API Manager",
|
||||
IMAGECAPTION: "Runware Image Caption",
|
||||
};
|
||||
|
||||
const RUNWARE_NODE_PROPS = {
|
||||
[RUNWARE_NODE_TYPES.IMAGEINFERENCE]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
liveDimensions: true,
|
||||
promptEnhancer: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.PHOTOMAKER]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
liveDimensions: true,
|
||||
promptEnhancer: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.MODELSEARCH]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
liveSearch: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.LORASEARCH]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
liveSearch: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.CONTROLNET]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
liveSearch: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.CONTROLNETCONDITIONING]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
liveSearch: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.BGREMOVAL]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.UPSCALER]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.REFINER]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.LORACOMBINE]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.CONTROLNETCOMBINE]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.IMAGEMASKING]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.CONTROLNETPREPROCESSING]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
liveDimensions: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.APIMANAGER]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.IMAGECAPTION]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
};
|
||||
|
||||
export {
|
||||
DEFAULT_BGCOLOR,
|
||||
DEFAULT_DIMENSIONS_LIST,
|
||||
DEFAULT_MODELS_ARCH_LIST,
|
||||
DEFAULT_CONTROLNET_CONDITIONING_LIST,
|
||||
SEARCH_TERMS,
|
||||
MODEL_TYPES_TERMS,
|
||||
MODEL_LIST_TERMS,
|
||||
RUNWARE_NODE_TYPES,
|
||||
RUNWARE_NODE_PROPS
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
import { DEFAULT_DIMENSIONS_LIST, DEFAULT_MODELS_ARCH_LIST, SEARCH_TERMS,
|
||||
DEFAULT_CONTROLNET_CONDITIONING_LIST, RUNWARE_NODE_TYPES, MODEL_TYPES_TERMS, MODEL_LIST_TERMS
|
||||
} from "./types.js";
|
||||
|
||||
async function enhancePrompt(userPrompt) {
|
||||
const resp = await api.fetchApi('/promptEnhance', {
|
||||
method: "POST",
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
userPrompt: userPrompt
|
||||
})
|
||||
});
|
||||
const enhancePromptResults = await resp.json();
|
||||
return enhancePromptResults;
|
||||
}
|
||||
|
||||
async function setAPIKey(apiKey) {
|
||||
const resp = await api.fetchApi('/setAPIKey', {
|
||||
method: "POST",
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
apiKey: apiKey
|
||||
})
|
||||
});
|
||||
const setAPIKeyResults = await resp.json();
|
||||
return setAPIKeyResults;
|
||||
}
|
||||
|
||||
async function modelSearch(modelQuery = "", modelArch = "all", modelType = "base", modelCat = "checkpoint", condtioning = "") {
|
||||
const resp = await api.fetchApi('/modelSearch', {
|
||||
method: "POST",
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
modelQuery: modelQuery,
|
||||
modelArch: modelArch,
|
||||
modelType: modelType,
|
||||
modelCat: modelCat,
|
||||
condtioning: condtioning
|
||||
})
|
||||
});
|
||||
const modelSearchResults = await resp.json();
|
||||
return modelSearchResults;
|
||||
}
|
||||
|
||||
function notifyUser(message, type="info", title = "Runware", life = 4.5) {
|
||||
app.extensionManager.toast.add({
|
||||
severity: type, // 'info', 'success', 'warn', 'error' \\
|
||||
summary: title,
|
||||
detail: message,
|
||||
life: Math.floor(life * 1000)
|
||||
});
|
||||
}
|
||||
|
||||
async function promptEnhanceHandler(e) {
|
||||
if(e.altKey && e.shiftKey && e.key === 'E') {
|
||||
const userPrompt = e.target.value.trim();
|
||||
if(userPrompt.length <= 1) {
|
||||
notifyUser("Prompt Is Too Short!", "error", "Runware Prompt Enhancer");
|
||||
return;
|
||||
} else if(userPrompt.length > 300) {
|
||||
notifyUser("Prompt Must Not Exceed 300 Characters!", "error", "Runware Prompt Enhancer");
|
||||
return;
|
||||
}
|
||||
const enhanceResults = await enhancePrompt(userPrompt);
|
||||
if(enhanceResults.success) {
|
||||
e.target.value = enhanceResults.enhancedPrompt;
|
||||
} else {
|
||||
notifyUser(enhanceResults.error, "error", "Runware Prompt Enhancer");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function remNode(node) {
|
||||
app.graph.remove(node);
|
||||
app.graph.setDirtyCanvas(true,true);
|
||||
}
|
||||
|
||||
async function APIKeyHandler(apiManagerNode) {
|
||||
const widgets = apiManagerNode.widgets;
|
||||
for(const widget of widgets) {
|
||||
if(widget.name === "API Key") {
|
||||
appendWidgetCB(widget, async function(...args) {
|
||||
const apiKey = args[0].trim();
|
||||
if(apiKey.length < 30) {
|
||||
notifyUser("Invalid API Key Set, Please Try Again!", "error", "Runware API Manager");
|
||||
return;
|
||||
} else {
|
||||
const setAPIKeyResults = await setAPIKey(apiKey);
|
||||
if(setAPIKeyResults.success) {
|
||||
remNode(apiManagerNode);
|
||||
notifyUser("API Key Set Successfully!", "success", "Runware API Manager");
|
||||
} else {
|
||||
notifyUser(setAPIKeyResults.error, "error", "Runware API Manager");
|
||||
}
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function appendWidgetCB(node, newCB) {
|
||||
const oldNodeCB = node.callback;
|
||||
if(typeof oldNodeCB === "function") {
|
||||
node.callback = function(...args) {
|
||||
newCB?.apply(this, args);
|
||||
oldNodeCB?.apply(this, args);
|
||||
}
|
||||
} else {
|
||||
node.callback = newCB;
|
||||
}
|
||||
}
|
||||
|
||||
async function syncDimensionsNodeHandler(node, dimensionsWidget) {
|
||||
const nodeWidgets = node.widgets;
|
||||
let widthWidget = false, heightWidget = false;
|
||||
for(const nodeWidget of nodeWidgets) {
|
||||
const widgetName = nodeWidget.name;
|
||||
const widgetType = nodeWidget.type;
|
||||
if(widgetName === "width" && widgetType === "number") widthWidget = nodeWidget;
|
||||
if(widgetName === "height" && widgetType === "number") heightWidget = nodeWidget;
|
||||
}
|
||||
|
||||
appendWidgetCB(dimensionsWidget, function(...args) {
|
||||
const chosenDimension = args[0];
|
||||
if(chosenDimension === "Custom") return;
|
||||
const dimensionValue = DEFAULT_DIMENSIONS_LIST[chosenDimension];
|
||||
const [width, height] = dimensionValue.split("x");
|
||||
widthWidget.callback(width, "customSetOperation");
|
||||
heightWidget.callback(height, "customSetOperation");
|
||||
});
|
||||
|
||||
if(widthWidget !== false) {
|
||||
appendWidgetCB(widthWidget, function(...args) {
|
||||
if(args[1] === "customSetOperation") return;
|
||||
dimensionsWidget.value = "Custom";
|
||||
});
|
||||
}
|
||||
|
||||
if(heightWidget !== false) {
|
||||
appendWidgetCB(heightWidget, function(...args) {
|
||||
if(args[1] === "customSetOperation") return;
|
||||
dimensionsWidget.value = "Custom";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function searchNodeHandler(searchNode, searchInputWidget) {
|
||||
const searchNodeWidgets = searchNode.widgets;
|
||||
let modelArchWidget = false, modelTypeWidget = false, modelListWidget = false, defaultWidgetValues = {};
|
||||
let isLora = false, isControlNet = false, modelTypeValue;
|
||||
if(searchNode.comfyClass === RUNWARE_NODE_TYPES.LORASEARCH) isLora = true;
|
||||
if(searchNode.comfyClass === RUNWARE_NODE_TYPES.CONTROLNET) isControlNet = true;
|
||||
for(const searchWidget of searchNodeWidgets) {
|
||||
const widgetName = searchWidget.name;
|
||||
const widgetType = searchWidget.type;
|
||||
if(widgetName === "Model Architecture" && widgetType === "combo") {
|
||||
modelArchWidget = searchWidget;
|
||||
defaultWidgetValues["modelArch"] = searchWidget.options.values;
|
||||
}
|
||||
if(MODEL_TYPES_TERMS.includes(widgetName) && widgetType === "combo") {
|
||||
modelTypeWidget = searchWidget;
|
||||
defaultWidgetValues["modelType"] = searchWidget.options.values;
|
||||
}
|
||||
if(MODEL_LIST_TERMS.includes(widgetName) && widgetType === "combo") {
|
||||
modelListWidget = searchWidget;
|
||||
defaultWidgetValues["modelList"] = searchWidget.options.values;
|
||||
}
|
||||
}
|
||||
|
||||
function resetValues() {
|
||||
if(modelArchWidget) {
|
||||
modelArchWidget.options.values = defaultWidgetValues["modelArch"];
|
||||
modelArchWidget.value = defaultWidgetValues["modelArch"][0];
|
||||
}
|
||||
if(modelTypeWidget) {
|
||||
modelTypeWidget.options.values = defaultWidgetValues["modelType"];
|
||||
modelTypeWidget.value = defaultWidgetValues["modelType"][0];
|
||||
}
|
||||
if(modelListWidget) {
|
||||
modelListWidget.options.values = defaultWidgetValues["modelList"];
|
||||
modelListWidget.value = defaultWidgetValues["modelList"][0];
|
||||
}
|
||||
}
|
||||
|
||||
async function searchModels() {
|
||||
const searchQuery = searchInputWidget.value.trim();
|
||||
const modelArchValue = DEFAULT_MODELS_ARCH_LIST[modelArchWidget.value];
|
||||
if(isControlNet) {
|
||||
modelTypeValue = DEFAULT_CONTROLNET_CONDITIONING_LIST[modelTypeWidget.value];
|
||||
} else {
|
||||
modelTypeValue = modelTypeWidget.value.toLowerCase().replace("model", "").trim();
|
||||
}
|
||||
|
||||
let modelSearchResults = null;
|
||||
if(isControlNet) {
|
||||
modelSearchResults = await modelSearch(searchQuery, modelArchValue, "", "controlnet", modelTypeValue);
|
||||
} else if(isLora) {
|
||||
modelSearchResults = await modelSearch(searchQuery, modelArchValue, "", modelTypeValue);
|
||||
} else {
|
||||
modelSearchResults = await modelSearch(searchQuery, modelArchValue, modelTypeValue);
|
||||
}
|
||||
|
||||
if(modelSearchResults.success) {
|
||||
const modelListArray = [];
|
||||
modelSearchResults.modelList.forEach(modelObj => {
|
||||
modelListArray.push(`${modelObj.air} (${modelObj.name} ${modelObj.version})`);
|
||||
});
|
||||
modelListWidget.options.values = modelListArray;
|
||||
modelListWidget.value = modelListArray[0];
|
||||
} else {
|
||||
notifyUser(modelSearchResults.error, "error", "Runware Search");
|
||||
}
|
||||
}
|
||||
|
||||
appendWidgetCB(searchInputWidget, async function(...args) {
|
||||
const searchQuery = args[0].trim();
|
||||
if(searchQuery.length === 0) {
|
||||
resetValues();
|
||||
} else if(searchQuery.length < 2 || searchQuery.length > 32) {
|
||||
notifyUser("Invalid Search Value, Search Query must be between 2 and 32 characters!", "error", "Runware Search");
|
||||
return;
|
||||
} else {
|
||||
await searchModels();
|
||||
}
|
||||
});
|
||||
|
||||
appendWidgetCB(modelArchWidget, async function(...args) {
|
||||
await searchModels();
|
||||
});
|
||||
|
||||
if(!isLora){
|
||||
appendWidgetCB(modelTypeWidget, async function(...args) {
|
||||
await searchModels();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
enhancePrompt,
|
||||
modelSearch,
|
||||
notifyUser,
|
||||
promptEnhanceHandler,
|
||||
syncDimensionsNodeHandler,
|
||||
searchNodeHandler,
|
||||
APIKeyHandler,
|
||||
};
|
||||
Reference in New Issue
Block a user