feat: ✨ implement ipaq ideas for the I/O sidebar

This commit is contained in:
Mel Massadian
2025-06-26 15:42:31 +02:00
committed by Mel Massadian
parent c7248344cc
commit 940a781f29
3 changed files with 127 additions and 74 deletions
+9 -4
View File
@@ -110,11 +110,15 @@ def ACTIONS_getUserVideos(
def ACTIONS_getUserImages( def ACTIONS_getUserImages(
mode: Literal["input", "output"], mode: Literal["input", "output"],
target_width: int | str | None = None,
count=1000, count=1000,
offset=0, offset=0,
sort: str | None = None, sort: str | None = None,
include_subfolders: bool = False, 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 # enabled = "MTB_EXPOSE" in os.environ
# if not enabled: # if not enabled:
@@ -122,11 +126,12 @@ def ACTIONS_getUserImages(
imgs = {} imgs = {}
count = count or 1000 count = count or 1000
target_width = int(target_width) if target_width else None
input_dir = Path(folder_paths.get_input_directory()) input_dir = Path(folder_paths.get_input_directory())
output_dir = Path(folder_paths.get_output_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: if subfolder:
entry_dir = entry_dir / subfolder entry_dir = entry_dir / subfolder
@@ -155,9 +160,9 @@ def ACTIONS_getUserImages(
imgs = { imgs = {
img.name: ( 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"{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) for i, img in enumerate(entries)
if offset <= i < offset + count if offset <= i < offset + count
+118 -42
View File
@@ -3,6 +3,7 @@
import { app } from '../../scripts/app.js' import { app } from '../../scripts/app.js'
import { api } from '../../scripts/api.js' import { api } from '../../scripts/api.js'
import * as mtb_ui from './mtb_ui.js'
import * as shared from './comfy_shared.js' import * as shared from './comfy_shared.js'
import { import {
@@ -15,7 +16,13 @@ import {
} from './mtb_ui.js' } from './mtb_ui.js'
const offset = 0 const offset = 0
// These are "global" variables mostly meant to sync user settings.
let currentWidth = 200 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 currentMode = 'input'
let subfolder = '' let subfolder = ''
let currentSort = 'None' let currentSort = 'None'
@@ -46,15 +53,19 @@ const updateImage = (node, image) => {
* @param {ResultItem} resultItem * @param {ResultItem} resultItem
* @returns {string} - The request URL. * @returns {string} - The request URL.
*/ */
const resultItemToQuery = (resultItem) => const resultItemToQuery = (resultItem) => {
[ const res = [
`/mtb/view?filename=${resultItem.filename}`, `/mtb/view?filename=${resultItem.filename}`,
`width=512`,
`type=${resultItem.type}`, `type=${resultItem.type}`,
`subfolder=${resultItem.subfolder}`, `subfolder=${resultItem.subfolder}`,
`preview=`, 'preview=',
].join('&') ]
if (targetWidth > 0) {
res.splice(1, 0, `width=${targetWidth}`)
}
return res.join('&')
}
/** /**
* Retrieves the unique prompt ID from a history task item. * Retrieves the unique prompt ID from a history task item.
* @param {HistoryTaskItem} historyTaskItem * @param {HistoryTaskItem} historyTaskItem
@@ -80,7 +91,7 @@ const getNewOutputUrls = (mostRecentTask) => {
const imageOutputs = Object.values(nodeOutputs.images) const imageOutputs = Object.values(nodeOutputs.images)
imageOutputs.forEach( imageOutputs.forEach(
(resultItem) => (resultItem) =>
(urls[resultItem.filename] = resultItemToQuery(resultItem)) (urls[resultItem.filename] = resultItemToQuery(resultItem)),
) )
} }
// Can process `animated` and `audio` outputs here. // Can process `animated` and `audio` outputs here.
@@ -209,7 +220,7 @@ const getUrls = async (subfolder) => {
if (currentMode === 'video') { if (currentMode === 'video') {
const output = await shared.runAction( const output = await shared.runAction(
'getUserVideos', 'getUserVideos',
256, targetWidth,
count, count,
offset, offset,
currentSort, currentSort,
@@ -219,11 +230,13 @@ const getUrls = async (subfolder) => {
const output = await shared.runAction( const output = await shared.runAction(
'getUserImages', 'getUserImages',
currentMode, currentMode,
targetWidth,
count, count,
offset, offset,
currentSort, currentSort,
false, false,
subfolder, subfolder,
saltUrls,
) )
return output || {} return output || {}
} }
@@ -236,55 +249,110 @@ if (window?.__COMFYUI_FRONTEND_VERSION__) {
const sidebar_extension = { const sidebar_extension = {
name: 'mtb.io-sidebar', name: 'mtb.io-sidebar',
// init: async () => { settings: [
// 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({
id: 'mtb.io-sidebar.count', id: 'mtb.io-sidebar.count',
category: ['mtb', 'Input & Output Sidebar', 'count'], category: ['mtb', 'Input & Output Sidebar', 'count'],
name: 'Number of images to fetch', name: 'Number of images to fetch',
type: 'number', type: 'number',
defaultValue: 1000, defaultValue: 1000,
tooltip: 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)", "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
}, },
}) tooltip:
'Adds a random query parameter to every urls to always invalidate caching.',
app.ui.settings.addSetting({ },
{
id: 'mtb.io-sidebar.img-size', id: 'mtb.io-sidebar.img-size',
category: ['mtb', 'Input & Output Sidebar', 'img-size'], category: ['mtb', 'Input & Output Sidebar', 'img-size'],
name: 'Resize width of shown images', name: 'Resize width of shown images',
type: 'number',
defaultValue: 512, 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.", console.log({ name, setter, value, attrs })
attrs: {
style: { const baseId = name.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase()
// fontFamily: 'monospace', 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', id: 'mtb.io-sidebar.sort',
category: ['mtb', 'Input & Output Sidebar', 'sort'], category: ['mtb', 'Input & Output Sidebar', 'sort'],
name: 'Default sort mode', name: 'Default sort mode',
@@ -304,7 +372,15 @@ if (window?.__COMFYUI_FRONTEND_VERSION__) {
'Name', 'Name',
'Name-Reverse', 'Name-Reverse',
], ],
}) },
],
init: () => {
let handle
const version = window?.__COMFYUI_FRONTEND_VERSION__
console.log(`%c ${version}`, 'background: orange; color: white;')
ensureMTBStyles()
app.extensionManager.registerSidebarTab({ app.extensionManager.registerSidebarTab({
id: 'mtb-inputs-outputs', id: 'mtb-inputs-outputs',
-28
View File
@@ -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 })
// },
// })
// }