Compare commits
13 Commits
builtin-te
...
fix-graph-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdcf566a43 | ||
|
|
b3584dd2ad | ||
|
|
60bd989915 | ||
|
|
eb335e9be7 | ||
|
|
73e007844a | ||
|
|
e729bc6c46 | ||
|
|
d11f66c5ac | ||
|
|
9f5b14a2bf | ||
|
|
74bad3bd1e | ||
|
|
8e27a2611c | ||
|
|
521ebb0ccf | ||
|
|
684e115f30 | ||
|
|
6ba17176c6 |
Submodule litegraph updated: d335948703...9b8d28d3e2
@@ -3,7 +3,7 @@
|
||||
"comfyUIPort": 8188,
|
||||
"alwaysStripUserState": false,
|
||||
"promptForWorkflowName": false,
|
||||
"confirmWhenUnloadingUnsavedChanges": true,
|
||||
"confirmWhenUnloadingUnsavedChanges": false,
|
||||
"builtInTemplates": ["ControlNet", "LoRA x5", "Model Loader", "Positive_Negative", "Seed Randomizer"],
|
||||
"cacheBuiltInResources": true
|
||||
}
|
||||
|
||||
@@ -3331,17 +3331,17 @@
|
||||
"title": "UI.Text",
|
||||
"properties": {
|
||||
"tags": [],
|
||||
"defaultValue": "masterpiece, 1girl, (yuri:1.2), city street, cityscape, open shirt, breasts, large breasts, nipples, shiny skin, full body, happy, red hair, red eyes, looking at another, eye contact, red skirt",
|
||||
"defaultValue": "masterpiece, 1girl, city street, cityscape, large shiny skin, full body, happy, red hair, red eyes, looking at another, eye contact, red skirt",
|
||||
"multiline": true,
|
||||
"lines": 5,
|
||||
"maxLines": 5
|
||||
},
|
||||
"widgets_values": [
|
||||
"masterpiece, 1girl, (yuri:1.2), city street, cityscape, open shirt, breasts, large breasts, nipples, shiny skin, full body, happy, red hair, red eyes, looking at another, eye contact, red skirt"
|
||||
"masterpiece, 1girl, city street, cityscape, large shiny skin, full body, happy, red hair, red eyes, looking at another, eye contact, red skirt"
|
||||
],
|
||||
"color": "#223",
|
||||
"bgColor": "#335",
|
||||
"comfyValue": "masterpiece, 1girl, (yuri:1.2), city street, cityscape, open shirt, breasts, large breasts, nipples, shiny skin, full body, happy, red hair, red eyes, looking at another, eye contact, red skirt",
|
||||
"comfyValue": "masterpiece, 1girl, city street, cityscape, large shiny skin, full body, happy, red hair, red eyes, looking at another, eye contact, red skirt",
|
||||
"shownOutputProperties": {},
|
||||
"saveUserState": true
|
||||
},
|
||||
@@ -3396,17 +3396,17 @@
|
||||
"title": "UI.Text",
|
||||
"properties": {
|
||||
"tags": [],
|
||||
"defaultValue": "masterpiece, 1girl, (yuri:1.2), open shirt, breasts, medium breasts, nipples, city street, cityscape, full body, happy, blue hair, blue eyes, looking at another, eye contact, blue skirt",
|
||||
"defaultValue": "masterpiece, 1girl, city street, cityscape, full body, happy, blue hair, blue eyes, looking at another, eye contact, blue skirt",
|
||||
"multiline": true,
|
||||
"lines": 5,
|
||||
"maxLines": 5
|
||||
},
|
||||
"widgets_values": [
|
||||
"masterpiece, 1girl, (yuri:1.2), open shirt, breasts, medium breasts, nipples, city street, cityscape, full body, happy, blue hair, blue eyes, looking at another, eye contact, blue skirt"
|
||||
"masterpiece, 1girl, city street, cityscape, full body, happy, blue hair, blue eyes, looking at another, eye contact, blue skirt"
|
||||
],
|
||||
"color": "#223",
|
||||
"bgColor": "#335",
|
||||
"comfyValue": "masterpiece, 1girl, (yuri:1.2), open shirt, breasts, medium breasts, nipples, city street, cityscape, full body, happy, blue hair, blue eyes, looking at another, eye contact, blue skirt",
|
||||
"comfyValue": "masterpiece, 1girl, city street, cityscape, full body, happy, blue hair, blue eyes, looking at another, eye contact, blue skirt",
|
||||
"shownOutputProperties": {},
|
||||
"saveUserState": true
|
||||
},
|
||||
@@ -3461,17 +3461,17 @@
|
||||
"title": "UI.Text",
|
||||
"properties": {
|
||||
"tags": [],
|
||||
"defaultValue": "masterpiece, 2girls, (yuri:1.2), open shirt, small breasts, nipples, city street, cityscape, full body, happy, yellow hair, yellow eyes, looking at another, eye contact, yellow skirt",
|
||||
"defaultValue": "masterpiece, 2girls, small city street, cityscape, full body, happy, yellow hair, yellow eyes, looking at another, eye contact, yellow skirt",
|
||||
"multiline": true,
|
||||
"lines": 5,
|
||||
"maxLines": 5
|
||||
},
|
||||
"widgets_values": [
|
||||
"masterpiece, 2girls, (yuri:1.2), open shirt, small breasts, nipples, city street, cityscape, full body, happy, yellow hair, yellow eyes, looking at another, eye contact, yellow skirt"
|
||||
"masterpiece, 2girls, small city street, cityscape, full body, happy, yellow hair, yellow eyes, looking at another, eye contact, yellow skirt"
|
||||
],
|
||||
"color": "#223",
|
||||
"bgColor": "#335",
|
||||
"comfyValue": "masterpiece, 2girls, (yuri:1.2), open shirt, small breasts, nipples, city street, cityscape, full body, happy, yellow hair, yellow eyes, looking at another, eye contact, yellow skirt",
|
||||
"comfyValue": "masterpiece, 2girls, small city street, cityscape, full body, happy, yellow hair, yellow eyes, looking at another, eye contact, yellow skirt",
|
||||
"shownOutputProperties": {},
|
||||
"saveUserState": true
|
||||
},
|
||||
@@ -3526,17 +3526,17 @@
|
||||
"title": "UI.Text",
|
||||
"properties": {
|
||||
"tags": [],
|
||||
"defaultValue": "masterpiece, 2girls, (yuri:1.2), open shirt, flat chest, nipples, city street, cityscape, full body, happy, green hair, green eyes, looking at another, eye contact, green skirt",
|
||||
"defaultValue": "masterpiece, 2girls, city street, cityscape, full body, happy, green hair, green eyes, looking at another, eye contact, green skirt",
|
||||
"multiline": true,
|
||||
"lines": 5,
|
||||
"maxLines": 5
|
||||
},
|
||||
"widgets_values": [
|
||||
"masterpiece, 2girls, (yuri:1.2), open shirt, flat chest, nipples, city street, cityscape, full body, happy, green hair, green eyes, looking at another, eye contact, green skirt"
|
||||
"masterpiece, 2girls, city street, cityscape, full body, happy, green hair, green eyes, looking at another, eye contact, green skirt"
|
||||
],
|
||||
"color": "#223",
|
||||
"bgColor": "#335",
|
||||
"comfyValue": "masterpiece, 2girls, (yuri:1.2), open shirt, flat chest, nipples, city street, cityscape, full body, happy, green hair, green eyes, looking at another, eye contact, green skirt",
|
||||
"comfyValue": "masterpiece, 2girls, city street, cityscape, full body, happy, green hair, green eyes, looking at another, eye contact, green skirt",
|
||||
"shownOutputProperties": {},
|
||||
"saveUserState": true
|
||||
},
|
||||
@@ -4706,17 +4706,17 @@
|
||||
"title": "UI.Text",
|
||||
"properties": {
|
||||
"tags": [],
|
||||
"defaultValue": "nsfw, masterpiece, 4girls, multiple girls, city street, cityscape, landscape, jeans, shoes, shirt, kanpai, happy, red hair, yellow hair, blue hair, green hair, looking at another, eye contact",
|
||||
"defaultValue": "masterpiece, 4girls, multiple girls, city street, cityscape, landscape, jeans, shoes, shirt, kanpai, happy, red hair, yellow hair, blue hair, green hair, looking at another, eye contact",
|
||||
"multiline": true,
|
||||
"lines": 5,
|
||||
"maxLines": 5
|
||||
},
|
||||
"widgets_values": [
|
||||
"nsfw, masterpiece, 4girls, multiple girls, city street, cityscape, landscape, jeans, shoes, shirt, kanpai, happy, red hair, yellow hair, blue hair, green hair, looking at another, eye contact"
|
||||
"masterpiece, 4girls, multiple girls, city street, cityscape, landscape, jeans, shoes, shirt, kanpai, happy, red hair, yellow hair, blue hair, green hair, looking at another, eye contact"
|
||||
],
|
||||
"color": "#223",
|
||||
"bgColor": "#335",
|
||||
"comfyValue": "nsfw, masterpiece, 4girls, multiple girls, city street, cityscape, landscape, jeans, shoes, shirt, kanpai, happy, red hair, yellow hair, blue hair, green hair, looking at another, eye contact",
|
||||
"comfyValue": "masterpiece, 4girls, multiple girls, city street, cityscape, landscape, jeans, shoes, shirt, kanpai, happy, red hair, yellow hair, blue hair, green hair, looking at another, eye contact",
|
||||
"shownOutputProperties": {},
|
||||
"saveUserState": true
|
||||
},
|
||||
@@ -10027,4 +10027,4 @@
|
||||
],
|
||||
"scale": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
"comfyBoxWorkflow": true,
|
||||
"createdBy": "ComfyBox",
|
||||
"version": 1,
|
||||
"commitHash": "574d3170a4e829df366dc12c3aaa049121052d8f\n",
|
||||
"commitHash": "60bd9899150678dbc46df543ac16e1099d58a07f\n",
|
||||
"workflow": {
|
||||
"last_node_id": 0,
|
||||
"last_link_id": 0,
|
||||
@@ -391,20 +391,7 @@
|
||||
"title": "UI.Gallery",
|
||||
"properties": {
|
||||
"tags": [],
|
||||
"defaultValue": [
|
||||
{
|
||||
"isComfyBoxImageMetadata": true,
|
||||
"comfyUIFile": {
|
||||
"filename": "ComfyUI_04712_.png",
|
||||
"subfolder": "",
|
||||
"type": "output"
|
||||
},
|
||||
"name": "File",
|
||||
"tags": [],
|
||||
"width": 6656,
|
||||
"height": 4096
|
||||
}
|
||||
],
|
||||
"defaultValue": [],
|
||||
"index": 0,
|
||||
"updateMode": "replace",
|
||||
"autoSelectOnUpdate": true
|
||||
@@ -412,20 +399,7 @@
|
||||
"widgets_values": [],
|
||||
"color": "#223",
|
||||
"bgColor": "#335",
|
||||
"comfyValue": [
|
||||
{
|
||||
"isComfyBoxImageMetadata": true,
|
||||
"comfyUIFile": {
|
||||
"filename": "ComfyUI_04712_.png",
|
||||
"subfolder": "",
|
||||
"type": "output"
|
||||
},
|
||||
"name": "File",
|
||||
"tags": [],
|
||||
"width": 6656,
|
||||
"height": 4096
|
||||
}
|
||||
],
|
||||
"comfyValue": [],
|
||||
"shownOutputProperties": {},
|
||||
"saveUserState": false
|
||||
},
|
||||
@@ -528,39 +502,13 @@
|
||||
],
|
||||
"title": "UI.ImageUpload",
|
||||
"properties": {
|
||||
"defaultValue": [
|
||||
{
|
||||
"isComfyBoxImageMetadata": true,
|
||||
"comfyUIFile": {
|
||||
"filename": "ComfyUI_05835_.png",
|
||||
"type": "output",
|
||||
"subfolder": ""
|
||||
},
|
||||
"name": "File",
|
||||
"tags": [],
|
||||
"width": 640,
|
||||
"height": 768
|
||||
}
|
||||
],
|
||||
"defaultValue": [],
|
||||
"tags": []
|
||||
},
|
||||
"widgets_values": [],
|
||||
"color": "#223",
|
||||
"bgColor": "#335",
|
||||
"comfyValue": [
|
||||
{
|
||||
"isComfyBoxImageMetadata": true,
|
||||
"comfyUIFile": {
|
||||
"filename": "ComfyUI_05835_.png",
|
||||
"type": "output",
|
||||
"subfolder": ""
|
||||
},
|
||||
"name": "File",
|
||||
"tags": [],
|
||||
"width": 640,
|
||||
"height": 768
|
||||
}
|
||||
],
|
||||
"comfyValue": [],
|
||||
"shownOutputProperties": {},
|
||||
"saveUserState": false
|
||||
},
|
||||
@@ -684,7 +632,8 @@
|
||||
"attrs": {
|
||||
"title": "Upscale by Model",
|
||||
"queuePromptButtonName": "Queue Prompt",
|
||||
"queuePromptButtonRunWorkflow": true
|
||||
"queuePromptButtonRunWorkflow": true,
|
||||
"showDefaultNotifications": true
|
||||
},
|
||||
"layout": {
|
||||
"root": "46a08906-61a9-4a23-881b-9615cf165e33",
|
||||
@@ -930,4 +879,4 @@
|
||||
],
|
||||
"scale": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -285,6 +285,19 @@ function relocateNodes(nodes: SerializedLGraphNode[]): SerializedLGraphNode[] {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/*
|
||||
* Strips tags from top-level nodes
|
||||
*/
|
||||
function stripTags(nodes: SerializedLGraphNode[]): SerializedLGraphNode[] {
|
||||
for (const node of nodes) {
|
||||
if (Array.isArray(node.properties.tags)) {
|
||||
node.properties.tags = []
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function pruneDetachedLinks(nodes: SerializedLGraphNode[], links: SerializedTemplateLink[]): [SerializedLGraphNode[], SerializedTemplateLink[]] {
|
||||
const nodeIds = new Set(nodes.map(n => n.id));
|
||||
|
||||
@@ -338,6 +351,7 @@ export function serializeTemplate(canvas: ComfyGraphCanvas, template: ComfyBoxTe
|
||||
uiState.update(s => { s.forceSaveUserState = null; return s; });
|
||||
|
||||
nodes = relocateNodes(nodes);
|
||||
nodes = stripTags(nodes);
|
||||
[nodes, links] = pruneDetachedLinks(nodes, links);
|
||||
|
||||
const svg = renderSvg(canvas, graph, TEMPLATE_SVG_PADDING);
|
||||
|
||||
@@ -249,6 +249,11 @@ export default class ComfyGraph extends LGraph {
|
||||
node_data.id = uuidv4();
|
||||
templateNodeIDToNewNode[prevNodeId] = node
|
||||
|
||||
// Strip tags from top-level nodes
|
||||
if (Array.isArray(node_data.properties.tags)) {
|
||||
node_data.properties.tags = []
|
||||
}
|
||||
|
||||
node.configure(node_data);
|
||||
|
||||
if (mapping) {
|
||||
|
||||
@@ -29,7 +29,7 @@ import queueState from "$lib/stores/queueState";
|
||||
import selectionState from "$lib/stores/selectionState";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import workflowState, { ComfyBoxWorkflow, type WorkflowAttributes, type WorkflowInstID } from "$lib/stores/workflowState";
|
||||
import { readFileToText, type SerializedPromptOutput } from "$lib/utils";
|
||||
import { playSound, readFileToText, type SerializedPromptOutput } from "$lib/utils";
|
||||
import { basename, capitalize, download, graphToGraphVis, jsonToJsObject, promptToGraphVis, range } from "$lib/utils";
|
||||
import { tick } from "svelte";
|
||||
import { type SvelteComponentDev } from "svelte/internal";
|
||||
@@ -891,7 +891,7 @@ export default class ComfyApp {
|
||||
if (workflow.attrs.queuePromptButtonRunWorkflow) {
|
||||
// Hold control to queue at the front
|
||||
const num = this.ctrlDown ? -1 : 0;
|
||||
this.queuePrompt(num, 1);
|
||||
this.queuePrompt(workflow, num, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -941,14 +941,8 @@ export default class ComfyApp {
|
||||
return this.promptSerializer.serialize(workflow.graph, tag)
|
||||
}
|
||||
|
||||
async queuePrompt(num: number, batchCount: number = 1, tag: string | null = null) {
|
||||
const activeWorkflow = workflowState.getActiveWorkflow();
|
||||
if (activeWorkflow == null) {
|
||||
notify("No workflow is opened!", { type: "error" })
|
||||
return;
|
||||
}
|
||||
|
||||
this.queueItems.push({ num, batchCount, workflow: activeWorkflow });
|
||||
async queuePrompt(targetWorkflow: ComfyBoxWorkflow, num: number, batchCount: number = 1, tag: string | null = null) {
|
||||
this.queueItems.push({ num, batchCount, workflow: targetWorkflow });
|
||||
|
||||
// Only have one action process the items so each one gets a unique seed correctly
|
||||
if (this.processingQueue) {
|
||||
@@ -958,6 +952,10 @@ export default class ComfyApp {
|
||||
if (tag === "")
|
||||
tag = null;
|
||||
|
||||
if (targetWorkflow.attrs.showDefaultNotifications) {
|
||||
notify("Prompt queued.", { type: "info" });
|
||||
}
|
||||
|
||||
this.processingQueue = true;
|
||||
let workflow: ComfyBoxWorkflow;
|
||||
|
||||
@@ -968,7 +966,7 @@ export default class ComfyApp {
|
||||
|
||||
const thumbnails = []
|
||||
for (const node of workflow.graph.iterateNodesInOrderRecursive()) {
|
||||
if (node.mode !== NodeMode.ALWAYS || (tag != null && !nodeHasTag(node, tag)))
|
||||
if (node.mode !== NodeMode.ALWAYS || (tag != null && !nodeHasTag(node, tag, true)))
|
||||
continue;
|
||||
|
||||
if ("getPromptThumbnails" in node) {
|
||||
@@ -1020,7 +1018,7 @@ export default class ComfyApp {
|
||||
try {
|
||||
const response = await this.api.queuePrompt(request);
|
||||
if (response.error != null) {
|
||||
error = response.error;
|
||||
error = response;
|
||||
}
|
||||
else {
|
||||
queueState.afterQueued(workflow.id, response.promptID, num, p.output, extraData)
|
||||
@@ -1030,7 +1028,7 @@ export default class ComfyApp {
|
||||
}
|
||||
|
||||
if (error != null) {
|
||||
const mes: string = error;
|
||||
const mes: any = error;
|
||||
notify(`Error queuing prompt: \n${mes} `, { type: "error" })
|
||||
console.error(graphToGraphVis(workflow.graph))
|
||||
console.error(promptToGraphVis(p))
|
||||
|
||||
@@ -15,18 +15,17 @@ function isGraphInputOutput(node: LGraphNode): boolean {
|
||||
return node.is(GraphInput) || node.is(GraphOutput)
|
||||
}
|
||||
|
||||
export function nodeHasTag(node: LGraphNode, tag: string): boolean {
|
||||
// Ignore tags on reroutes since they're just movable wires and it defeats
|
||||
// the convenience gains to have to set tags for all them
|
||||
if (isReroute(node))
|
||||
return true;
|
||||
|
||||
export function nodeHasTag(node: LGraphNode, tag: string, checkParents: boolean): boolean {
|
||||
while (node != null) {
|
||||
if ("tags" in node.properties) {
|
||||
if (node.properties.tags.indexOf(tag) !== -1)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!checkParents) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Count parent subgraphs having the tag also.
|
||||
node = node.graph?._subgraph_node;
|
||||
}
|
||||
@@ -38,8 +37,10 @@ export function isActiveNode(node: LGraphNode, tag: string | null = null): boole
|
||||
if (!node)
|
||||
return false;
|
||||
|
||||
// Check tags but not on graph inputs/outputs
|
||||
if (!isGraphInputOutput(node) && (tag && !nodeHasTag(node, tag))) {
|
||||
// Ignore tags on reroutes since they're just movable wires and it defeats
|
||||
// the convenience gains to have to set tags for all them
|
||||
// Also ignore graph inputs/outputs
|
||||
if (!isReroute(node) && !isGraphInputOutput(node) && (tag && !nodeHasTag(node, tag, true))) {
|
||||
console.debug("Skipping tagged node", tag, node.properties.tags, node)
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -144,8 +144,9 @@
|
||||
if (entry.extraData?.workflowTitle != null) {
|
||||
message = `${entry.extraData.workflowTitle}`
|
||||
}
|
||||
if (subgraphs?.length > 0)
|
||||
message += ` (${subgraphs.join(', ')})`
|
||||
const subgraphsString = subgraphs.join(', ')
|
||||
if (subgraphsString.length > 0)
|
||||
message += ` (${subgraphsString})`
|
||||
|
||||
let submessage = `Nodes: ${Object.keys(entry.prompt).length}`
|
||||
|
||||
|
||||
@@ -103,6 +103,16 @@ export default class ComfyGraphNode extends LGraphNode {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Traverses this node backwards in the graph in order to determine the type
|
||||
* for slot type inheritance. This is used if there isn't a valid upstream
|
||||
* link but the output type can be inferred otherwise (for example from
|
||||
* properties or other connected inputs)
|
||||
*/
|
||||
getUpstreamLinkForInheritedType(): LLink | null {
|
||||
return this.getUpstreamLink();
|
||||
}
|
||||
|
||||
get layoutState(): WritableLayoutStateStore | null {
|
||||
return layoutStates.getLayoutByNode(this);
|
||||
}
|
||||
@@ -151,7 +161,7 @@ export default class ComfyGraphNode extends LGraphNode {
|
||||
|
||||
while (currentNode) {
|
||||
updateNodes.unshift(currentNode);
|
||||
const link = currentNode.getUpstreamLink();
|
||||
const link = currentNode.getUpstreamLinkForInheritedType();
|
||||
if (link !== null) {
|
||||
const node = this.graph.getNodeById(link.origin_id) as ComfyGraphNode;
|
||||
if (node.canInheritSlotTypes) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { BuiltInSlotType, LiteGraph, NodeMode, type INodeInputSlot, type SlotLayout, type INodeOutputSlot, LLink, LConnectionKind, type ITextWidget, type SerializedLGraphNode, type IComboWidget } from "@litegraph-ts/core";
|
||||
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
|
||||
import { Watch } from "@litegraph-ts/nodes-basic";
|
||||
import { nextLetter } from "$lib/utils";
|
||||
|
||||
export type PickFirstMode = "anyActiveLink" | "truthy" | "dataNonNull"
|
||||
|
||||
@@ -8,17 +9,6 @@ export interface ComfyPickFirstNodeProperties extends ComfyGraphNodeProperties {
|
||||
mode: PickFirstMode
|
||||
}
|
||||
|
||||
function nextLetter(s: string): string {
|
||||
return s.replace(/([a-zA-Z])[^a-zA-Z]*$/, function(a) {
|
||||
var c = a.charCodeAt(0);
|
||||
switch (c) {
|
||||
case 90: return 'A';
|
||||
case 122: return 'a';
|
||||
default: return String.fromCharCode(++c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default class ComfyPickFirstNode extends ComfyGraphNode {
|
||||
override properties: ComfyPickFirstNodeProperties = {
|
||||
tags: [],
|
||||
|
||||
147
src/lib/nodes/ComfySwitch.ts
Normal file
147
src/lib/nodes/ComfySwitch.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { nextLetter } from "$lib/utils";
|
||||
import { LConnectionKind, LLink, LiteGraph, type INodeInputSlot, type INodeOutputSlot, type SlotLayout } from "@litegraph-ts/core";
|
||||
import ComfyGraphNode from "./ComfyGraphNode";
|
||||
|
||||
export default class ComfySwitch extends ComfyGraphNode {
|
||||
static slotLayout: SlotLayout = {
|
||||
inputs: [
|
||||
{ name: "A_value", type: "*" },
|
||||
{ name: "A_cond", type: "boolean" },
|
||||
],
|
||||
outputs: [
|
||||
{ name: "out", type: "*" }
|
||||
],
|
||||
}
|
||||
|
||||
override canInheritSlotTypes = true;
|
||||
|
||||
private _selected: number | null = null;
|
||||
|
||||
constructor(title?: string) {
|
||||
super(title);
|
||||
}
|
||||
|
||||
override getUpstreamLinkForInheritedType(): LLink | null {
|
||||
for (let index = 0; index < this.inputs.length / 2; index++) {
|
||||
const link = this.getInputLink(index * 2);
|
||||
if (link != null)
|
||||
return link
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
override getUpstreamLink(): LLink | null {
|
||||
const selected = this.getSelected();
|
||||
if (selected == null)
|
||||
return null;
|
||||
|
||||
return this.getInputLink(selected * 2);
|
||||
}
|
||||
|
||||
getSelected(): number | null {
|
||||
for (let i = 0; i < this.inputs.length / 2; i++) {
|
||||
if (this.getInputData(i * 2 + 1) == true)
|
||||
return i
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
override onDrawBackground(ctx: CanvasRenderingContext2D) {
|
||||
if (this.flags.collapsed || this._selected == null) {
|
||||
return;
|
||||
}
|
||||
ctx.fillStyle = "#AFB";
|
||||
var y = this._selected * 2 * LiteGraph.NODE_SLOT_HEIGHT + 6;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(30 + 50, y);
|
||||
ctx.lineTo(30 + 50, y + LiteGraph.NODE_SLOT_HEIGHT);
|
||||
ctx.lineTo(30 + 34, y + LiteGraph.NODE_SLOT_HEIGHT * 0.5);
|
||||
ctx.fill();
|
||||
};
|
||||
|
||||
override onExecute() {
|
||||
this._selected = this.getSelected();
|
||||
var sel = this._selected
|
||||
|
||||
if (sel == null || sel.constructor !== Number) {
|
||||
this.setOutputData(0, null)
|
||||
return
|
||||
}
|
||||
|
||||
var v = this.getInputData(sel * 2);
|
||||
if (v !== undefined) {
|
||||
this.setOutputData(0, v);
|
||||
}
|
||||
}
|
||||
|
||||
private hasActiveSlots(pairIndex: number): boolean {
|
||||
const slotValue = this.inputs[pairIndex * 2]
|
||||
const slotCond = this.inputs[pairIndex * 2 + 1];
|
||||
return slotValue && slotCond && (slotValue.link != null || slotCond.link != null);
|
||||
}
|
||||
|
||||
override onConnectionsChange(
|
||||
type: LConnectionKind,
|
||||
slotIndex: number,
|
||||
isConnected: boolean,
|
||||
link: LLink,
|
||||
ioSlot: (INodeInputSlot | INodeOutputSlot)
|
||||
) {
|
||||
super.onConnectionsChange(type, slotIndex, isConnected, link, ioSlot);
|
||||
|
||||
if (type !== LConnectionKind.INPUT)
|
||||
return;
|
||||
|
||||
const lastPairIdx = Math.floor((this.inputs.length / 2) - 1);
|
||||
|
||||
let newlyConnected = false;
|
||||
if (isConnected) {
|
||||
newlyConnected = this.hasActiveSlots(lastPairIdx)
|
||||
}
|
||||
let newlyDisconnected = false;
|
||||
if (!isConnected) {
|
||||
newlyDisconnected = !this.hasActiveSlots(lastPairIdx) && !this.hasActiveSlots(lastPairIdx - 1)
|
||||
}
|
||||
|
||||
console.error("CONNCHANGE", lastPairIdx, this.hasActiveSlots(lastPairIdx), isConnected, slotIndex, this.inputs.length, newlyConnected, newlyDisconnected);
|
||||
|
||||
if (newlyConnected) {
|
||||
if (link != null) {
|
||||
// Add new inputs
|
||||
const lastInputName = this.inputs[this.inputs.length - 1].name
|
||||
const inputName = nextLetter(lastInputName.split("_")[0]);
|
||||
this.addInput(`${inputName}_value`, this.inputs[0].type)
|
||||
this.addInput(`${inputName}_cond`, "boolean")
|
||||
}
|
||||
}
|
||||
else if (newlyDisconnected) {
|
||||
// Remove empty inputs
|
||||
for (let i = this.inputs.length / 2; i > 0; i -= 1) {
|
||||
if (i <= 0)
|
||||
break;
|
||||
|
||||
if (!this.hasActiveSlots(i - 1)) {
|
||||
this.removeInput(i * 2)
|
||||
this.removeInput(i * 2)
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let name = "A"
|
||||
for (let i = 0; i < this.inputs.length; i += 2) {
|
||||
this.inputs[i].name = `${name}_value`;
|
||||
this.inputs[i + 1].name = `${name}_cond`
|
||||
name = nextLetter(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType({
|
||||
class: ComfySwitch,
|
||||
title: "Comfy.Switch",
|
||||
desc: "Selects an output if its condition is true, if none match returns null",
|
||||
type: "utils/switch"
|
||||
})
|
||||
@@ -47,7 +47,7 @@ export default class ComfyExecuteSubgraphAction extends ComfyGraphNode {
|
||||
|
||||
// Hold control to queue at the front
|
||||
const num = app.ctrlDown ? -1 : 0;
|
||||
app.queuePrompt(num, 1, tag);
|
||||
app.queuePrompt(this.workflow, num, 1, tag);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { BuiltInSlotType, LiteGraph, type SlotLayout } from "@litegraph-ts/core";
|
||||
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "../ComfyGraphNode";
|
||||
import { playSound } from "$lib/utils";
|
||||
|
||||
export interface ComfyPlaySoundActionProperties extends ComfyGraphNodeProperties {
|
||||
sound: string,
|
||||
@@ -21,9 +22,7 @@ export default class ComfyPlaySoundAction extends ComfyGraphNode {
|
||||
override onAction(action: any, param: any) {
|
||||
const sound = this.getInputData(0) || this.properties.sound;
|
||||
if (sound) {
|
||||
const url = `${location.origin}/sound/${sound}`;
|
||||
const audio = new Audio(url);
|
||||
audio.play();
|
||||
playSound(sound)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export default class ComfySetNodeModeAction extends ComfyGraphNode {
|
||||
for (const node of this.graph._nodes) {
|
||||
if ("tags" in node.properties) {
|
||||
const comfyNode = node as ComfyGraphNode;
|
||||
const hasTag = tags.some(t => nodeHasTag(comfyNode, t));
|
||||
const hasTag = tags.some(t => nodeHasTag(comfyNode, t, false));
|
||||
if (hasTag) {
|
||||
let newMode: NodeMode;
|
||||
if (enabled) {
|
||||
|
||||
@@ -69,7 +69,7 @@ export default class ComfySetNodeModeAdvancedAction extends ComfyGraphNode {
|
||||
for (const node of this.graph.iterateNodesInOrderRecursive()) {
|
||||
if ("tags" in node.properties) {
|
||||
const comfyNode = node as ComfyGraphNode;
|
||||
const hasTag = nodeHasTag(comfyNode, action.tag);
|
||||
const hasTag = nodeHasTag(comfyNode, action.tag, false);
|
||||
|
||||
if (hasTag) {
|
||||
let newMode: NodeMode;
|
||||
|
||||
@@ -2,6 +2,7 @@ export { default as ComfyReroute } from "./ComfyReroute"
|
||||
export { default as ComfyPickFirstNode } from "./ComfyPickFirstNode"
|
||||
export { default as ComfyValueControl } from "./ComfyValueControl"
|
||||
export { default as ComfySelector } from "./ComfySelector"
|
||||
export { default as ComfySwitch } from "./ComfySwitch"
|
||||
export { default as ComfyTriggerNewEventNode } from "./ComfyTriggerNewEventNode"
|
||||
export { default as ComfyConfigureQueuePromptButton } from "./ComfyConfigureQueuePromptButton"
|
||||
export { default as ComfyPickImageNode } from "./ComfyPickImageNode"
|
||||
|
||||
@@ -667,6 +667,13 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
|
||||
location: "workflow",
|
||||
editable: true,
|
||||
defaultValue: true
|
||||
},
|
||||
{
|
||||
name: "showDefaultNotifications",
|
||||
type: "boolean",
|
||||
location: "workflow",
|
||||
editable: true,
|
||||
defaultValue: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import type { Progress, SerializedPromptInputsAll, SerializedPromptOutputs, Work
|
||||
import type { ComfyExecutionResult } from "$lib/nodes/ComfyWidgetNodes";
|
||||
import notify from "$lib/notify";
|
||||
import { get, writable, type Writable } from "svelte/store";
|
||||
import workflowState from "./workflowState";
|
||||
import { playSound } from "$lib/utils";
|
||||
|
||||
export type QueueEntryStatus = "success" | "error" | "interrupted" | "all_cached" | "unknown";
|
||||
|
||||
@@ -267,6 +269,11 @@ function executingUpdated(promptID: PromptID, runningNodeID: ComfyNodeID | null)
|
||||
moveToCompleted(index, queue, "all_cached", "(Execution was cached)");
|
||||
}
|
||||
else if (entry.nodesRan.size >= totalNodesInPrompt) {
|
||||
const workflow = workflowState.getWorkflow(entry.extraData.workflowID);
|
||||
if (workflow?.attrs.showDefaultNotifications) {
|
||||
notify("Prompt finished!", { type: "success" });
|
||||
playSound("notification.mp3")
|
||||
}
|
||||
moveToCompleted(index, queue, "success")
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -54,6 +54,13 @@ export type WorkflowAttributes = {
|
||||
* Comfy.QueueEvents node.
|
||||
*/
|
||||
queuePromptButtonRunWorkflow: boolean,
|
||||
|
||||
/*
|
||||
* If true, notifications will be shown when a prompt is queued and
|
||||
* completed. Set to false if you need more detailed control over the
|
||||
* notification type/contents, and use the `ComfyNotifyAction` node instead.
|
||||
*/
|
||||
showDefaultNotifications: boolean,
|
||||
}
|
||||
|
||||
export class ComfyBoxWorkflow {
|
||||
@@ -217,7 +224,7 @@ export class ComfyBoxWorkflow {
|
||||
// this.#invokeExtensions("loadedGraphNode", node);
|
||||
}
|
||||
|
||||
this.attrs = data.attrs;
|
||||
this.attrs = { ...defaultWorkflowAttributes, ...data.attrs };
|
||||
|
||||
// Now restore the layout
|
||||
// Subsequent added nodes will add the UI data to layoutState
|
||||
|
||||
@@ -610,3 +610,21 @@ export async function readFileToText(file: File): Promise<string> {
|
||||
reader.readAsText(file);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export function nextLetter(s: string): string {
|
||||
return s.replace(/([a-zA-Z])[^a-zA-Z]*$/, function(a) {
|
||||
var c = a.charCodeAt(0);
|
||||
switch (c) {
|
||||
case 90: return 'A';
|
||||
case 122: return 'a';
|
||||
default: return String.fromCharCode(++c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function playSound(sound: string) {
|
||||
const url = `${location.origin}/sound/${sound}`;
|
||||
const audio = new Audio(url);
|
||||
audio.play();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user