// Workflow list management let workflowsState = { workflows: [], offset: 0, limit: 20, loading: false, hasMore: true, initialized: false, currentSearch: "", }; // Make workflowsState accessible globally window.workflowsState = workflowsState; // Helper: ensure a `ComfyDeploy` node exists and is updated with correct version function ensureComfyDeployNode({ name, id, version }) { if (!window.app || !window.app.graph) { console.warn("ComfyDeploy: App or graph not available"); return; } const graph = window.app.graph; let nodes = graph.findNodesByType("ComfyDeploy"); graph.beforeChange(); if (nodes.length === 0) { // Create new ComfyDeploy node console.log("ComfyDeploy: Creating new ComfyDeploy node", { name, id, version, }); const node = LiteGraph.createNode("ComfyDeploy"); node.configure({ widgets_values: [name || "", id || "", version || 1] }); node.pos = [0, 0]; graph.add(node); } else { // Update existing node - always sync all values to ensure consistency const node = nodes[0]; const currentValues = node.widgets_values || []; const newValues = [ name || currentValues[0] || "", id || currentValues[1] || "", version || currentValues[2] || 1, ]; // Check if any values changed const valuesChanged = JSON.stringify(currentValues) !== JSON.stringify(newValues); if (valuesChanged) { console.log("ComfyDeploy: Updating ComfyDeploy node", { from: { name: currentValues[0], id: currentValues[1], version: currentValues[2], }, to: { name: newValues[0], id: newValues[1], version: newValues[2] }, }); node.widgets_values = newValues; // Force widget updates if they exist if (node.widgets) { node.widgets.forEach((widget, index) => { if (widget && newValues[index] !== undefined) { widget.value = newValues[index]; } }); } } else { console.log("ComfyDeploy: ComfyDeploy node already up to date", { name, id, version, }); } } graph.afterChange(); } // Helper: automatically ensure ComfyDeploy node after workflow loading async function ensureComfyDeployNodeAfterLoad( { name, id, version }, delayMs = 200 ) { // Wait for workflow to be fully loaded await new Promise((resolve) => setTimeout(resolve, delayMs)); // Ensure the ComfyDeploy node is present and correct ensureComfyDeployNode({ name, id, version }); // Also refresh the workflow card if it's visible if (window.refreshCurrentWorkflowCard) { setTimeout(() => { window.refreshCurrentWorkflowCard(id, version); }, 100); } } function getComfyDeployMeta() { if (!window.app || !window.app.graph) return null; const graph = window.app.graph; const nodes = graph.findNodesByType("ComfyDeploy"); if (nodes.length === 0) return null; const v = nodes[0].widgets_values || []; const meta = { name: v[0], id: v[1], version: v[2] }; console.log("Current ComfyDeploy meta:", meta); return meta; } async function fetchWorkflowVersions( getData, workflowId, offset = 0, limit = 20, search = "" ) { const data = getData(); const params = new URLSearchParams({ workflow_id: workflowId, offset: String(offset), limit: String(limit), api_url: data.apiUrl || "https://api.comfydeploy.com", ...(search && { search }), }); const res = await fetch(`/comfyui-deploy/workflow/versions?${params}`, { method: "GET", headers: { Authorization: `Bearer ${data.apiKey}`, "Content-Type": "application/json", }, }); if (!res.ok) return []; const versions = await res.json(); return Array.isArray(versions) ? versions : []; } async function fetchWorkflowVersionData(getData, workflowId, version) { const data = getData(); const params = new URLSearchParams({ workflow_id: workflowId, version: String(version), api_url: data.apiUrl || "https://api.comfydeploy.com", }); const res = await fetch(`/comfyui-deploy/workflow/version?${params}`, { method: "GET", headers: { Authorization: `Bearer ${data.apiKey}`, "Content-Type": "application/json", }, }); if (!res.ok) throw new Error(`Failed to fetch workflow version ${version}`); return await res.json(); } // Shared function to create workflow card HTML template function createWorkflowCardHTML(workflowName, version) { return `
Current Workflow

