From ab9f2133ef0e17b73fe074db9dea0c8305e1751e Mon Sep 17 00:00:00 2001 From: Laurent Erignoux Date: Sun, 19 Oct 2025 21:52:03 +0800 Subject: [PATCH] Adding a node to open a generated pecha kucha presentation --- __init__.py | 4 ++ download_pecha_kucha.py | 38 +++++++++++ example_workflows/SimplePechaKucha.json | 87 +++++++++++++++++++------ web/js/download_pecha_kucha.js | 70 ++++++++++++++++++++ 4 files changed, 178 insertions(+), 21 deletions(-) create mode 100644 download_pecha_kucha.py create mode 100644 web/js/download_pecha_kucha.js diff --git a/__init__.py b/__init__.py index 8c198b5..905cfd0 100644 --- a/__init__.py +++ b/__init__.py @@ -1,13 +1,17 @@ from comfy_api.latest import ComfyExtension, io +from .download_pecha_kucha import DownloadPechaKucha from .pecha_kucha import GeneratePowerpoint from .split_prompt import SplitPrompt +WEB_DIRECTORY = "./web" + class PechaKucha(ComfyExtension): # must be declared as async async def get_node_list(self) -> list[type[io.ComfyNode]]: return [ + DownloadPechaKucha, GeneratePowerpoint, SplitPrompt, ] diff --git a/download_pecha_kucha.py b/download_pecha_kucha.py new file mode 100644 index 0000000..c4bf5e7 --- /dev/null +++ b/download_pecha_kucha.py @@ -0,0 +1,38 @@ +import logging + +from comfy_api.latest import io, ui + +log = logging.getLogger(__name__) + + +class DownloadPechaKucha(io.ComfyNode): + """ + A simple node to view/download a generated PechaKucha Powerpoint. + """ + + @classmethod + def define_schema(cls) -> io.Schema: + return io.Schema( + node_id="DownloadPechaKucha", + display_name="Download a generated PechaKucha Powerpoint", + description="Download a generated pecha kucha presentation", + category="pecha_kucha", + inputs=[ + io.String.Input( + "filename", + default="", + multiline=False, + tooltip="The path of this Presentation.", + ) + ], + is_output_node=True, + outputs=[], + ) + + @classmethod + def fingerprint_inputs(cls, filename): + return float("NaN") + + @classmethod + def execute(cls, filename): + return io.NodeOutput(filename, ui=ui.PreviewText(filename, cls=cls)) diff --git a/example_workflows/SimplePechaKucha.json b/example_workflows/SimplePechaKucha.json index 0fad6f5..9949eba 100644 --- a/example_workflows/SimplePechaKucha.json +++ b/example_workflows/SimplePechaKucha.json @@ -1,6 +1,8 @@ { - "last_node_id": 29, - "last_link_id": 31, + "id": "594262c2-cffc-4068-8fa7-d4ea47a1869d", + "revision": 0, + "last_node_id": 30, + "last_link_id": 32, "nodes": [ { "id": 19, @@ -72,10 +74,10 @@ { "name": "CONDITIONING", "type": "CONDITIONING", + "slot_index": 0, "links": [ 16 - ], - "slot_index": 0 + ] } ], "properties": { @@ -125,10 +127,10 @@ { "name": "LATENT", "type": "LATENT", + "slot_index": 0, "links": [ 19 - ], - "slot_index": 0 + ] } ], "properties": { @@ -174,10 +176,10 @@ { "name": "IMAGE", "type": "IMAGE", + "slot_index": 0, "links": [ 28 - ], - "slot_index": 0 + ] } ], "properties": { @@ -202,14 +204,13 @@ "inputs": [], "outputs": [ { - "name": "Split Prompts", + "name": "split prompt", "type": "STRING", - "shape": 6, + "slot_index": 0, "links": [ 24, 31 - ], - "slot_index": 0 + ] } ], "properties": { @@ -273,19 +274,19 @@ { "name": "MODEL", "type": "MODEL", + "slot_index": 0, "links": [ 13 - ], - "slot_index": 0 + ] }, { "name": "CLIP", "type": "CLIP", + "slot_index": 1, "links": [ 14, 15 - ], - "slot_index": 1 + ] }, { "name": "VAE", @@ -302,6 +303,39 @@ "epicrealismXL_v5Ultimate.safetensors" ] }, + { + "id": 30, + "type": "DownloadPechaKucha", + "pos": [ + 1904.9952392578125, + 915.4524536132812 + ], + "size": [ + 397.9166564941406, + 82 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "filename", + "type": "STRING", + "widget": { + "name": "filename" + }, + "link": 32 + } + ], + "outputs": [], + "properties": { + "Node name for S&R": "DownloadPechaKucha" + }, + "widgets_values": [ + "", + "" + ] + }, { "id": 27, "type": "GeneratePowerpoint", @@ -335,8 +369,10 @@ { "name": "filename", "type": "STRING", - "links": null, - "slot_index": 0 + "slot_index": 0, + "links": [ + 32 + ] } ], "properties": { @@ -438,6 +474,14 @@ 27, 1, "STRING" + ], + [ + 32, + 27, + 0, + 30, + 0, + "STRING" ] ], "groups": [], @@ -446,10 +490,11 @@ "ds": { "scale": 0.9090909090909091, "offset": [ - 812.004781836218, - -127.85274297232016 + -220.895218163782, + -490.8527429723203 ] - } + }, + "frontendVersion": "1.27.10" }, "version": 0.4 } \ No newline at end of file diff --git a/web/js/download_pecha_kucha.js b/web/js/download_pecha_kucha.js new file mode 100644 index 0000000..b75b2f5 --- /dev/null +++ b/web/js/download_pecha_kucha.js @@ -0,0 +1,70 @@ +import { app } from "/scripts/app.js"; + +app.registerExtension({ + name: "pecha_kucha.DownloadPechaKucha", + nodeCreated(node) { + if (node.comfyClass !== "DownloadPechaKucha") return; + console.log("Creating DownloadPechaKucha node."); + + const onExecuted = node.onExecuted; + + node.onExecuted = function (message) { + onExecuted?.apply(this, arguments); + + let filePath = message.text[0]; + console.log(`Powerpoint path received : ${filePath}`); + + if (!filePath) { + const msg = "Unable to get presentation File path."; + console.error(msg); + useToastStore().addAlert(msg); + } + + const modelWidget = node.widgets?.find( + (w) => w.name === "Open Presentation", + ); + + if (modelWidget) { + modelWidget.value = filePath.replaceAll("\\", "/"); + modelWidget.disabled = false; + } + }; + + const download = () => { + let filePath = ""; + + const fileNameWidget = node.widgets?.find( + (w) => w.name === "Open Presentation", + ); + if (!fileNameWidget) { + console.error("Could not find Open presentation widget."); + } + filePath = fileNameWidget.value; + if (!filePath) { + console.error(`Could not find presentation file: ${filePath}`); + } + + console.log("Downloading presentation:", filePath); + + // Remove any leading ./output/ prefix if present + const cleanFilePath = filePath.replace(/^\.\/output\//, ""); + const folderName = cleanFilePath.replace(/[^\\/]+$/, ""); + const fileNameMatch = cleanFilePath.match(/[^\\/]+$/); + const fileName = fileNameMatch ? fileNameMatch[0] : ""; + const viewUrl = `/api/view?subfolder=${encodeURIComponent(folderName)}&filename=${encodeURIComponent(fileName)}&type=output`; + + // Open the presentation in a new tab + window.open(viewUrl, "_blank"); + }; + + // Create the button widget (initially disabled until we get the presentation file available) + const downloadButton = node.addWidget( + "button", + "Open Presentation", + "", + download, + ); + downloadButton.disabled = true; + node.downloadButton = downloadButton; + }, +});