Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3637e19
Add mime type for js files
pythongosssss Mar 2, 2023
5e25c77
Initial refactoring changes
pythongosssss Mar 2, 2023
2eaa664
Refactored sockets
pythongosssss Mar 2, 2023
7e436ba
Added handling of sockets
pythongosssss Mar 2, 2023
65c432e
Merge remote-tracking branch 'origin' into frontendrefactor
pythongosssss Mar 3, 2023
5e66b68
add loras to ignore
pythongosssss Mar 3, 2023
a5c5c97
More work on UI
pythongosssss Mar 3, 2023
72cdd83
Readded loading file
pythongosssss Mar 3, 2023
2e52d01
Documenting methods
pythongosssss Mar 3, 2023
4ef4cf9
Adding built in extensions + example
pythongosssss Mar 3, 2023
e14b79c
Changed to store resolved prompt in workflow info
pythongosssss Mar 3, 2023
c23af92
Redraw node on opt change
pythongosssss Mar 3, 2023
592b377
Added dynamic loading of extensions
pythongosssss Mar 3, 2023
bba1424
Merge remote-tracking branch 'origin' into frontendrefactor
pythongosssss Mar 3, 2023
947e786
Restored missing input edit fix
pythongosssss Mar 3, 2023
0e7b98e
Restored accidently removed code
pythongosssss Mar 3, 2023
9913114
Only poll if it has never opened
pythongosssss Mar 3, 2023
dc85b3b
Allow serializeValue to be async
pythongosssss Mar 3, 2023
1ee35fd
Merge remote-tracking branch 'origin/master' into frontendrefactor
pythongosssss Mar 5, 2023
a8315a6
Allow any node to draw images
pythongosssss Mar 6, 2023
4bdfbb3
Fixed check for polling
pythongosssss Mar 6, 2023
09db313
Merge remote-tracking branch 'origin/master' into frontendrefactor
pythongosssss Mar 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ output/
models/checkpoints
models/vae
models/embeddings
models/loras
2 changes: 1 addition & 1 deletion nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def interrupt_processing(value=True):
class CLIPTextEncode:
@classmethod
def INPUT_TYPES(s):
return {"required": {"text": ("STRING", {"multiline": True, "dynamic_prompt": True}), "clip": ("CLIP", )}}
return {"required": {"text": ("STRING", {"multiline": True}), "clip": ("CLIP", )}}
RETURN_TYPES = ("CONDITIONING",)
FUNCTION = "encode"

Expand Down
12 changes: 11 additions & 1 deletion server.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import execution
import uuid
import json
import glob

try:
import aiohttp
Expand All @@ -16,16 +17,20 @@
print("pip install -r requirements.txt")
sys.exit()

import mimetypes;

class PromptServer():
def __init__(self, loop):
mimetypes.init();
mimetypes.types_map['.js'] = 'application/javascript; charset=utf-8'
self.prompt_queue = None
self.loop = loop
self.messages = asyncio.Queue()
self.number = 0
self.app = web.Application()
self.sockets = dict()
self.web_root = os.path.join(os.path.dirname(
os.path.realpath(__file__)), "webshit")
os.path.realpath(__file__)), "web")
routes = web.RouteTableDef()

@routes.get('/ws')
Expand All @@ -48,6 +53,11 @@ async def websocket_handler(request):
async def get_root(request):
return web.FileResponse(os.path.join(self.web_root, "index.html"))

@routes.get("/extensions")
async def get_extensions(request):
files = glob.glob(os.path.join(self.web_root, 'extensions/**/*.js'), recursive=True)
return web.json_response(list(map(lambda f: "/" + os.path.relpath(f, self.web_root).replace("\\", "/"), files)))

@routes.get("/view/{file}")
async def view_image(request):
if "file" in request.match_info:
Expand Down
40 changes: 40 additions & 0 deletions web/extensions/core/dynamicPrompts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { app } from "../../scripts/app.js";

// Allows for simple dynamic prompt replacement
// Inputs in the format {a|b} will have a random value of a or b chosen when the prompt is queued.

app.registerExtension({
name: "Comfy.DynamicPrompts",
nodeCreated(node) {
if (node.widgets) {
// Locate dynamic prompt text widgets
// Include any widgets with dynamicPrompts set to true, and customtext
const widgets = node.widgets.filter(
(n) => (n.type === "customtext" && n.dynamicPrompts !== false) || n.dynamicPrompts
);
for (const widget of widgets) {
// Override the serialization of the value to resolve dynamic prompts for all widgets supporting it in this node
widget.serializeValue = (workflowNode, widgetIndex) => {
let prompt = widget.value;
while (prompt.replace("\\{", "").includes("{") && prompt.replace("\\}", "").includes("}")) {
const startIndex = prompt.replace("\\{", "00").indexOf("{");
const endIndex = prompt.replace("\\}", "00").indexOf("}");

const optionsString = prompt.substring(startIndex + 1, endIndex);
const options = optionsString.split("|");

const randomIndex = Math.floor(Math.random() * options.length);
const randomOption = options[randomIndex];

prompt = prompt.substring(0, startIndex) + randomOption + prompt.substring(endIndex + 1);
}

// Overwrite the value in the serialized workflow pnginfo
workflowNode.widgets_values[widgetIndex] = prompt;

return prompt;
};
}
}
},
});
93 changes: 93 additions & 0 deletions web/extensions/core/rerouteNode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { app } from "../../scripts/app.js";

