13 Commits

Author SHA1 Message Date
space-nuko
cdcf566a43 Strip user state for upscaleByModel 2023-05-26 17:09:09 -05:00
space-nuko
b3584dd2ad Default notifications for workflows 2023-05-26 17:06:23 -05:00
space-nuko
60bd989915 Strip tags from top-level nodes when inserting templates 2023-05-26 16:06:54 -05:00
space-nuko
eb335e9be7 Fix litegraph 2023-05-26 14:19:05 -05:00
space-nuko
73e007844a Default preserve outputs true 2023-05-26 14:01:55 -05:00
space-nuko
e729bc6c46 Update default workflow 2023-05-26 13:57:42 -05:00
space-nuko
d11f66c5ac fix HR not setting thumbnails 2023-05-26 13:33:05 -05:00
space-nuko
9f5b14a2bf Update default workflow 2023-05-26 13:27:33 -05:00
space-nuko
74bad3bd1e Update default workflow 2023-05-26 13:11:34 -05:00
space-nuko
8e27a2611c Switch node 2023-05-26 12:59:38 -05:00
space-nuko
521ebb0ccf Fix default workflow 2023-05-25 22:13:38 -05:00
space-nuko
684e115f30 Fixes for tag scoping 2023-05-25 21:59:03 -05:00
space-nuko
6ba17176c6 Merge pull request #72 from space-nuko/builtin-templates
Builtin templates
2023-05-25 21:19:25 -05:00
22 changed files with 8287 additions and 7411 deletions

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
}
}
}

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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))

View File

@@ -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;
}

View File

@@ -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}`

View File

@@ -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) {

View File

@@ -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: [],

View 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"
})

View File

@@ -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);
}
}

View File

@@ -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)
}
};
}

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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"

View File

@@ -667,6 +667,13 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
location: "workflow",
editable: true,
defaultValue: true
},
{
name: "showDefaultNotifications",
type: "boolean",
location: "workflow",
editable: true,
defaultValue: true
}
]
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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();
}