/** * Pure helpers for image entries rendered by the admin and gallery pages. * * Image rows come from the database (`width`, `height`, `file_size` are stored * from a POST body and may be strings) or from the output-folder scan (`id` is * a path digest, `path` is the file). Everything these helpers return for HTML * is already escaped, so callers can drop it into text or a quoted attribute. * * Loadable in the browser (window.ImageHelpers) and in Node for tests. */ (function (root) { "use strict"; const escapeHtml = typeof module !== "undefined" && module.exports ? require("./html-escape.js").escapeHtml : root.escapeHtml; const UNKNOWN_SIZE = "Unknown size"; /** * "640×480 | 1.2 MB" for a card body or a viewer caption, escaped. * * @param {{width?: unknown, height?: unknown, file_size?: unknown}|null|undefined} image * @param {{formatFileSize?: (bytes: number) => string, separator?: string}} [options] * @returns {string} escaped text; "Unknown size" when a dimension is missing */ function formatImageCaption(image, { formatFileSize, separator = " | " } = {}) { const entry = image || {}; const parts = []; if (entry.width && entry.height) { parts.push(`${escapeHtml(entry.width)}×${escapeHtml(entry.height)}`); } else { parts.push(UNKNOWN_SIZE); } const bytes = Number(entry.file_size); if (formatFileSize && Number.isFinite(bytes) && bytes > 0) { parts.push(escapeHtml(formatFileSize(bytes))); } return parts.join(escapeHtml(separator)); } /** * A complete standalone page showing a workflow as pretty-printed JSON. * Written into a blank same-origin window, so the JSON must not be able * to close the
 and inject markup.
     *
     * @param {unknown} workflow
     * @returns {string} HTML document
     */
    function workflowDocumentHtml(workflow) {
        const json = escapeHtml(JSON.stringify(workflow, null, 2));
        return [
            "",
            "",
            "ComfyUI Workflow Data",
            "",
            "

ComfyUI Workflow JSON

", `
${json}
`, "", "", ].join("\n"); } /** * Body for POST /prompt_manager/autotag/single. * * Database rows (admin review, /images/all) have an integer id the server * looks up; output-folder entries (gallery review, /images/output) have a * path digest as id, which would 404, so those are addressed by path. * * @param {{id?: unknown, path?: unknown}} image * @param {{modelType: string, generalThreshold?: number, characterThreshold?: number, prompt?: string}} settings * @returns {object} JSON-serialisable request body */ function autotagSingleBody(image, settings) { const entry = image || {}; const target = typeof entry.path === "string" && entry.path && !Number.isInteger(entry.id) ? { path: entry.path } : { image_id: entry.id }; if ("path" in target && Number.isInteger(entry.root_index)) { target.root_index = entry.root_index; } const modelType = String(settings.modelType || ""); const params = modelType.startsWith("wd14") ? { general_threshold: settings.generalThreshold, character_threshold: settings.characterThreshold, } : { prompt: settings.prompt }; return { ...target, model_type: modelType, ...params }; } /** * Next source to try after an fails. Cards show the thumbnail first; * when it fails (stale cache, half-written file) the original is worth one * attempt. Returns null when there is nothing left to try. * @param {string} currentSrc * @param {DOMStringMap|object|undefined} dataset `data-original`, `data-fell-back` */ function fallbackImageSource(currentSrc, dataset) { if (!dataset || dataset.fellBack) return null; const original = dataset.original; if (!original || original === currentSrc) return null; return original; } function metadataLookupUrl(imageUrl) { try { const url = new URL(imageUrl, 'http://prompt-manager.invalid'); const marker = '/prompt_manager/images/serve/'; const index = url.pathname.indexOf(marker); if (index < 0) return null; const relativePath = decodeURIComponent(url.pathname.slice(index + marker.length)); const query = new URLSearchParams(); if (url.searchParams.has('root')) query.set('root', url.searchParams.get('root')); const suffix = query.size ? `?${query}` : ''; return `/prompt_manager/images/prompt/${encodeURIComponent(relativePath)}${suffix}`; } catch { return null; } } const api = { formatImageCaption, workflowDocumentHtml, autotagSingleBody, fallbackImageSource, metadataLookupUrl }; if (typeof module !== "undefined" && module.exports) module.exports = api; else root.ImageHelpers = api; })(typeof window !== "undefined" ? window : globalThis);