import { headerHtml, loadingIcon } from '../ui.js'; import { ComfyDialog, $el } from '../comfy/comfy.js'; import { infoDialog } from '../comfy/ui.js'; import { formatTimestamp, formatDuration } from '../utils.js'; import { nimbus } from '../resource/index.js'; import { WorkflowRunsTable } from '../ui/table.js'; import { RunDetails } from '../ui/runDetails.js'; import van from '../lib/van.js'; const activeTab = van.state(0) const runId = van.state(null) class WorkflowTableDialog extends ComfyDialog { container = null; constructor() { super(); this.element.classList.add("comfy-normal-modal"); this.element.style.paddingBottom = "20px"; this.element.style.overflowY = "auto"; this.container = document.createElement("div"); this.container.style.color = "white"; this.container.style.width = "720px"; this.container.style.minHeight = "540px"; this.header = document.createElement("div"); this.header.innerHTML = headerHtml this.element.querySelector(".comfy-modal-content").prepend(this.container); this.element.querySelector(".comfy-modal-content").prepend(this.header); this.poll = null; } createButtons() { return [ $el( "div", [ $el("button", { type: "button", textContent: "Close", onclick: () => this.close(), }), ], ), ]; } close() { this.container.removeChild(this.component) this.component.remove() //this.innerContainer.remove() this.element.style.display = "none"; } async show() { this.component = document.createElement("div"); van.add(this.component, () => van.tags.div( activeTab.val == 0 ? WorkflowRunsTable(activeTab, runId) : RunDetails(activeTab, runId) )) this.container.append(this.component) this.element.style.display = "flex"; this.element.style.zIndex = 1001; //await this.showWorkflowsTable(); } async showWorkflowDetails(id) { this.container.innerHTML = workflowDetailsHtml(); const progressBar = this.container.querySelector("#comfycloud-progress-bar") const estimatedRunningTimeContainer = this.container.querySelector("#comfycloud-estimated-running-time-container") const statusContainer = this.container.querySelector("#comfycloud-status-container") const link = this.container.querySelector("#back-button"); const terminate = this.container.querySelector("#terminate-button"); const outputBox = this.container.querySelector("#comfycloud-output-box"); terminate.onclick = async (e) => { try { e.target.innerHTML = loadingIcon; e.target.disabled = true; await nimbus.workflowRun.cancel(id) infoDialog.show(); infoDialog.showMessage( "Successfully terminated", "You will only be billed for the time your workflow was running.", ); } catch(error) { infoDialog.show(); infoDialog.showMessage( "Error", `Something went wrong: ${e}`, ); } finally { e.target.innerHTML = "Stop workflow execution"; e.target.disabled = false; this.close() clearInterval(this.poll) } }; // Main polling function const getWorkflowRunData = async () => { try { const { workflowRun, progress } = await nimbus.workflowRun.pollRun(id) // Stop polling once its successful // handle terminal states (success, failed, terminated) if(workflowRun?.status == "success" || workflowRun?.status == "failed" || workflowRun?.status == "terminated") { terminate.remove(); progressBar.remove(); estimatedRunningTimeContainer.remove() // @todo - Add a final billed time contianer this.stopPolling() // query output only for failed / succeeded runs if (workflowRun?.status != "terminated") { //const data = await getWorkflowRunOutput(id) const data = await nimbus.workflowRun.retrieveOutput(id) outputBox.innerHTML = generateOutputs(data.outputs) } } else if(workflowRun) { // queue the poll if(this.poll == null) { this.poll = setInterval(getWorkflowRunData, 2000); } } // update status if (workflowRun?.status) { statusContainer.innerHTML = workflowRun.status } // Update elapsed time let elapsedTimeInSeconds if (workflowRun?.started_at) { let startTime = new Date(workflowRun.started_at).getTime(); // Convert start time to milliseconds // Current time in milliseconds let currentTime = Date.now(); // Calculate elapsed time in seconds elapsedTimeInSeconds = (currentTime - startTime) / 1000; } // update progress if(progress) { progressBar.style.width = `${progress.value/progress.max * 100}%` progressBar.style.height = "24px" progressBar.style.backgroundColor = "#1D4AFF" progressBar.style.transition = "all .2s" progressBar.innerHTML = `${progress.value/progress.max * 100}% - ${progress.iterationsPerSecond}/s` if(elapsedTimeInSeconds) { const remainingProgress = progress.max - progress.value const remainingProgressInSeconds = (remainingProgress * progress.iterationsPerSecond) const estimatedRunningTime = elapsedTimeInSeconds + remainingProgressInSeconds; estimatedRunningTimeContainer.innerHTML = `${estimatedRunningTime}s` } } } catch(error) { console.error("Error:", error); this.stopPolling() } } getWorkflowRunData() // Handle button clicks link.onclick = async () => { await this.showWorkflowsTable() this.stopPolling() }; this.element.style.display = "flex"; this.element.style.zIndex = 1001; } stopPolling() { clearInterval(this.poll) this.poll = null } async showWorkflowsTable() { // @todo - call api to set data //const workflow = await getCloudWorkflow() const workflow = await nimbus.workflow.retrieve() this.runs = workflow.runs || []; this.container.innerHTML = tableHtml(this.runs); const workflowRows = this.container.querySelectorAll(".workflow-row"); workflowRows.forEach((row, i) => { row.onclick = async () => { const rowId = this.runs[i].id await this.showWorkflowDetails(rowId) } }); this.element.style.display = "flex"; this.element.style.zIndex = 1001; } } export const workflowTableDialog = new WorkflowTableDialog() // Function to handle row click const workflowDetailsHtml = () => { //const imageData = rowDetails.outputs[0]?.data.images[0].filename; return`

Row Details

Status:

Estimated total running time:

`; } const generateOutputs = (outputs) => { return outputs?.map((run) => { const fileName = run.data.images?.[0].filename || run.data.files?.[0].filename || run.data.gifs?.[0].filename; if (!fileName) { return` Output ${JSON.stringify(run.data, null, 2)} `; } // const filePath return ` ${fileName} ${generateOutputDisplay(run.run_id, fileName)} `; }).join(''); } const generateOutputDisplay = (run_id, filename) => { const url = `https://comfyui-output.nyc3.digitaloceanspaces.com/comfyui-output/outputs/runs/${run_id}/${filename}` if (filename.endsWith(".mp4") || filename.endsWith(".webm")) { return` `; } if ( filename.endsWith(".png") || filename.endsWith(".gif") || filename.endsWith(".jpg") || filename.endsWith(".jpeg") ) { return `${filename}`; } else { return `${filename}`; } } // Function to generate HTML rows based on the array of objects function generateRows(data) { return data.map((item, i) => ` ${i+1} ${formatTimestamp(item.created_at)} ${formatDuration(item.duration)} ${item.status} `).join(''); } // Generate the table HTML with dynamic rows const tableHtml = (data) => { return` ${generateRows(data)}
Number Time Duration Status
`; }