diff --git a/endpoint.py b/endpoint.py index b91f238..f23302b 100644 --- a/endpoint.py +++ b/endpoint.py @@ -110,11 +110,15 @@ def ACTIONS_getUserVideos( def ACTIONS_getUserImages( mode: Literal["input", "output"], + target_width: int | str | None = None, count=1000, offset=0, sort: str | None = None, include_subfolders: bool = False, - subfolder=None, + subfolder: str | None = None, + # IIRC I copied this from Comfy base + # just keeping it until I properly checked implications + salt_urls=False, ): # enabled = "MTB_EXPOSE" in os.environ # if not enabled: @@ -122,11 +126,12 @@ def ACTIONS_getUserImages( imgs = {} count = count or 1000 + target_width = int(target_width) if target_width else None input_dir = Path(folder_paths.get_input_directory()) output_dir = Path(folder_paths.get_output_directory()) - entry_dir = input_dir if mode == "input" else output_dir + entry_dir: Path = input_dir if mode == "input" else output_dir if subfolder: entry_dir = entry_dir / subfolder @@ -155,9 +160,9 @@ def ACTIONS_getUserImages( imgs = { img.name: ( - f"/mtb/view?filename={img.name}&width=512&type={mode}&subfolder={subfolder or ''}" + f"/mtb/view?filename={img.name}{f'&width={target_width}' if target_width and target_width > 0 else ''}&type={mode}&subfolder={subfolder or ''}" f"{img.parent.relative_to(entry_dir) if include_subfolders else ''}" - f"&preview=&rand={secrets.randbelow(424242)}" + f"&preview={f'&rand={secrets.randbelow(424242)}' if salt_urls else ''}" ) for i, img in enumerate(entries) if offset <= i < offset + count diff --git a/web/mtb_input_output_sidebar.js b/web/mtb_input_output_sidebar.js index 8dea7f4..ebb2a3b 100644 --- a/web/mtb_input_output_sidebar.js +++ b/web/mtb_input_output_sidebar.js @@ -3,6 +3,7 @@ import { app } from '../../scripts/app.js' import { api } from '../../scripts/api.js' +import * as mtb_ui from './mtb_ui.js' import * as shared from './comfy_shared.js' import { @@ -15,7 +16,13 @@ import { } from './mtb_ui.js' const offset = 0 + +// These are "global" variables mostly meant to sync user settings. let currentWidth = 200 +let saltUrls = + app.extensionManager.setting.get('mtb.io-sidebar.salt_urls') || false +let targetWidth = + app.extensionManager.setting.get('mtb.io-sidebar.img-size') || 512 let currentMode = 'input' let subfolder = '' let currentSort = 'None' @@ -46,15 +53,19 @@ const updateImage = (node, image) => { * @param {ResultItem} resultItem * @returns {string} - The request URL. */ -const resultItemToQuery = (resultItem) => - [ +const resultItemToQuery = (resultItem) => { + const res = [ `/mtb/view?filename=${resultItem.filename}`, - `width=512`, `type=${resultItem.type}`, `subfolder=${resultItem.subfolder}`, - `preview=`, - ].join('&') + 'preview=', + ] + if (targetWidth > 0) { + res.splice(1, 0, `width=${targetWidth}`) + } + return res.join('&') +} /** * Retrieves the unique prompt ID from a history task item. * @param {HistoryTaskItem} historyTaskItem @@ -80,7 +91,7 @@ const getNewOutputUrls = (mostRecentTask) => { const imageOutputs = Object.values(nodeOutputs.images) imageOutputs.forEach( (resultItem) => - (urls[resultItem.filename] = resultItemToQuery(resultItem)) + (urls[resultItem.filename] = resultItemToQuery(resultItem)), ) } // Can process `animated` and `audio` outputs here. @@ -209,7 +220,7 @@ const getUrls = async (subfolder) => { if (currentMode === 'video') { const output = await shared.runAction( 'getUserVideos', - 256, + targetWidth, count, offset, currentSort, @@ -219,11 +230,13 @@ const getUrls = async (subfolder) => { const output = await shared.runAction( 'getUserImages', currentMode, + targetWidth, count, offset, currentSort, false, subfolder, + saltUrls, ) return output || {} } @@ -236,55 +249,110 @@ if (window?.__COMFYUI_FRONTEND_VERSION__) { const sidebar_extension = { name: 'mtb.io-sidebar', - // init: async () => { - // try { - // const res = await api.fetchApi('/mtb/server-info') - // const msg = await res.json() - // exposed = msg.exposed - // } catch (e) { - // console.error('Error:', e) - // } - // }, - init: () => { - let handle - const version = window?.__COMFYUI_FRONTEND_VERSION__ - console.log(`%c ${version}`, 'background: orange; color: white;') - - ensureMTBStyles() - - app.ui.settings.addSetting({ + settings: [ + { id: 'mtb.io-sidebar.count', category: ['mtb', 'Input & Output Sidebar', 'count'], - name: 'Number of images to fetch', type: 'number', defaultValue: 1000, - tooltip: "This setting affects the input/output sidebar to determine how many images to fetch per pagination (pagination is not yet supported so for now it's the static total)", - attrs: { - style: { - // fontFamily: 'monospace', - }, + }, + { + id: 'mtb.io-sidebar.salt_urls', + category: ['mtb', 'Input & Output Sidebar', 'salt_urls'], + name: 'Salt URLs', + type: 'boolean', + defaultValue: false, + onChange: (n, o) => { + saltUrls = n }, - }) - - app.ui.settings.addSetting({ + tooltip: + 'Adds a random query parameter to every urls to always invalidate caching.', + }, + { id: 'mtb.io-sidebar.img-size', category: ['mtb', 'Input & Output Sidebar', 'img-size'], name: 'Resize width of shown images', - type: 'number', defaultValue: 512, + type: (name, setter, value, attrs) => { + targetWidth = value + const container = mtb_ui.makeElement('div', { + display: 'flex', + alignItems: 'center', + gap: '8px', + }) - tooltip: "It's recommended to keep it at 512px. Setting a high enough value will show images of less width in their native resolution.", - attrs: { - style: { - // fontFamily: 'monospace', - }, + console.log({ name, setter, value, attrs }) + + const baseId = name.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase() + const checkboxId = `${baseId}-checkbox` + const numberInputId = `${baseId}-number` + + const isCheckedInitially = value !== -1 + + // TODO: better way to get defaultValue? + const defaultValue = 512 + const initialNumberValue = isCheckedInitially ? value : defaultValue + + console.log('recreate') + const checkbox = mtb_ui.makeElement( + // harder to match styles (.p-toggleswitch-input) + // since it uses a div synced to the input... + 'input', + {}, + container, + ) + checkbox.type = 'checkbox' + checkbox.id = checkboxId + checkbox.checked = isCheckedInitially + + const numberInput = mtb_ui.makeElement( + 'input.p-inputtext', + {}, + container, + ) + numberInput.type = 'number' + numberInput.id = numberInputId + numberInput.value = initialNumberValue + numberInput.disabled = !isCheckedInitially + numberInput.min = 128 + + checkbox.addEventListener('change', () => { + let valToSet = -1 + if (checkbox.checked) { + numberInput.disabled = false + + valToSet = Number.parseInt(numberInput.value, 10) + if (Number.isNaN(valToSet) || valToSet < numberInput.min) { + valToSet = defaultValue + numberInput.value = valToSet + } + } else { + numberInput.disabled = true + } + setter(valToSet) + }) + + numberInput.addEventListener('input', () => { + if (checkbox.checked) { + const numValue = Number.parseInt(numberInput.value, 10) + if (!Number.isNaN(numValue) && numberInput.value !== '') { + setter(numValue) + } + } + }) + + return container }, - }) - app.ui.settings.addSetting({ + + tooltip: + "If browsing large folders it's recommended to use this to avoid overflow/crash of the webpage. Image will get resized to this target width on the server before being sent to the client.", + }, + + { id: 'mtb.io-sidebar.sort', category: ['mtb', 'Input & Output Sidebar', 'sort'], name: 'Default sort mode', @@ -304,7 +372,15 @@ if (window?.__COMFYUI_FRONTEND_VERSION__) { 'Name', 'Name-Reverse', ], - }) + }, + ], + + init: () => { + let handle + const version = window?.__COMFYUI_FRONTEND_VERSION__ + console.log(`%c ${version}`, 'background: orange; color: white;') + + ensureMTBStyles() app.extensionManager.registerSidebarTab({ id: 'mtb-inputs-outputs', diff --git a/web/mtb_sidebar.js b/web/mtb_sidebar.js deleted file mode 100644 index cb1cf7e..0000000 --- a/web/mtb_sidebar.js +++ /dev/null @@ -1,28 +0,0 @@ -// NOTE: this will be the LT part of mtb API system -// I need to properly publish the source and fix a few things before - -// import { app } from '../../scripts/app.js' -// // import { api } from '../../scripts/api.js' -// -// import * as shared from './comfy_shared.js' -// import { createOutliner } from './dist/mtb_inspector.js' -// -// if (window?.__COMFYUI_FRONTEND_VERSION__) { -// const version = window?.__COMFYUI_FRONTEND_VERSION__ -// console.log(`%c ${version}`, 'background: orange; color: white;') -// -// const panel = app.extensionManager.registerSidebarTab({ -// id: 'mtb-nodes', -// icon: 'pi pi-bolt', -// title: 'MTB', -// tooltip: 'MTB: API outliner', -// type: 'custom', -// // this is run everytime the tab's diplay is toggled on. -// render: (el) => { -// const outliner = createOutliner(el) -// const inputs = shared.getAPIInputs() -// console.log('INPUTS', inputs) -// outliner.$$set({ inputs }) -// }, -// }) -// }