diff --git a/__init__.py b/__init__.py index 7121e6e..f20ad32 100644 --- a/__init__.py +++ b/__init__.py @@ -1,11 +1,11 @@ -""" -Animagine Prompt Node for ComfyUI +"""Animagine Prompt Node for ComfyUI A node designed to help structure prompts according to specific guidelines """ import os import logging from .animagine_node import AnimaginePromptNode from .wildcards_node import MultilineTextInput, TextFileLoader, MultiWildcardLoader +from .api_routes import define_routes # Basic logging configuration logging.basicConfig( @@ -22,7 +22,7 @@ SAMPLE_DATA_DIR = os.path.join(NODE_DIR, "sample_data") if not os.path.exists(SAMPLE_DATA_DIR): os.makedirs(SAMPLE_DATA_DIR) -# Mapping for node registration +# Node class mappings for registration NODE_CLASS_MAPPINGS = { "AnimaginePrompt": AnimaginePromptNode, "MultilineTextInput": MultilineTextInput, @@ -30,7 +30,7 @@ NODE_CLASS_MAPPINGS = { "MultiWildcardLoader": MultiWildcardLoader } -# Mapping for display name +# Display name mappings for nodes NODE_DISPLAY_NAME_MAPPINGS = { "AnimaginePrompt": "Animagine Prompt", "MultilineTextInput": "Multiline Text Input", @@ -38,5 +38,11 @@ NODE_DISPLAY_NAME_MAPPINGS = { "MultiWildcardLoader": "Multi-Wildcard Loader" } +# Web directory configuration for JavaScript extensions +WEB_DIRECTORY = "./web" -__version__ = "1.5.0" +# Register API routes directly +define_routes() +logger.info("[Animagine Prompt] API routes initialized") + +__version__ = "1.6.0" diff --git a/animagine_node.py b/animagine_node.py index 1368184..c14a1dc 100644 --- a/animagine_node.py +++ b/animagine_node.py @@ -165,6 +165,49 @@ class AnimaginePromptNode: return None + def reload_csv(self, csv_path: str) -> Dict[str, Any]: + """ + Reload CSV file by clearing cache and forcing reload + + Args: + csv_path (str): Path to CSV file to reload + + Returns: + Dict[str, Any]: Result with success status and info + """ + try: + # Clear the cache for this specific file + if csv_path in self.csv_handler.cache: + del self.csv_handler.cache[csv_path] + logger.logger.info(f"Cleared cache for CSV: {csv_path}") + + # Reset last_csv_path to force reload + if self.last_csv_path == csv_path: + self.last_csv_path = None + + # Force reload the CSV + df = self.csv_handler.load_csv(csv_path) + if df is not None: + logger.logger.info(f"Successfully reloaded CSV: {csv_path} with {len(df)} rows") + return { + "success": True, + "rows": len(df), + "message": f"CSV reloaded successfully with {len(df)} rows" + } + else: + logger.logger.error(f"Failed to reload CSV: {csv_path}") + return { + "success": False, + "error": "Failed to load CSV file" + } + + except Exception as e: + logger.log_error(e, "CSV Reload", {"csv_path": csv_path}) + return { + "success": False, + "error": str(e) + } + def generate_prompt( self, positive_prompt: str, diff --git a/api_routes.py b/api_routes.py new file mode 100644 index 0000000..ea6f96c --- /dev/null +++ b/api_routes.py @@ -0,0 +1,68 @@ +"""API routes for Animagine Prompt Node""" + +import json +from aiohttp import web +from server import PromptServer +from .animagine_node import AnimaginePromptNode +from .logger import logger + +# Global instance for API calls +_animagine_instance = None + +def get_animagine_instance(): + """Get or create AnimaginePromptNode instance for API calls""" + global _animagine_instance + if _animagine_instance is None: + _animagine_instance = AnimaginePromptNode() + return _animagine_instance + +async def reload_csv_endpoint(request): + """ + Endpoint to reload CSV files + + POST /animagine/reload_csv + Body: {"csv_path": "path/to/file.csv"} + """ + try: + # Get request data + data = await request.json() + csv_path = data.get('csv_path') + + if not csv_path: + return web.json_response({ + 'success': False, + 'error': 'csv_path is required' + }, status=400) + + # Get node instance and reload CSV + animagine_node = get_animagine_instance() + result = animagine_node.reload_csv(csv_path) + + logger.logger.info(f"CSV reload API call: {csv_path} - Success: {result['success']}") + + # Return result + status_code = 200 if result['success'] else 400 + return web.json_response(result, status=status_code) + + except json.JSONDecodeError: + return web.json_response({ + 'success': False, + 'error': 'Invalid JSON in request body' + }, status=400) + + except Exception as e: + logger.log_error(e, "CSV Reload API", {"request_data": str(data) if 'data' in locals() else None}) + return web.json_response({ + 'success': False, + 'error': f'Internal server error: {str(e)}' + }, status=500) + +def define_routes(): + """ + Define API routes using PromptServer.instance.routes + """ + @PromptServer.instance.routes.post('/animagine/reload_csv') + async def reload_csv_route(request): + return await reload_csv_endpoint(request) + + logger.logger.info("Registered API routes for Animagine Prompt: /animagine/reload_csv") \ No newline at end of file diff --git a/example_workflow/example_workflow.json b/example_workflow/example_workflow.json index c23bad3..de1d03e 100644 --- a/example_workflow/example_workflow.json +++ b/example_workflow/example_workflow.json @@ -208,7 +208,7 @@ ], "size": [ 400, - 474 + 503 ], "flags": {}, "order": 0, @@ -252,6 +252,7 @@ "low quality, worst quality", "average score, bad score, low score", false, + 2024, 2024 ] }, @@ -340,7 +341,7 @@ ], "size": [ 400, - 540 + 564 ], "flags": {}, "order": 2, @@ -386,6 +387,7 @@ -1, "", true, + -1, -1 ] }, @@ -483,16 +485,16 @@ "ds": { "scale": 0.8264462809917354, "offset": [ - 76.06999999999984, - 30.500000000000014 + 110, + -115.26971435546875 ] }, + "frontendVersion": "1.23.4", "node_versions": { "ComfyUI-Jjk-Nodes": "b3c99bb78a99551776b5eab1a820e1cd58f84f31", "ComfyUI-Animagine-prompt": "4f0855ce259ac48ae59468d4ce5a8b726a8b2923" }, - "ue_links": [], - "frontendVersion": "1.23.4" + "ue_links": [] }, "version": 0.4 } \ No newline at end of file diff --git a/example_workflow/example_workflow.png b/example_workflow/example_workflow.png index 1b1093c..84a6701 100644 Binary files a/example_workflow/example_workflow.png and b/example_workflow/example_workflow.png differ diff --git a/pyproject.toml b/pyproject.toml index 599095a..d986935 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "animagine-prompt" description = "Comfy UI node to prompt build for https://huggingface.co/cagliostrolab/animagine-xl-4.0 model " -version = "1.5.3" +version = "1.6.0" license = {file = "LICENSE"} [project.urls] diff --git a/web/animagine_reload.js b/web/animagine_reload.js new file mode 100644 index 0000000..d4e987e --- /dev/null +++ b/web/animagine_reload.js @@ -0,0 +1,91 @@ +import { app } from "../../scripts/app.js"; +import { api } from "../../scripts/api.js"; + +/** + * Extension to add CSV reload button to AnimaginePrompt node + */ +app.registerExtension({ + name: "AnimaginePrompt.ReloadCSV", + + async beforeRegisterNodeDef(nodeType, nodeData, app) { + // Only apply to our AnimaginePrompt node + if (nodeData.name === "AnimaginePrompt") { + + // Function to reload CSV + const reloadCSV = async (node) => { + try { + // Get current csv_path value + const csvPathWidget = node.widgets.find(w => w.name === "csv_path"); + if (!csvPathWidget || !csvPathWidget.value) { + alert("No CSV path specified"); + return; + } + + // Get reload button to update its text + const reloadButton = node.widgets.find(w => w.name === "🔄 Reload CSV" || w.name.startsWith("🔄 Reload CSV")); + + // Call reload endpoint + const response = await api.fetchApi("/animagine/reload_csv", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + csv_path: csvPathWidget.value + }) + }); + + if (response.ok) { + const result = await response.json(); + if (result.success) { + console.log(`CSV reloaded successfully. Rows: ${result.rows}`); + // Update button text with row count + if (reloadButton) { + reloadButton.name = `🔄 Reload CSV (${result.rows})`; + } + node.setDirtyCanvas(true, true); + } else { + alert(`Error reloading CSV: ${result.error}`); + } + } else { + alert("Connection error while reloading CSV"); + } + + } catch (error) { + console.error("Error reloading CSV:", error); + alert("Error reloading CSV: " + error.message); + } + }; + + // Function executed when node is created + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function() { + const result = onNodeCreated?.apply(this, arguments); + + // Add reload button + const reloadButton = this.addWidget( + "button", + "🔄 Reload CSV", + null, + () => reloadCSV(this), + { + serialize: false + } + ); + + // Button styling + reloadButton.computeSize = () => [120, 25]; + + // Reorder widgets: move button after csv_path + const csvPathIndex = this.widgets.findIndex(w => w.name === "csv_path"); + if (csvPathIndex !== -1) { + const buttonIndex = this.widgets.length - 1; + const button = this.widgets.splice(buttonIndex, 1)[0]; + this.widgets.splice(csvPathIndex + 1, 0, button); + } + + return result; + }; + } + } +}); \ No newline at end of file