// Workflow list management let workflowsState = { workflows: [], offset: 0, limit: 20, loading: false, hasMore: true, initialized: false, currentSearch: "", }; // Make workflowsState accessible globally window.workflowsState = workflowsState; 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"); 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 const 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); // Show success toast window.app.extensionManager.toast.add({ severity: "success", summary: "Workflow loaded successfully", detail: `Loaded "${workflow.name}" v${latestVersion.version}`, life: 3000, }); } } } 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 { 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 - 550px); 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); // 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.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); } // Export the functions export { initializeWorkflowsList, addWorkflowSearch, workflowsState, fetchWorkflows, loadMoreWorkflows, };