diff --git a/web/admin.html b/web/admin.html
new file mode 100644
index 0000000..8cfea6e
--- /dev/null
+++ b/web/admin.html
@@ -0,0 +1,1458 @@
+
+
+
+
+
+ PromptManager - Admin Dashboard
+
+
+
+
+
+
+
+
+
+
+
+
+ ๐
+
+
+
+ PromptManager Admin Dashboard
+
+
Manage and organize your AI prompts with powerful search and editing tools
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Recent Prompts
+ Loading...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
โ๏ธ Settings
+
+
+
+
+
+
Set to 0 to disable auto-hide. Controls how long search results stay visible in the ComfyUI node.
+
+
+
+
+
+
Choose how the Web UI opens when clicked from ComfyUI nodes.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
๐ท๏ธ Add Tags to Selected Prompts
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
๐ Set Category for Selected Prompts
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
๐ Gallery System Diagnostics
+
+
+
+
+
+
+
+
+
+
+
+
+
Generated Images
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![Generated image]()
+
+
+
+ 1 / 1
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/index.html b/web/index.html
new file mode 100644
index 0000000..f1bbb39
--- /dev/null
+++ b/web/index.html
@@ -0,0 +1,61 @@
+
+
+
+
+
+ PromptManager - Redirecting to Admin
+
+
+
+
+
+
๐ PromptManager
+
Redirecting to Admin Dashboard...
+
Loading the full-featured prompt management interface
+
+
+
+
+
\ No newline at end of file
diff --git a/web/prompt_manager.js b/web/prompt_manager.js
new file mode 100644
index 0000000..75d7af7
--- /dev/null
+++ b/web/prompt_manager.js
@@ -0,0 +1,673 @@
+// PromptManager/web/prompt_manager.js
+
+import { app } from "../../scripts/app.js";
+
+app.registerExtension({
+ name: "PromptManager.UI",
+
+ async beforeRegisterNodeDef(nodeType, nodeData, app) {
+ if (nodeData.name === "PromptManager") {
+ console.log("[PromptManager] Patching node type for custom UI");
+
+ // Store original methods
+ const onNodeCreated = nodeType.prototype.onNodeCreated;
+
+ // Override onNodeCreated to add custom UI
+ nodeType.prototype.onNodeCreated = function () {
+ if (onNodeCreated) {
+ onNodeCreated.apply(this, arguments);
+ }
+
+ // Initialize properties for search state
+ this.properties = this.properties || {};
+ this.properties.searchResults = [];
+ this.properties.selectedPromptIndex = -1;
+ this.properties.resultTimeout = 3; // Default 3 seconds
+ this.properties.showTestButton = false; // Default hide test button
+ this.properties.webuiDisplayMode = 'popup'; // Default popup mode
+ this.resultHideTimer = null;
+
+ // Load settings from API
+ this.loadSettings();
+
+ // Create DOM widget container
+ const container = document.createElement("div");
+ container.style.padding = "8px";
+ container.style.backgroundColor = "#2a2a2a";
+ container.style.borderRadius = "4px";
+ container.style.marginTop = "5px";
+
+ this.createSearchUI(container);
+
+ // Add as DOM widget
+ this.searchWidget = this.addDOMWidget(
+ "prompt_manager_search_ui",
+ "div",
+ container,
+ );
+
+ // Recompute size
+ this.size = this.computeSize();
+ this.setDirtyCanvas(true, true);
+ };
+
+ // Method to create search UI elements
+ nodeType.prototype.createSearchUI = function (container) {
+ container.innerHTML = "";
+
+ // Search controls section
+ const searchSection = document.createElement("div");
+ searchSection.style.marginBottom = "10px";
+
+ // Search buttons row
+ const buttonRow = document.createElement("div");
+ buttonRow.style.display = "flex";
+ buttonRow.style.gap = "8px";
+ buttonRow.style.marginBottom = "8px";
+
+ // Search button
+ const searchButton = document.createElement("button");
+ searchButton.textContent = "๐ Search";
+ searchButton.style.flex = "1";
+ searchButton.style.padding = "6px 12px";
+ searchButton.style.backgroundColor = "#4a90e2";
+ searchButton.style.color = "white";
+ searchButton.style.border = "none";
+ searchButton.style.borderRadius = "4px";
+ searchButton.style.cursor = "pointer";
+ searchButton.style.fontSize = "12px";
+ searchButton.addEventListener("click", () => this.performSearch());
+ buttonRow.appendChild(searchButton);
+
+ // Recent prompts button
+ const recentButton = document.createElement("button");
+ recentButton.textContent = "๐ Recent";
+ recentButton.style.flex = "1";
+ recentButton.style.padding = "6px 12px";
+ recentButton.style.backgroundColor = "#7b68ee";
+ recentButton.style.color = "white";
+ recentButton.style.border = "none";
+ recentButton.style.borderRadius = "4px";
+ recentButton.style.cursor = "pointer";
+ recentButton.style.fontSize = "12px";
+ recentButton.addEventListener("click", () => this.loadRecentPrompts());
+ buttonRow.appendChild(recentButton);
+
+ // Web UI button
+ const webUIButton = document.createElement("button");
+ webUIButton.textContent = "๐ Web UI";
+ webUIButton.style.flex = "1";
+ webUIButton.style.padding = "6px 12px";
+ webUIButton.style.backgroundColor = "#50c878";
+ webUIButton.style.color = "white";
+ webUIButton.style.border = "none";
+ webUIButton.style.borderRadius = "4px";
+ webUIButton.style.cursor = "pointer";
+ webUIButton.style.fontSize = "12px";
+ webUIButton.addEventListener("click", () => this.openWebInterface());
+ buttonRow.appendChild(webUIButton);
+
+ searchSection.appendChild(buttonRow);
+
+ // Add test API button for debugging (only if enabled in settings)
+ if (this.properties.showTestButton) {
+ const testRow = document.createElement("div");
+ testRow.style.marginBottom = "8px";
+
+ const testButton = document.createElement("button");
+ testButton.textContent = "๐ง Test API";
+ testButton.style.width = "100%";
+ testButton.style.padding = "4px 8px";
+ testButton.style.backgroundColor = "#666";
+ testButton.style.color = "white";
+ testButton.style.border = "none";
+ testButton.style.borderRadius = "3px";
+ testButton.style.cursor = "pointer";
+ testButton.style.fontSize = "10px";
+ testButton.addEventListener("click", () => this.testApiConnection());
+ testRow.appendChild(testButton);
+
+ searchSection.appendChild(testRow);
+ }
+
+ // Results section
+ const resultsSection = document.createElement("div");
+ resultsSection.style.maxHeight = "200px";
+ resultsSection.style.overflowY = "auto";
+ resultsSection.style.border = "1px solid #444";
+ resultsSection.style.borderRadius = "4px";
+ resultsSection.style.backgroundColor = "#333";
+ resultsSection.style.padding = "8px";
+ resultsSection.style.fontSize = "11px";
+ resultsSection.style.fontFamily = "monospace";
+ resultsSection.innerHTML =
+ '๐ก Click Search or Recent to find prompts
';
+
+ this.resultsSection = resultsSection;
+ searchSection.appendChild(resultsSection);
+
+ container.appendChild(searchSection);
+ };
+
+ // Method to perform search
+ nodeType.prototype.performSearch = async function () {
+ try {
+ this.resultsSection.innerHTML =
+ '๐ Searching database...
';
+
+ // Get search criteria from node widgets (only search_text available now)
+ const searchText =
+ this.widgets?.find((w) => w.name === "search_text")?.value || "";
+
+ // Validate search criteria
+ if (!searchText.trim()) {
+ this.resultsSection.innerHTML =
+ 'โ ๏ธ Please enter search text
';
+ return;
+ }
+
+ // Call the backend search function
+ const response = await this.callNodeMethod("search_prompts", {
+ search_text: searchText,
+ });
+
+ this.displayResults(response.results || []);
+
+ // Show success notification
+ if (response.results && response.results.length > 0) {
+ this.showNotification(
+ `Found ${response.results.length} prompts`,
+ "success",
+ );
+ }
+ } catch (error) {
+ console.error("[PromptManager] Search error:", error);
+ this.resultsSection.innerHTML = `โ Search error: ${error.message}
`;
+ this.showNotification(`Search failed: ${error.message}`, "error");
+ }
+ };
+
+ // Method to load recent prompts
+ nodeType.prototype.loadRecentPrompts = async function () {
+ try {
+ this.resultsSection.innerHTML =
+ '๐ Loading recent prompts...
';
+
+ const response = await this.callNodeMethod("get_recent_prompts", {
+ limit: 20,
+ });
+ this.displayResults(response.results || []);
+
+ // Show success notification
+ if (response.results && response.results.length > 0) {
+ this.showNotification(
+ `Loaded ${response.results.length} recent prompts`,
+ "success",
+ );
+ } else {
+ this.showNotification("No recent prompts found", "info");
+ }
+ } catch (error) {
+ console.error("[PromptManager] Recent prompts error:", error);
+ this.resultsSection.innerHTML = `โ Error loading recent prompts: ${error.message}
`;
+ this.showNotification(
+ `Failed to load recent prompts: ${error.message}`,
+ "error",
+ );
+ }
+ };
+
+ // Method to load settings from API
+ nodeType.prototype.loadSettings = async function () {
+ try {
+ const response = await fetch("/prompt_manager/settings");
+ if (response.ok) {
+ const data = await response.json();
+ if (data.success && data.settings) {
+ this.properties.resultTimeout = data.settings.result_timeout || 3;
+ this.properties.showTestButton =
+ data.settings.show_test_button || false;
+ this.properties.webuiDisplayMode =
+ data.settings.webui_display_mode || 'popup';
+ }
+ }
+ } catch (error) {
+ console.log(
+ "[PromptManager] Could not load settings, using defaults",
+ );
+ }
+ };
+
+ // Method to start auto-hide timer
+ nodeType.prototype.startAutoHideTimer = function () {
+ // Clear existing timer
+ if (this.resultHideTimer) {
+ clearTimeout(this.resultHideTimer);
+ }
+
+ // Only set timer if timeout > 0
+ if (this.properties.resultTimeout > 0) {
+ this.resultHideTimer = setTimeout(() => {
+ this.hideResults();
+ }, this.properties.resultTimeout * 1000);
+ }
+ };
+
+ // Method to hide results
+ nodeType.prototype.hideResults = function () {
+ if (this.resultsSection) {
+ // Completely hide the results section
+ this.resultsSection.style.display = "none";
+
+ // Create a restore button in place of the results
+ const restoreButton = document.createElement("div");
+ restoreButton.style.padding = "20px";
+ restoreButton.style.backgroundColor = "#333";
+ restoreButton.style.border = "1px solid #444";
+ restoreButton.style.borderRadius = "4px";
+ restoreButton.style.textAlign = "center";
+ restoreButton.style.cursor = "pointer";
+ restoreButton.style.color = "#999";
+ restoreButton.style.fontSize = "12px";
+ restoreButton.innerHTML =
+ "๐ Results auto-hidden
Click to restore";
+
+ // Add hover effect
+ restoreButton.addEventListener("mouseenter", () => {
+ restoreButton.style.backgroundColor = "#444";
+ restoreButton.style.color = "#ccc";
+ });
+ restoreButton.addEventListener("mouseleave", () => {
+ restoreButton.style.backgroundColor = "#333";
+ restoreButton.style.color = "#999";
+ });
+
+ // Click to restore
+ restoreButton.addEventListener("click", () => {
+ this.showResults();
+ });
+
+ // Store reference and insert after results section
+ this.restoreButton = restoreButton;
+ this.resultsSection.parentNode.insertBefore(
+ restoreButton,
+ this.resultsSection.nextSibling,
+ );
+ }
+ };
+
+ // Method to show results
+ nodeType.prototype.showResults = function () {
+ if (this.resultsSection) {
+ // Show the results section
+ this.resultsSection.style.display = "block";
+
+ // Remove restore button if it exists
+ if (this.restoreButton && this.restoreButton.parentNode) {
+ this.restoreButton.parentNode.removeChild(this.restoreButton);
+ this.restoreButton = null;
+ }
+
+ // Restart timer
+ this.startAutoHideTimer();
+ }
+ };
+
+ // Method to display search results
+ nodeType.prototype.displayResults = function (results) {
+ if (!results || results.length === 0) {
+ this.resultsSection.innerHTML =
+ '๐ญ No prompts found
';
+ return;
+ }
+
+ this.properties.searchResults = results;
+ this.resultsSection.innerHTML = "";
+
+ // Ensure results are visible
+ this.resultsSection.style.opacity = "1";
+ this.resultsSection.style.pointerEvents = "auto";
+
+ // Header
+ const header = document.createElement("div");
+ header.style.marginBottom = "10px";
+ header.style.fontWeight = "bold";
+ header.style.color = "#4facfe";
+ header.style.textAlign = "center";
+ header.style.padding = "8px";
+ header.style.backgroundColor = "rgba(79, 172, 254, 0.1)";
+ header.style.borderRadius = "4px";
+ header.innerHTML = `๐ Found ${results.length} prompt${results.length === 1 ? "" : "s"}`;
+ this.resultsSection.appendChild(header);
+
+ // Start auto-hide timer
+ this.startAutoHideTimer();
+
+ // Results list
+ results.forEach((result, index) => {
+ const resultItem = document.createElement("div");
+ resultItem.style.padding = "6px";
+ resultItem.style.marginBottom = "4px";
+ resultItem.style.backgroundColor = "#444";
+ resultItem.style.borderRadius = "3px";
+ resultItem.style.cursor = "pointer";
+ resultItem.style.border = "1px solid transparent";
+
+ // Hover effect
+ resultItem.addEventListener("mouseenter", () => {
+ resultItem.style.backgroundColor = "#555";
+ resultItem.style.border = "1px solid #666";
+ });
+ resultItem.addEventListener("mouseleave", () => {
+ resultItem.style.backgroundColor = "#444";
+ resultItem.style.border = "1px solid transparent";
+ });
+
+ // Click to use prompt
+ resultItem.addEventListener("click", () => {
+ this.usePrompt(result, index);
+ });
+
+ // Format display
+ const tags = Array.isArray(result.tags)
+ ? result.tags.join(", ")
+ : "No tags";
+ const rating = result.rating ? `โญ${result.rating}` : "No rating";
+ const category = result.category || "No category";
+ const created = result.created_at
+ ? new Date(result.created_at).toLocaleDateString()
+ : "Unknown";
+
+ const promptPreview =
+ result.text.length > 80
+ ? result.text.substring(0, 80) + "..."
+ : result.text;
+
+ resultItem.innerHTML = `
+
+ ${promptPreview}
+
+
+
+ ๐ ${category} โข
+ ${rating} โข
+ ๐
${created}
+
+
+ ๐ท๏ธ ${tags}
+
+
+ `;
+
+ this.resultsSection.appendChild(resultItem);
+ });
+ };
+
+ // Method to use a selected prompt
+ nodeType.prototype.usePrompt = function (prompt, index) {
+ // Find the text widget and set its value
+ const textWidget = this.widgets?.find((w) => w.name === "text");
+ if (textWidget) {
+ textWidget.value = prompt.text;
+
+ // Also set metadata if available
+ const categoryWidget = this.widgets?.find(
+ (w) => w.name === "category",
+ );
+ if (categoryWidget && prompt.category) {
+ categoryWidget.value = prompt.category;
+ }
+
+ const tagsWidget = this.widgets?.find((w) => w.name === "tags");
+ if (tagsWidget && prompt.tags) {
+ tagsWidget.value = Array.isArray(prompt.tags)
+ ? prompt.tags.join(", ")
+ : prompt.tags;
+ }
+
+ // Visual feedback
+ this.highlightSelectedResult(index);
+
+ // Trigger widget change events
+ this.setDirtyCanvas(true, true);
+
+ console.log(
+ `[PromptManager] Loaded prompt: ${prompt.text.substring(0, 50)}...`,
+ );
+ }
+ };
+
+ // Method to highlight selected result
+ nodeType.prototype.highlightSelectedResult = function (index) {
+ const resultItems = this.resultsSection.querySelectorAll(
+ "div[style*='cursor: pointer']",
+ );
+ resultItems.forEach((item, i) => {
+ if (i === index) {
+ item.style.backgroundColor = "#2a5d31";
+ item.style.border = "1px solid #4a8c57";
+ } else {
+ item.style.backgroundColor = "#444";
+ item.style.border = "1px solid transparent";
+ }
+ });
+ };
+
+ // Method to call backend API endpoints
+ nodeType.prototype.callNodeMethod = async function (method, params) {
+ try {
+ let url,
+ options = { method: "GET" };
+
+ if (method === "search_prompts") {
+ // Build query string for search (simplified - only text search)
+ const queryParams = new URLSearchParams();
+ if (params.search_text)
+ queryParams.append("text", params.search_text);
+ queryParams.append("limit", "50");
+
+ url = `/prompt_manager/search?${queryParams.toString()}`;
+ } else if (method === "get_recent_prompts") {
+ const limit = params.limit || 20;
+ url = `/prompt_manager/recent?limit=${limit}`;
+ } else {
+ throw new Error(`Unknown method: ${method}`);
+ }
+
+ const response = await fetch(url, options);
+
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+ }
+
+ const data = await response.json();
+
+ if (!data.success) {
+ throw new Error(data.error || "API call failed");
+ }
+
+ return { results: data.results || [] };
+ } catch (error) {
+ console.error(
+ `[PromptManager] API call failed for ${method}:`,
+ error,
+ );
+ throw error;
+ }
+ };
+
+ // Method to save a prompt via API
+ nodeType.prototype.savePromptToDatabase = async function (promptData) {
+ try {
+ const response = await fetch("/prompt_manager/save", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(promptData),
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+ }
+
+ const data = await response.json();
+
+ if (!data.success) {
+ throw new Error(data.error || "Save failed");
+ }
+
+ return data;
+ } catch (error) {
+ console.error("[PromptManager] Save prompt failed:", error);
+ throw error;
+ }
+ };
+
+ // Method to delete a prompt via API
+ nodeType.prototype.deletePrompt = async function (promptId) {
+ try {
+ const response = await fetch(`/prompt_manager/delete/${promptId}`, {
+ method: "DELETE",
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+ }
+
+ const data = await response.json();
+
+ if (!data.success) {
+ throw new Error(data.error || "Delete failed");
+ }
+
+ return data;
+ } catch (error) {
+ console.error("[PromptManager] Delete prompt failed:", error);
+ throw error;
+ }
+ };
+
+ // Method to test API connection
+ nodeType.prototype.testApiConnection = async function () {
+ try {
+ this.resultsSection.innerHTML =
+ '๐ง Testing API connection...
';
+
+ const response = await fetch("/prompt_manager/test");
+
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+ }
+
+ const data = await response.json();
+
+ if (data.success) {
+ this.resultsSection.innerHTML = `
+
+ โ
API Connection Successful!
+ ${data.message}
+ Time: ${data.timestamp}
+
+ `;
+ this.showNotification("API connection test passed!", "success");
+ } else {
+ throw new Error(data.message || "Test failed");
+ }
+ } catch (error) {
+ console.error("[PromptManager] API test failed:", error);
+ this.resultsSection.innerHTML = `
+
+ โ API Connection Failed
+ ${error.message}
+
+ `;
+ this.showNotification(`API test failed: ${error.message}`, "error");
+ }
+ };
+
+ // Method to open web interface
+ nodeType.prototype.openWebInterface = function () {
+ try {
+ // Open the web interface served via our API endpoint
+ const currentOrigin = window.location.origin;
+ const webUrl = `${currentOrigin}/prompt_manager/web`;
+
+ if (this.properties.webuiDisplayMode === 'newtab') {
+ // Open in new tab without window options
+ const newWindow = window.open(webUrl, "_blank");
+
+ if (newWindow) {
+ console.log("[PromptManager] Web interface opened in new tab");
+ this.showNotification("Web interface opened in new tab", "success");
+ } else {
+ // Fallback if popup was blocked
+ console.log("[PromptManager] Popup blocked, trying alternative method");
+ window.location.href = webUrl;
+ }
+ } else {
+ // Default popup mode with specific dimensions
+ const newWindow = window.open(
+ webUrl,
+ "_blank",
+ "width=1200,height=800,scrollbars=yes,resizable=yes",
+ );
+
+ if (newWindow) {
+ console.log("[PromptManager] Web interface opened in popup");
+ this.showNotification("Web interface opened in popup", "success");
+ } else {
+ // Fallback if popup was blocked
+ console.log("[PromptManager] Popup blocked, trying alternative method");
+ window.location.href = webUrl;
+ }
+ }
+ } catch (error) {
+ console.error("[PromptManager] Error opening web interface:", error);
+ this.showNotification(
+ `Web interface error: ${error.message}`,
+ "error",
+ );
+ }
+ };
+
+ // Method to show notifications
+ nodeType.prototype.showNotification = function (message, type = "info") {
+ // Create a temporary notification element
+ const notification = document.createElement("div");
+ notification.style.position = "fixed";
+ notification.style.top = "20px";
+ notification.style.right = "20px";
+ notification.style.padding = "10px 15px";
+ notification.style.borderRadius = "4px";
+ notification.style.color = "white";
+ notification.style.fontSize = "12px";
+ notification.style.zIndex = "10000";
+ notification.style.maxWidth = "300px";
+ notification.textContent = message;
+
+ // Set color based on type
+ switch (type) {
+ case "success":
+ notification.style.backgroundColor = "#4caf50";
+ break;
+ case "error":
+ notification.style.backgroundColor = "#f44336";
+ break;
+ case "warning":
+ notification.style.backgroundColor = "#ff9800";
+ break;
+ default:
+ notification.style.backgroundColor = "#2196f3";
+ }
+
+ document.body.appendChild(notification);
+
+ // Remove after 3 seconds
+ setTimeout(() => {
+ document.body.removeChild(notification);
+ }, 3000);
+ };
+ }
+ },
+});