${workflowName}

v${version}
`; } function renderCurrentWorkflowCard(element, getData) { const container = element.querySelector("#workflows-container"); if (!container) return; // Remove old card if any const old = container.querySelector("#cd-current-workflow-card"); if (old) old.remove(); const meta = getComfyDeployMeta(); if (!meta || !meta.id) { console.log("No ComfyDeploy meta found, not showing workflow card"); return; // Only show when node exists } console.log("Rendering workflow card with meta:", meta); const card = document.createElement("div"); card.id = "cd-current-workflow-card"; card.style.cssText = ` margin: 8px 0 16px 0; padding: 16px; border: 1px solid #374151; border-radius: 12px; background: linear-gradient(135deg, #1f2937 0%, #111827 100%); color: #f9fafb; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); transition: all 0.2s ease; position: relative; `; // Add hover effect card.addEventListener("mouseenter", () => { card.style.transform = "translateY(-1px)"; card.style.boxShadow = "0 8px 20px rgba(0, 0, 0, 0.5)"; card.style.borderColor = "#4b5563"; }); card.addEventListener("mouseleave", () => { card.style.transform = "translateY(0)"; card.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.4)"; card.style.borderColor = "#374151"; }); // Use the shared template card.innerHTML = createWorkflowCardHTML( meta.name || "Unnamed Workflow", meta.version || 1 ); // Add custom scrollbar styles const scrollbarStyle = document.createElement("style"); scrollbarStyle.textContent = ` #cd-version-list::-webkit-scrollbar { width: 6px; } #cd-version-list::-webkit-scrollbar-track { background: transparent; } #cd-version-list::-webkit-scrollbar-thumb { background: #4b5563; border-radius: 3px; } #cd-version-list::-webkit-scrollbar-thumb:hover { background: #6b7280; } `; document.head.appendChild(scrollbarStyle); const btn = card.querySelector("#cd-version-btn"); const panel = card.querySelector("#cd-version-panel"); const dropdown = card.querySelector("#cd-version-dropdown"); const list = card.querySelector("#cd-version-list"); const loading = card.querySelector("#cd-version-loading"); const state = { offset: 0, limit: 20, loading: false, hasMore: true, isOpen: false, }; // Close dropdown when clicking outside const handleClickOutside = (event) => { if (!card.contains(event.target)) { closeDropdown(); } }; const openDropdown = () => { state.isOpen = true; panel.style.display = "block"; btn.querySelector("svg").style.transform = "rotate(180deg)"; document.addEventListener("click", handleClickOutside); // Add opening animation dropdown.style.opacity = "0"; dropdown.style.transform = "translateY(-8px)"; setTimeout(() => { dropdown.style.transition = "opacity 0.2s ease, transform 0.2s ease"; dropdown.style.opacity = "1"; dropdown.style.transform = "translateY(0)"; }, 10); }; const closeDropdown = () => { state.isOpen = false; panel.style.display = "none"; btn.querySelector("svg").style.transform = "rotate(0deg)"; document.removeEventListener("click", handleClickOutside); }; async function loadMore() { if (state.loading || !state.hasMore) return; state.loading = true; loading.style.display = "block"; try { const items = await fetchWorkflowVersions( getData, meta.id, state.offset, state.limit ); if (items.length === 0) { state.hasMore = false; // If this is the first load and no items, show a message if (state.offset === 0) { const noVersionsMsg = document.createElement("div"); noVersionsMsg.style.cssText = ` padding: 16px; text-align: center; color: #9ca3af; font-size: 12px; `; noVersionsMsg.textContent = "No versions found"; list.appendChild(noVersionsMsg); } } else { items.forEach((v, index) => { const row = document.createElement("div"); const isCurrentVersion = v.version === meta.version; row.style.cssText = ` padding: 12px 16px; border-bottom: 1px solid #374151; cursor: pointer; transition: all 0.15s ease; display: flex; justify-content: space-between; align-items: center; ${ isCurrentVersion ? "background: rgba(59, 130, 246, 0.1); border-left: 3px solid #3b82f6;" : "" } `; row.innerHTML = `
v${v.version} ${isCurrentVersion ? "(Current)" : ""}
${ v.comment ? `
${v.comment}
` : "" }
${ isCurrentVersion ? '
✓
' : "" } `; if (!isCurrentVersion) { row.addEventListener("mouseenter", () => { row.style.background = "#374151"; }); row.addEventListener("mouseleave", () => { row.style.background = "transparent"; }); } row.onclick = async () => { if (isCurrentVersion) return; try { // Add loading state to the row row.style.opacity = "0.6"; row.style.cursor = "wait"; const data = await fetchWorkflowVersionData( getData, meta.id, v.version ); if (data?.workflow) { window.app.loadGraphData(data.workflow); // Ensure ComfyDeploy node is correct and refresh UI await ensureComfyDeployNodeAfterLoad({ name: meta.name, id: meta.id, version: v.version, }); renderCurrentWorkflowCard(element, getData); window.app.extensionManager.toast.add({ severity: "success", summary: "Version loaded", detail: `Loaded v${v.version}${ v.comment ? ` - ${v.comment}` : "" }`, life: 3000, }); closeDropdown(); } } catch (e) { row.style.opacity = "1"; row.style.cursor = "pointer"; window.app.extensionManager.toast.add({ severity: "error", summary: "Failed to load version", detail: e.message, life: 4000, }); } }; list.appendChild(row); }); state.offset += items.length; } } catch (error) { console.error("Error loading versions:", error); // Show error message in dropdown if it's the first load if (state.offset === 0) { const errorMsg = document.createElement("div"); errorMsg.style.cssText = ` padding: 16px; text-align: center; color: #ef4444; font-size: 12px; line-height: 1.4; `; errorMsg.innerHTML = `
Failed to load versions
Check your connection and try again
`; list.appendChild(errorMsg); } // Don't set hasMore to false on error, allow retry } finally { state.loading = false; loading.style.display = "none"; } } btn.onclick = async (e) => { e.stopPropagation(); if (state.isOpen) { closeDropdown(); } else { openDropdown(); // Always clear and reload to get fresh data list.innerHTML = ""; // Reset state for fresh load state.offset = 0; state.hasMore = true; state.loading = false; await loadMore(); } }; list.addEventListener("scroll", () => { if (list.scrollTop + list.clientHeight >= list.scrollHeight - 10) { loadMore(); } }); // Populate current comment (async () => { try { const vdata = await fetchWorkflowVersionData( getData, meta.id, meta.version ); const commentEl = card.querySelector("#cd-current-comment"); if (commentEl && vdata?.comment) { commentEl.style.display = "block"; commentEl.textContent = vdata.comment; } } catch (error) { console.warn( `Could not fetch version ${meta.version} data:`, error.message ); // Don't show error to user for comment fetching, just log it const commentEl = card.querySelector("#cd-current-comment"); if (commentEl) { commentEl.style.display = "none"; } } })(); const searchEl = container.querySelector("#cd-search-container"); if (searchEl) { container.insertBefore(card, searchEl); } else { container.prepend(card); } } async function fetchWorkflows(getData, offset = 0, limit = 20, search = "") { try { const data = getData(); if (!data.apiKey) { throw new Error("API key not configured"); } const params = new URLSearchParams({ offset: offset.toString(), limit: limit.toString(), api_url: data.apiUrl || "https://api.comfydeploy.com", ...(search && { search }), }); const response = await fetch(`/comfyui-deploy/workflows?${params}`, { method: "GET", headers: { Authorization: `Bearer ${data.apiKey}`, "Content-Type": "application/json", }, }); if (!response.ok) { throw new Error(`Failed to fetch workflows: ${response.status}`); } const result = await response.json(); console.log("result", result); return Array.isArray(result) ? result : []; } catch (error) { console.error("Error fetching workflows:", error); return []; } } function createWorkflowItem(workflow, getTimeAgo, getData) { const li = document.createElement("li"); let loadingToast = null; li.style.cssText = ` border-bottom: 1px solid #444; background: transparent; transition: all 0.2s ease; cursor: pointer; `; li.addEventListener("mouseenter", () => { li.style.background = "#333"; }); li.addEventListener("mouseleave", () => { li.style.background = "transparent"; }); // Add click handler to fetch and load workflow data li.addEventListener("click", async () => { try { const data = getData(); if (!data.apiKey) { console.error("No API key configured"); return; } // Show loading toast loadingToast = window.app.extensionManager.toast.add({ severity: "info", summary: "Loading workflow...", detail: `Loading "${workflow.name}"`, life: 3000, }); const params = new URLSearchParams({ workflow_id: workflow.id, api_url: data.apiUrl || "https://api.comfydeploy.com", }); const response = await fetch(`/comfyui-deploy/workflow?${params}`, { method: "GET", headers: { Authorization: `Bearer ${data.apiKey}`, "Content-Type": "application/json", }, }); if (!response.ok) { throw new Error(`Failed to fetch workflow: ${response.status}`); } const workflowData = await response.json(); console.log("Workflow data:", workflowData); // Load the workflow into the graph if (workflowData.versions && workflowData.versions.length > 0) { const latestVersion = workflowData.versions[0]; if (latestVersion.workflow && window.app) { // Load the workflow window.app.loadGraphData(latestVersion.workflow); // Ensure ComfyDeploy node is correct with latest version await ensureComfyDeployNodeAfterLoad({ name: workflow.name, id: workflow.id, version: latestVersion.version, }); // Show success toast window.app.extensionManager.toast.add({ severity: "success", summary: "Workflow loaded successfully", detail: `Loaded "${workflow.name}" v${latestVersion.version}`, life: 3000, }); // Re-render header card to reflect newly selected workflow renderCurrentWorkflowCard(document, getData); } } } catch (error) { console.error("Error loading workflow:", error); // Show error toast window.app.extensionManager.toast.add({ severity: "error", summary: "Failed to load workflow", detail: error.message, life: 5000, }); } finally { if (loadingToast) { loadingToast.close(); } } }); const updatedDate = new Date(workflow.updated_at); const timeAgo = getTimeAgo(updatedDate); li.innerHTML = `
${ workflow.cover_image ? `` : `
${workflow.name.charAt(0).toUpperCase()}
` }

${workflow.name}

${ workflow.pinned ? `📌` : "" }
${ workflow.description ? `

${workflow.description}

` : "" }
${workflow.user_name} • Updated ${timeAgo}
`; return li; } async function loadMoreWorkflows(element, getData, getTimeAgo) { if (workflowsState.loading || !workflowsState.hasMore) return; workflowsState.loading = true; const workflowsList = element.querySelector("#workflows-list"); const workflowsLoading = element.querySelector("#workflows-loading"); // Show loading indicator workflowsLoading.style.display = "flex"; try { const newWorkflows = await fetchWorkflows( getData, workflowsState.offset, workflowsState.limit, workflowsState.currentSearch ); if (newWorkflows.length === 0) { workflowsState.hasMore = false; } else { workflowsState.workflows.push(...newWorkflows); workflowsState.offset += newWorkflows.length; // Render new workflow items newWorkflows.forEach((workflow) => { const workflowItem = createWorkflowItem(workflow, getTimeAgo, getData); workflowsList.appendChild(workflowItem); }); } } catch (error) { console.error("Error loading more workflows:", error); } finally { workflowsState.loading = false; workflowsLoading.style.display = "none"; } } function setupInfiniteScroll(container, element, getData, getTimeAgo) { let isScrolling = false; container.addEventListener("scroll", () => { if (isScrolling) return; const { scrollTop, scrollHeight, clientHeight } = container; // Load more when scrolled to bottom (with 100px threshold) if (scrollTop + clientHeight >= scrollHeight - 100) { isScrolling = true; loadMoreWorkflows(element, getData, getTimeAgo).finally(() => { isScrolling = false; }); } }); } async function initializeWorkflowsList(element, getData, getTimeAgo) { const workflowsContainer = element.querySelector("#workflows-container"); const workflowsList = element.querySelector("#workflows-list"); const workflowsLoading = element.querySelector("#workflows-loading"); // Check if already initialized AND the DOM elements still exist if ( workflowsState.initialized && workflowsList && workflowsList.children.length > 0 ) return; try { // Reset state (always reset when reinitializing) workflowsState = { workflows: [], offset: 0, limit: 20, loading: false, hasMore: true, initialized: true, currentSearch: "", }; // Clear existing content in case of reinitialization if (workflowsList) { workflowsList.innerHTML = ""; } // Show container and loading workflowsContainer.style.display = "block"; workflowsLoading.style.display = "flex"; // Style the workflows list for full height scrolling workflowsList.style.cssText = ` list-style-type: none; padding: 0; margin: 0; height: calc(100vh - 650px); overflow-y: auto; scrollbar-width: thin; scrollbar-color: #666 transparent; border-top: 1px solid #444; `; // Add webkit scrollbar styles const style = document.createElement("style"); style.textContent = ` #workflows-list::-webkit-scrollbar { width: 6px; } #workflows-list::-webkit-scrollbar-track { background: transparent; } #workflows-list::-webkit-scrollbar-thumb { background: #666; border-radius: 3px; } #workflows-list::-webkit-scrollbar-thumb:hover { background: #777; } `; document.head.appendChild(style); // Setup infinite scroll setupInfiniteScroll(workflowsList, element, getData, getTimeAgo); // Load initial workflows await loadMoreWorkflows(element, getData, getTimeAgo); // Render current workflow card if a ComfyDeploy node exists try { renderCurrentWorkflowCard(element, getData); } catch (e) { console.error("Render current workflow card failed", e); } // Show the list workflowsList.style.display = "block"; } catch (error) { console.error("Error initializing workflows list:", error); workflowsLoading.innerHTML = `
Failed to load workflows
`; } } // Search functionality function addWorkflowSearch(element, getData, getTimeAgo) { const workflowsContainer = element.querySelector("#workflows-container"); const h4 = workflowsContainer.querySelector("h4"); const searchContainer = document.createElement("div"); searchContainer.id = "cd-search-container"; searchContainer.style.cssText = "margin-bottom: 12px;"; const searchInput = document.createElement("input"); searchInput.type = "text"; searchInput.placeholder = "Search workflows..."; searchInput.style.cssText = ` width: 100%; padding: 8px 12px; border: 1px solid #555; border-radius: 6px; font-size: 12px; box-sizing: border-box; background: #333; color: #fff; `; let searchTimeout; searchInput.addEventListener("input", (e) => { clearTimeout(searchTimeout); searchTimeout = setTimeout(async () => { const searchTerm = e.target.value.trim(); // Update the tracked search term workflowsState.currentSearch = searchTerm; // Reset state for new search workflowsState.workflows = []; workflowsState.offset = 0; workflowsState.hasMore = true; // Clear current list const workflowsList = element.querySelector("#workflows-list"); workflowsList.innerHTML = ""; // Load with search term workflowsState.loading = false; await loadMoreWorkflows(element, getData, getTimeAgo); }, 300); }); searchContainer.appendChild(searchInput); h4.after(searchContainer); } // Function to refresh current workflow card after deployment function refreshCurrentWorkflowCard(workflowId = null, version = null) { try { console.log("Refreshing current workflow card...", { workflowId, version }); // Find the current workflow card container const container = document.querySelector("#workflows-container"); if (container) { const getDataFunction = window.comfyDeployGetData; // If specific workflow ID and version are provided, use those if (workflowId && version) { console.log("Using provided workflow ID and version:", { workflowId, version, }); // Create a custom renderCurrentWorkflowCard that uses the provided data renderCurrentWorkflowCardWithData( document, getDataFunction, workflowId, version ); } else { // Fallback to getting meta from ComfyDeploy node const currentMeta = getComfyDeployMeta(); console.log("Meta from node:", currentMeta); // Re-render the current workflow card with updated version renderCurrentWorkflowCard(document, getDataFunction); } console.log("Current workflow card refreshed after deployment"); } else { console.log("Workflows container not found, skipping refresh"); } } catch (error) { console.error("Error refreshing current workflow card:", error); } } // Function to render workflow card with specific data (not from ComfyDeploy node) function renderCurrentWorkflowCardWithData( element, getData, workflowId, version ) { const container = element.querySelector("#workflows-container"); if (!container) return; // Remove old card if any const old = container.querySelector("#cd-current-workflow-card"); if (old) old.remove(); // Get the workflow name from the ComfyDeploy node if available const meta = getComfyDeployMeta(); const workflowName = meta?.name || "Unnamed Workflow"; console.log("Rendering workflow card with provided data:", { workflowId, version, workflowName, }); const card = document.createElement("div"); card.id = "cd-current-workflow-card"; card.style.cssText = ` margin: 8px 0 16px 0; padding: 16px; border: 1px solid #374151; border-radius: 12px; background: linear-gradient(135deg, #1f2937 0%, #111827 100%); color: #f9fafb; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); transition: all 0.2s ease; position: relative; `; // Add hover effect card.addEventListener("mouseenter", () => { card.style.transform = "translateY(-1px)"; card.style.boxShadow = "0 8px 20px rgba(0, 0, 0, 0.5)"; card.style.borderColor = "#4b5563"; }); card.addEventListener("mouseleave", () => { card.style.transform = "translateY(0)"; card.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.4)"; card.style.borderColor = "#374151"; }); // Use the shared template card.innerHTML = createWorkflowCardHTML(workflowName, version); // Add the same dropdown functionality as the original card setupVersionDropdown(card, getData, workflowId, version, workflowName); // Populate current comment using the provided version (async () => { try { const vdata = await fetchWorkflowVersionData( getData, workflowId, version ); const commentEl = card.querySelector("#cd-current-comment"); if (commentEl && vdata?.comment) { commentEl.style.display = "block"; commentEl.textContent = vdata.comment; } } catch (error) { console.warn(`Could not fetch version ${version} data:`, error.message); // Don't show error to user for comment fetching, just log it const commentEl = card.querySelector("#cd-current-comment"); if (commentEl) { commentEl.style.display = "none"; } } })(); const searchEl = container.querySelector("#cd-search-container"); if (searchEl) { container.insertBefore(card, searchEl); } else { container.prepend(card); } } // Extract dropdown setup logic to reuse function setupVersionDropdown( card, getData, workflowId, currentVersion, workflowName ) { const btn = card.querySelector("#cd-version-btn"); const panel = card.querySelector("#cd-version-panel"); const dropdown = card.querySelector("#cd-version-dropdown"); const list = card.querySelector("#cd-version-list"); const loading = card.querySelector("#cd-version-loading"); const state = { offset: 0, limit: 20, loading: false, hasMore: true, isOpen: false, }; // Close dropdown when clicking outside const handleClickOutside = (event) => { if (!card.contains(event.target)) { closeDropdown(); } }; const openDropdown = () => { state.isOpen = true; panel.style.display = "block"; btn.querySelector("svg").style.transform = "rotate(180deg)"; document.addEventListener("click", handleClickOutside); // Add opening animation dropdown.style.opacity = "0"; dropdown.style.transform = "translateY(-8px)"; setTimeout(() => { dropdown.style.transition = "opacity 0.2s ease, transform 0.2s ease"; dropdown.style.opacity = "1"; dropdown.style.transform = "translateY(0)"; }, 10); }; const closeDropdown = () => { state.isOpen = false; panel.style.display = "none"; btn.querySelector("svg").style.transform = "rotate(0deg)"; document.removeEventListener("click", handleClickOutside); }; async function loadMore() { if (state.loading || !state.hasMore) return; state.loading = true; loading.style.display = "block"; try { const items = await fetchWorkflowVersions( getData, workflowId, state.offset, state.limit ); if (items.length === 0) { state.hasMore = false; if (state.offset === 0) { const noVersionsMsg = document.createElement("div"); noVersionsMsg.style.cssText = ` padding: 16px; text-align: center; color: #9ca3af; font-size: 12px; `; noVersionsMsg.textContent = "No versions found"; list.appendChild(noVersionsMsg); } } else { items.forEach((v, index) => { const row = document.createElement("div"); const isCurrentVersion = v.version == currentVersion; // Use == for type coercion row.style.cssText = ` padding: 12px 16px; border-bottom: 1px solid #374151; cursor: pointer; transition: all 0.15s ease; display: flex; justify-content: space-between; align-items: center; ${ isCurrentVersion ? "background: rgba(59, 130, 246, 0.1); border-left: 3px solid #3b82f6;" : "" } `; row.innerHTML = `
v${v.version} ${isCurrentVersion ? "(Current)" : ""}
${ v.comment ? `
${v.comment}
` : "" }
${ isCurrentVersion ? '
✓
' : "" } `; if (!isCurrentVersion) { row.addEventListener("mouseenter", () => { row.style.background = "#374151"; }); row.addEventListener("mouseleave", () => { row.style.background = "transparent"; }); } row.onclick = async () => { if (isCurrentVersion) return; try { row.style.opacity = "0.6"; row.style.cursor = "wait"; const data = await fetchWorkflowVersionData( getData, workflowId, v.version ); if (data?.workflow) { window.app.loadGraphData(data.workflow); // Ensure ComfyDeploy node is correct and refresh UI await ensureComfyDeployNodeAfterLoad({ name: workflowName, id: workflowId, version: v.version, }); window.app.extensionManager.toast.add({ severity: "success", summary: "Version loaded", detail: `Loaded v${v.version}${ v.comment ? ` - ${v.comment}` : "" }`, life: 3000, }); closeDropdown(); } else { throw new Error( data === null ? `Version ${v.version} not found or no longer exists` : `Version ${v.version} has no workflow data` ); } } catch (e) { row.style.opacity = "1"; row.style.cursor = "pointer"; window.app.extensionManager.toast.add({ severity: "error", summary: "Failed to load version", detail: e.message || `Could not load version ${v.version}`, life: 4000, }); } }; list.appendChild(row); }); state.offset += items.length; } } catch (error) { console.error("Error loading versions:", error); if (state.offset === 0) { const errorMsg = document.createElement("div"); errorMsg.style.cssText = ` padding: 16px; text-align: center; color: #ef4444; font-size: 12px; line-height: 1.4; `; errorMsg.innerHTML = `
Failed to load versions
Check your connection and try again
`; list.appendChild(errorMsg); } } finally { state.loading = false; loading.style.display = "none"; } } btn.onclick = async (e) => { e.stopPropagation(); if (state.isOpen) { closeDropdown(); } else { openDropdown(); // Always clear and reload to get fresh data list.innerHTML = ""; state.offset = 0; state.hasMore = true; state.loading = false; await loadMore(); } }; list.addEventListener("scroll", () => { if (list.scrollTop + list.clientHeight >= list.scrollHeight - 10) { loadMore(); } }); } // Make the refresh function available globally window.refreshCurrentWorkflowCard = refreshCurrentWorkflowCard; // Export the functions export { initializeWorkflowsList, addWorkflowSearch, workflowsState, fetchWorkflows, loadMoreWorkflows, refreshCurrentWorkflowCard, };