// Node that allows you to redirect connections for cleaner graphs

app.registerExtension({
name: "Comfy.RerouteNode",
registerCustomNodes() {
class RerouteNode {
constructor() {
if (!this.properties) {
this.properties = {};
}
this.properties.showOutputText = RerouteNode.defaultVisibility;

this.addInput("", "*");
this.addOutput(this.properties.showOutputText ? "*" : "", "*");
this.onConnectInput = function (_, type) {
if (type !== this.outputs[0].type) {
this.removeOutput(0);
this.addOutput(this.properties.showOutputText ? type : "", type);
this.size = this.computeSize();
}
};

this.clone = function () {
const cloned = RerouteNode.prototype.clone.apply(this);
cloned.removeOutput(0);
cloned.addOutput(this.properties.showOutputText ? "*" : "", "*");
cloned.size = cloned.computeSize();
return cloned;
};

// This node is purely frontend and does not impact the resulting prompt so should not be serialized
this.isVirtualNode = true;
}

getExtraMenuOptions(_, options) {
options.unshift(
{
content: (this.properties.showOutputText ? "Hide" : "Show") + " Type",
callback: () => {
this.properties.showOutputText = !this.properties.showOutputText;
if (this.properties.showOutputText) {
this.outputs[0].name = this.outputs[0].type;
} else {
this.outputs[0].name = "";
}
this.size = this.computeSize();
app.graph.setDirtyCanvas(true);
},
},
{
content: (RerouteNode.defaultVisibility ? "Hide" : "Show") + " Type By Default",
callback: () => {
RerouteNode.setDefaultTextVisibility(!RerouteNode.defaultVisibility);
},
}
);
}

computeSize() {
return [
this.properties.showOutputText && this.outputs && this.outputs.length
? Math.max(55, LiteGraph.NODE_TEXT_SIZE * this.outputs[0].name.length * 0.6 + 40)
: 55,
26,
];
}

static setDefaultTextVisibility(visible) {
RerouteNode.defaultVisibility = visible;
if (visible) {
localStorage["Comfy.RerouteNode.DefaultVisibility"] = "true";
} else {
delete localStorage["Comfy.RerouteNode.DefaultVisibility"];
}
}
}

// Load default visibility
RerouteNode.setDefaultTextVisibility(!!localStorage["Comfy.RerouteNode.DefaultVisibility"]);

LiteGraph.registerNodeType(
"Reroute",
Object.assign(RerouteNode, {
title_mode: LiteGraph.NO_TITLE,
title: "Reroute",
})
);

RerouteNode.category = "utils";
},
});
55 changes: 55 additions & 0 deletions web/extensions/logging.js.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { app } from "../scripts/app.js";

const ext = {
// Unique name for the extension
name: "Example.LoggingExtension",
async init(app) {
// Any initial setup to run as soon as the page loads
console.log("[logging]", "extension init");
},
async setup(app) {
// Any setup to run after the app is created
console.log("[logging]", "extension setup");
},
async addCustomNodeDefs(defs, app) {
// Add custom node definitions
// These definitions will be configured and registered automatically
// defs is a lookup core nodes, add yours into this
console.log("[logging]", "add custom node definitions", "current nodes:", Object.keys(defs));
},
async getCustomWidgets(app) {
// Return custom widget types
// See ComfyWidgets for widget examples
console.log("[logging]", "provide custom widgets");
},
async beforeRegisterNodeDef(nodeType, nodeData, app) {
// Run custom logic before a node definition is registered with the graph
console.log("[logging]", "before register node: ", nodeType, nodeData);

// This fires for every node definition so only log once
delete ext.beforeRegisterNodeDef;
},
async registerCustomNodes(app) {
// Register any custom node implementations here allowing for more flexability than a custom node def
console.log("[logging]", "register custom nodes");
},
loadedGraphNode(node, app) {
// Fires for each node when loading/dragging/etc a workflow json or png
// If you break something in the backend and want to patch workflows in the frontend
// This is the place to do this
console.log("[logging]", "loaded graph node: ", node);

// This fires for every node on each load so only log once
delete ext.loadedGraphNode;
},
nodeCreated(node, app) {
// Fires every time a node is constructed
// You can modify widgets/add handlers/etc here
console.log("[logging]", "node created: ", node);

// This fires for every node so only log once
delete ext.nodeCreated;
}
};

app.registerExtension(ext);
16 changes: 16 additions & 0 deletions web/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" type="text/css" href="lib/litegraph.css" />
<link rel="stylesheet" type="text/css" href="style.css" />
<script type="text/javascript" src="lib/litegraph.core.js"></script>

<script type="module">
import { app } from "/scripts/app.js";
await app.setup();
window.app = app;
window.graph = app.graph;
</script>
</head>
<body></body>
</html>
File renamed without changes.
File renamed without changes.
Loading