diff --git a/__init__.py b/__init__.py index 861e6ec..a445663 100644 --- a/__init__.py +++ b/__init__.py @@ -15,6 +15,7 @@ from .src.prompt_selector import PromptSelectNode from .src.perlin_noise import FilmGrain from .src.upscale_with_model_by import UpscaleImageBy from .src.load_lora_from_folder import LoadLoraFolderNode +from .src.textbox import TextboxNode NODE_CLASS_MAPPINGS = { "Find SDXL Resolution": FindResolutionNode, @@ -31,6 +32,7 @@ NODE_CLASS_MAPPINGS = { "Colored Film Grain": FilmGrain, "Upscale Image With Model By": UpscaleImageBy, "Load Lora Folder": LoadLoraFolderNode, + "Copy/Paste Textbox": TextboxNode, } NODE_DISPLAY_NAME_MAPPINGS = { @@ -48,6 +50,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "Colored Film Grain": "Colored Film Grain", "Upscale Image With Model By": "Upscale Image With Model By", "Load Lora Folder": "Load Lora Folder", + "Copy/Paste Textbox": "Textbox", } WEB_DIRECTORY = "./web" diff --git a/sn0w.py b/sn0w.py index 91c9049..459f488 100644 --- a/sn0w.py +++ b/sn0w.py @@ -1,7 +1,11 @@ import os import json +import time import torch +from server import PromptServer +from aiohttp import web + class ConfigReader: @staticmethod def get_setting(setting_id, default=None): @@ -117,7 +121,7 @@ class Utility: def initialize_state(): # Default values default_state = { - 'random_character_chosen': False + 'character': "None" } state_path = os.path.join(Utility.ROOT_DIR, 'state.json') @@ -165,4 +169,29 @@ class Utility: # Create a new state.json with default values or empty with open(state_path, 'w') as f: json.dump({}, f) - return None \ No newline at end of file + return None + +class MessageHolder: + messages = {} + + @classmethod + def addMessage(self, id, message): + self.messages[str(id)] = message + print(f"Adding message for ID: {id}, Message: {message}") + + @classmethod + def waitForMessage(self, id, period = 0.1): + sid = str(id) + + while not (sid in self.messages): + time.sleep(period) + + message = self.messages.pop(str(id),None) + return message + +routes = PromptServer.instance.routes +@routes.post('/textbox_string') +async def handle_textbox_string(request): + data = await request.json() # Parse JSON from request + MessageHolder.addMessage(data.get("node_id"), data.get("outputs")) + return web.json_response({"status": "ok"}) \ No newline at end of file diff --git a/src/textbox.py b/src/textbox.py new file mode 100644 index 0000000..cbbd163 --- /dev/null +++ b/src/textbox.py @@ -0,0 +1,26 @@ +from server import PromptServer +from ..sn0w import MessageHolder + +class TextboxNode: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + }, + "hidden": { + "unique_id": "UNIQUE_ID", + }, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "run" + + CATEGORY = "utils" + + def run(self, unique_id): + PromptServer.instance.send_sync("textbox", { + "id": unique_id, + }) + outputs = MessageHolder.waitForMessage(unique_id) + return (outputs['output'],) + \ No newline at end of file diff --git a/web/textbox.js b/web/textbox.js new file mode 100644 index 0000000..1c57275 --- /dev/null +++ b/web/textbox.js @@ -0,0 +1,64 @@ +import { app } from "../../../scripts/app.js"; +import { api } from '../../scripts/api.js'; +import { ComfyWidgets } from "../../../scripts/widgets.js"; + +app.registerExtension({ + name: "sn0w.Textbox", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "Copy/Paste Textbox") { + // Get textbox text + nodeType.prototype.getTextboxText = function() { + const textbox = this.inputEl ? this.inputEl.value : ''; + return textbox; + }; + + nodeType.prototype.populate = function() { + // Adding a multiline text widget and storing a reference to the input element + let textWidget = ComfyWidgets["STRING"](this, "text", ["STRING", { default: "", multiline: true }], app).widget; + this.inputEl = textWidget.inputEl || textWidget.querySelector('input, textarea'); + + // Implement copy functionality with proper callback + this.addWidget("button", "Copy", "Copy", () => { + navigator.clipboard.writeText(this.getTextboxText()); + }, { serialize: false }); + + // Implement paste functionality with proper callback handling asynchronous operation + this.addWidget("button", "Paste", "Paste", () => { + navigator.clipboard.readText().then(text => { + this.inputEl.value = text; + }).catch(error => { + console.error('Failed to read clipboard contents:', error); + }); + }, { serialize: false }); + }; + + nodeType.prototype.onNodeCreated = function () { + this.populate(); + // Bind the getTextboxText to this instance + this.getTextboxText = this.getTextboxText.bind(this); + console.log(this) + + api.addEventListener('textbox', (event) => { + const data = event.detail + const outputText = this.getTextboxText(); + if (this.id == data.id) { + api.fetchApi("/textbox_string", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify( + { + node_id: data.id, + outputs: { + output: outputText + } + } + ), + }) + } + }) + } + } + }, +});