diff --git a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png index f6130d900..aaa2b590a 100644 Binary files a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png and b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png index 1b4771e31..b63ad44ee 100644 Binary files a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png and b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png index 4ed230307..df8f56cb4 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png index 077e34405..ebfe67a8d 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png index d0235f54b..cbbb02330 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png index e4155c01f..38d93e717 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png index c5d0151f1..9740334b8 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png index 7b1d91b3d..5df3a9f42 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png index 2dd92ee99..c8d2db1f4 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png index 50f26e324..ead82b036 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png index 95a0f2a7d..ff06bd6ad 100644 Binary files a/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png index de2f73910..43e9ac170 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png index e04cb92a7..c876d8af3 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png index 2bee56e28..015c2479d 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png index 76070e397..77678b6b0 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png index 19a832b92..4656299b2 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png index 9ad3d1c02..ce83405d2 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png differ diff --git a/eslint.config.ts b/eslint.config.ts index b66674865..e4b151080 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -60,6 +60,7 @@ export default defineConfig([ '**/vite.config.*.timestamp*', '**/vitest.config.*.timestamp*', 'packages/registry-types/src/comfyRegistryTypes.ts', + 'public/auth-dev-sw.js', 'public/auth-sw.js', 'src/extensions/core/*', 'src/scripts/*', diff --git a/knip.config.ts b/knip.config.ts index 928483060..6f5487eba 100644 --- a/knip.config.ts +++ b/knip.config.ts @@ -42,8 +42,9 @@ const config: KnipConfig = { 'packages/registry-types/src/comfyRegistryTypes.ts', // Used by a custom node (that should move off of this) 'src/scripts/ui/components/splitButton.ts', - // Service worker - registered at runtime via navigator.serviceWorker.register() - 'public/auth-sw.js' + // Service workers - registered at runtime via navigator.serviceWorker.register() + 'public/auth-sw.js', + 'public/auth-dev-sw.js' ], compilers: { // https://github.com/webpro-nl/knip/issues/1008#issuecomment-3207756199 diff --git a/public/auth-dev-sw.js b/public/auth-dev-sw.js new file mode 100644 index 000000000..c721ba2ac --- /dev/null +++ b/public/auth-dev-sw.js @@ -0,0 +1,168 @@ +/** + * @fileoverview Authentication Service Worker (Development Version) + * Intercepts /api/view requests and rewrites them to a configurable base URL with auth token. + * Required for browser-native requests (img, video, audio) that cannot send custom headers. + * This version is used in development to proxy requests to staging/test environments. + * Default base URL: https://testcloud.comfy.org (configurable via SET_BASE_URL message) + */ + +/** + * @typedef {Object} AuthHeader + * @property {string} Authorization - Bearer token for authentication + */ + +/** + * @typedef {Object} CachedAuth + * @property {AuthHeader|null} header + * @property {number} expiresAt - Timestamp when cache expires + */ + +const CACHE_TTL_MS = 50 * 60 * 1000 // 50 minutes (Firebase tokens expire in 1 hour) + +/** @type {CachedAuth|null} */ +let authCache = null + +/** @type {Promise|null} */ +let authRequestInFlight = null + +/** @type {string} */ +let baseUrl = 'https://testcloud.comfy.org' + +self.addEventListener('message', (event) => { + if (event.data.type === 'INVALIDATE_AUTH_HEADER') { + authCache = null + authRequestInFlight = null + } + + if (event.data.type === 'SET_BASE_URL') { + baseUrl = event.data.baseUrl + console.log('[Auth DEV SW] Base URL set to:', baseUrl) + } +}) + +self.addEventListener('fetch', (event) => { + const url = new URL(event.request.url) + + if ( + !url.pathname.startsWith('/api/view') && + !url.pathname.startsWith('/api/viewvideo') + ) { + return + } + + event.respondWith( + (async () => { + try { + // Rewrite URL to use configured base URL (default: stagingcloud.comfy.org) + const originalUrl = new URL(event.request.url) + const rewrittenUrl = new URL( + originalUrl.pathname + originalUrl.search, + baseUrl + ) + + const authHeader = await getAuthHeader() + + // With mode: 'no-cors', Authorization headers are stripped by the browser + // So we add the token to the URL as a query parameter instead + if (authHeader && authHeader.Authorization) { + const token = authHeader.Authorization.replace('Bearer ', '') + rewrittenUrl.searchParams.set('token', token) + } + + // Cross-origin request requires no-cors mode + // - mode: 'no-cors' allows cross-origin fetches without CORS headers + // - Returns opaque response, which works fine for images/videos/audio + // - Auth token is sent via query parameter since headers are stripped in no-cors mode + // - Server may return redirect to GCS, which will be followed automatically + return fetch(rewrittenUrl, { + method: 'GET', + redirect: 'follow', + mode: 'no-cors' + }) + } catch (error) { + console.error('[Auth DEV SW] Request failed:', error) + const originalUrl = new URL(event.request.url) + const rewrittenUrl = new URL( + originalUrl.pathname + originalUrl.search, + baseUrl + ) + return fetch(rewrittenUrl, { + mode: 'no-cors', + redirect: 'follow' + }) + } + })() + ) +}) + +/** + * Gets auth header from cache or requests from main thread + * @returns {Promise} + */ +async function getAuthHeader() { + // Return cached value if valid + if (authCache && authCache.expiresAt > Date.now()) { + return authCache.header + } + + // Clear expired cache + if (authCache) { + authCache = null + } + + // Deduplicate concurrent requests + if (authRequestInFlight) { + return authRequestInFlight + } + + authRequestInFlight = requestAuthHeaderFromMainThread() + const header = await authRequestInFlight + authRequestInFlight = null + + // Cache the result + if (header) { + authCache = { + header, + expiresAt: Date.now() + CACHE_TTL_MS + } + } + + return header +} + +/** + * Requests auth header from main thread via MessageChannel + * @returns {Promise} + */ +async function requestAuthHeaderFromMainThread() { + const clients = await self.clients.matchAll() + if (clients.length === 0) { + return null + } + + const messageChannel = new MessageChannel() + + return new Promise((resolve) => { + let timeoutId + + messageChannel.port1.onmessage = (event) => { + clearTimeout(timeoutId) + resolve(event.data.authHeader) + } + + timeoutId = setTimeout(() => { + console.error( + '[Auth DEV SW] Timeout waiting for auth header from main thread' + ) + resolve(null) + }, 1000) + + clients[0].postMessage({ type: 'REQUEST_AUTH_HEADER' }, [ + messageChannel.port2 + ]) + }) +} + +self.addEventListener('activate', (event) => { + event.waitUntil(self.clients.claim()) +}) diff --git a/src/App.vue b/src/App.vue index c0c9fdd20..a2bd28fdd 100644 --- a/src/App.vue +++ b/src/App.vue @@ -42,7 +42,6 @@ const showContextMenu = (event: MouseEvent) => { } onMounted(() => { - // @ts-expect-error fixme ts strict error window['__COMFYUI_FRONTEND_VERSION__'] = config.app_version if (isElectron()) { diff --git a/src/components/graph/widgets/MultiSelectWidget.vue b/src/components/graph/widgets/MultiSelectWidget.vue index 611743385..14936fca3 100644 --- a/src/components/graph/widgets/MultiSelectWidget.vue +++ b/src/components/graph/widgets/MultiSelectWidget.vue @@ -8,6 +8,9 @@ :max-selected-labels="3" :display="display" class="w-full" + :pt="{ + dropdownIcon: 'text-button-icon' + }" /> diff --git a/src/composables/useTemplateFiltering.ts b/src/composables/useTemplateFiltering.ts index c22abb57b..03a599f29 100644 --- a/src/composables/useTemplateFiltering.ts +++ b/src/composables/useTemplateFiltering.ts @@ -128,6 +128,17 @@ export function useTemplateFiltering( }) }) + const getVramMetric = (template: TemplateInfo) => { + if ( + typeof template.vram === 'number' && + Number.isFinite(template.vram) && + template.vram > 0 + ) { + return template.vram + } + return Number.POSITIVE_INFINITY + } + const sortedTemplates = computed(() => { const templates = [...filteredByLicenses.value] @@ -145,9 +156,21 @@ export function useTemplateFiltering( return dateB.getTime() - dateA.getTime() }) case 'vram-low-to-high': - // TODO: Implement VRAM sorting when VRAM data is available - // For now, keep original order - return templates + return templates.sort((a, b) => { + const vramA = getVramMetric(a) + const vramB = getVramMetric(b) + + if (vramA === vramB) { + const nameA = a.title || a.name || '' + const nameB = b.title || b.name || '' + return nameA.localeCompare(nameB) + } + + if (vramA === Number.POSITIVE_INFINITY) return 1 + if (vramB === Number.POSITIVE_INFINITY) return -1 + + return vramA - vramB + }) case 'model-size-low-to-high': return templates.sort((a: any, b: any) => { const sizeA = diff --git a/src/platform/assets/composables/useAssetBrowser.ts b/src/platform/assets/composables/useAssetBrowser.ts index 98e7208c9..28d32a900 100644 --- a/src/platform/assets/composables/useAssetBrowser.ts +++ b/src/platform/assets/composables/useAssetBrowser.ts @@ -1,5 +1,7 @@ import { computed, ref } from 'vue' import type { Ref } from 'vue' +import { useFuse } from '@vueuse/integrations/useFuse' +import type { UseFuseOptions } from '@vueuse/integrations/useFuse' import { d, t } from '@/i18n' import type { FilterState } from '@/platform/assets/components/AssetFilterBar.vue' @@ -15,19 +17,6 @@ function filterByCategory(category: string) { } } -function filterByQuery(query: string) { - return (asset: AssetItem) => { - if (!query) return true - const lowerQuery = query.toLowerCase() - const description = getAssetDescription(asset) - return ( - asset.name.toLowerCase().includes(lowerQuery) || - (description && description.toLowerCase().includes(lowerQuery)) || - asset.tags.some((tag) => tag.toLowerCase().includes(lowerQuery)) - ) - } -} - function filterByFileFormats(formats: string[]) { return (asset: AssetItem) => { if (formats.length === 0) return true @@ -160,9 +149,31 @@ export function useAssetBrowser( return assets.value.filter(filterByCategory(selectedCategory.value)) }) + const fuseOptions: UseFuseOptions = { + fuseOptions: { + keys: [ + { name: 'name', weight: 0.4 }, + { name: 'tags', weight: 0.3 } + ], + threshold: 0.4, // Higher threshold for typo tolerance (0.0 = exact, 1.0 = match all) + ignoreLocation: true, // Search anywhere in the string, not just at the beginning + includeScore: true + }, + matchAllWhenSearchEmpty: true + } + + const { results: fuseResults } = useFuse( + searchQuery, + categoryFilteredAssets, + fuseOptions + ) + + const searchFiltered = computed(() => + fuseResults.value.map((result) => result.item) + ) + const filteredAssets = computed(() => { - const filtered = categoryFilteredAssets.value - .filter(filterByQuery(searchQuery.value)) + const filtered = searchFiltered.value .filter(filterByFileFormats(filters.value.fileFormats)) .filter(filterByBaseModels(filters.value.baseModels)) diff --git a/src/platform/auth/serviceWorker/register.ts b/src/platform/auth/serviceWorker/register.ts index 0f44f3b9b..c752721f7 100644 --- a/src/platform/auth/serviceWorker/register.ts +++ b/src/platform/auth/serviceWorker/register.ts @@ -13,7 +13,34 @@ async function registerAuthServiceWorker(): Promise { } try { - await navigator.serviceWorker.register('/auth-sw.js') + // Use dev service worker in development mode (rewrites to configured backend URL with token in query param) + // Use production service worker in production (same-origin requests with Authorization header) + const swPath = import.meta.env.DEV ? '/auth-dev-sw.js' : '/auth-sw.js' + const registration = await navigator.serviceWorker.register(swPath) + + // Configure base URL for dev service worker + if (import.meta.env.DEV) { + console.warn('[Auth DEV SW] Registering development serviceworker') + // Use the same URL that Vite proxy is using + const baseUrl = __DEV_SERVER_COMFYUI_URL__ + navigator.serviceWorker.controller?.postMessage({ + type: 'SET_BASE_URL', + baseUrl + }) + + // Also set base URL when service worker becomes active + registration.addEventListener('updatefound', () => { + const newWorker = registration.installing + newWorker?.addEventListener('statechange', () => { + if (newWorker.state === 'activated') { + navigator.serviceWorker.controller?.postMessage({ + type: 'SET_BASE_URL', + baseUrl + }) + } + }) + }) + } setupAuthHeaderProvider() setupCacheInvalidation() diff --git a/src/platform/remote/comfyui/history/adapters/v2ToV1Adapter.ts b/src/platform/remote/comfyui/history/adapters/v2ToV1Adapter.ts new file mode 100644 index 000000000..5b0e9f266 --- /dev/null +++ b/src/platform/remote/comfyui/history/adapters/v2ToV1Adapter.ts @@ -0,0 +1,43 @@ +/** + * @fileoverview Adapter to convert V2 history format to V1 format + * @module platform/remote/comfyui/history/adapters/v2ToV1Adapter + * + * Converts cloud API V2 response format to the V1 format expected by the app. + */ + +import type { HistoryTaskItem, TaskPrompt } from '../types/historyV1Types' +import type { + HistoryResponseV2, + RawHistoryItemV2, + TaskOutput, + TaskPromptV2 +} from '../types/historyV2Types' + +/** + * Maps V2 prompt format to V1 prompt tuple format. + */ +function mapPromptV2toV1( + promptV2: TaskPromptV2, + outputs: TaskOutput +): TaskPrompt { + const outputNodesIds = Object.keys(outputs) + const { priority, prompt_id, extra_data } = promptV2 + return [priority, prompt_id, {}, extra_data, outputNodesIds] +} + +/** + * Maps V2 history format to V1 history format. + */ +export function mapHistoryV2toHistory( + historyV2Response: HistoryResponseV2 +): HistoryTaskItem[] { + return historyV2Response.history.map( + ({ prompt, status, outputs, meta }: RawHistoryItemV2): HistoryTaskItem => ({ + taskType: 'History' as const, + prompt: mapPromptV2toV1(prompt, outputs), + status, + outputs, + meta + }) + ) +} diff --git a/src/platform/remote/comfyui/history/fetchers/fetchHistoryV1.ts b/src/platform/remote/comfyui/history/fetchers/fetchHistoryV1.ts new file mode 100644 index 000000000..1034753a9 --- /dev/null +++ b/src/platform/remote/comfyui/history/fetchers/fetchHistoryV1.ts @@ -0,0 +1,36 @@ +/** + * @fileoverview V1 History Fetcher - Desktop/localhost API + * @module platform/remote/comfyui/history/fetchers/fetchHistoryV1 + * + * Fetches history directly from V1 API endpoint. + * Used by desktop and localhost distributions. + */ + +import type { + HistoryTaskItem, + HistoryV1Response +} from '../types/historyV1Types' + +/** + * Fetches history from V1 API endpoint + * @param api - API instance with fetchApi method + * @param maxItems - Maximum number of history items to fetch + * @returns Promise resolving to V1 history response + */ +export async function fetchHistoryV1( + fetchApi: (url: string) => Promise, + maxItems: number = 200 +): Promise { + const res = await fetchApi(`/history?max_items=${maxItems}`) + const json: Record< + string, + Omit + > = await res.json() + + return { + History: Object.values(json).map((item) => ({ + ...item, + taskType: 'History' + })) + } +} diff --git a/src/platform/remote/comfyui/history/fetchers/fetchHistoryV2.ts b/src/platform/remote/comfyui/history/fetchers/fetchHistoryV2.ts new file mode 100644 index 000000000..129c0ab8a --- /dev/null +++ b/src/platform/remote/comfyui/history/fetchers/fetchHistoryV2.ts @@ -0,0 +1,27 @@ +/** + * @fileoverview V2 History Fetcher - Cloud API with adapter + * @module platform/remote/comfyui/history/fetchers/fetchHistoryV2 + * + * Fetches history from V2 API endpoint and converts to V1 format. + * Used exclusively by cloud distribution. + */ + +import { mapHistoryV2toHistory } from '../adapters/v2ToV1Adapter' +import type { HistoryV1Response } from '../types/historyV1Types' +import type { HistoryResponseV2 } from '../types/historyV2Types' + +/** + * Fetches history from V2 API endpoint and adapts to V1 format + * @param fetchApi - API instance with fetchApi method + * @param maxItems - Maximum number of history items to fetch + * @returns Promise resolving to V1 history response (adapted from V2) + */ +export async function fetchHistoryV2( + fetchApi: (url: string) => Promise, + maxItems: number = 200 +): Promise { + const res = await fetchApi(`/history_v2?max_items=${maxItems}`) + const rawData: HistoryResponseV2 = await res.json() + const adaptedHistory = mapHistoryV2toHistory(rawData) + return { History: adaptedHistory } +} diff --git a/src/platform/remote/comfyui/history/index.ts b/src/platform/remote/comfyui/history/index.ts new file mode 100644 index 000000000..fc96225e4 --- /dev/null +++ b/src/platform/remote/comfyui/history/index.ts @@ -0,0 +1,29 @@ +/** + * @fileoverview History API module - Distribution-aware exports + * @module platform/remote/comfyui/history + * + * This module provides a unified history fetching interface that automatically + * uses the correct implementation based on build-time distribution constant. + * + * - Cloud builds: Uses V2 API with adapter (tree-shakes V1 fetcher) + * - Desktop/localhost builds: Uses V1 API directly (tree-shakes V2 fetcher + adapter) + * + * The rest of the application only needs to import from this module and use + * V1 types - all distribution-specific details are encapsulated here. + */ + +import { isCloud } from '@/platform/distribution/types' +import { fetchHistoryV1 } from './fetchers/fetchHistoryV1' +import { fetchHistoryV2 } from './fetchers/fetchHistoryV2' + +/** + * Fetches history using the appropriate API for the current distribution. + * Build-time constant enables dead code elimination - only one implementation + * will be included in the final bundle. + */ +export const fetchHistory = isCloud ? fetchHistoryV2 : fetchHistoryV1 + +/** + * Export only V1 types publicly - consumers don't need to know about V2 + */ +export type * from './types' diff --git a/src/platform/remote/comfyui/history/types/historyV1Types.ts b/src/platform/remote/comfyui/history/types/historyV1Types.ts new file mode 100644 index 000000000..f7bff7a84 --- /dev/null +++ b/src/platform/remote/comfyui/history/types/historyV1Types.ts @@ -0,0 +1,15 @@ +/** + * @fileoverview History V1 types - Public interface used throughout the app + * @module platform/remote/comfyui/history/types/historyV1Types + * + * These types represent the V1 history format that the application expects. + * Both desktop (direct V1 API) and cloud (V2 API + adapter) return data in this format. + */ + +import type { HistoryTaskItem, TaskPrompt } from '@/schemas/apiSchema' + +export interface HistoryV1Response { + History: HistoryTaskItem[] +} + +export type { HistoryTaskItem, TaskPrompt } diff --git a/src/platform/remote/comfyui/history/types/historyV2Types.ts b/src/platform/remote/comfyui/history/types/historyV2Types.ts new file mode 100644 index 000000000..16c4fa000 --- /dev/null +++ b/src/platform/remote/comfyui/history/types/historyV2Types.ts @@ -0,0 +1,45 @@ +/** + * @fileoverview History V2 types and schemas - Internal cloud API format + * @module platform/remote/comfyui/history/types/historyV2Types + * + * These types and schemas represent the V2 history format returned by the cloud API. + * They are only used internally and are converted to V1 format via adapter. + * + * IMPORTANT: These types should NOT be used outside this history module. + */ + +import { z } from 'zod' + +import { + zExtraData, + zPromptId, + zQueueIndex, + zStatus, + zTaskMeta, + zTaskOutput +} from '@/schemas/apiSchema' + +const zTaskPromptV2 = z.object({ + priority: zQueueIndex, + prompt_id: zPromptId, + extra_data: zExtraData +}) + +const zRawHistoryItemV2 = z.object({ + prompt_id: zPromptId, + prompt: zTaskPromptV2, + status: zStatus.optional(), + outputs: zTaskOutput, + meta: zTaskMeta.optional() +}) + +const zHistoryResponseV2 = z.object({ + history: z.array(zRawHistoryItemV2) +}) + +export type TaskPromptV2 = z.infer +export type RawHistoryItemV2 = z.infer +export type HistoryResponseV2 = z.infer +export type TaskOutput = z.infer + +export { zRawHistoryItemV2 } diff --git a/src/platform/remote/comfyui/history/types/index.ts b/src/platform/remote/comfyui/history/types/index.ts new file mode 100644 index 000000000..d49f66ffe --- /dev/null +++ b/src/platform/remote/comfyui/history/types/index.ts @@ -0,0 +1,9 @@ +/** + * @fileoverview Public history types export + * @module platform/remote/comfyui/history/types + * + * Only V1 types are exported publicly - the rest of the app + * should never need to know about V2 types or implementation details. + */ + +export type * from './historyV1Types' diff --git a/src/platform/workflow/templates/types/template.ts b/src/platform/workflow/templates/types/template.ts index dba93adf4..774f3a585 100644 --- a/src/platform/workflow/templates/types/template.ts +++ b/src/platform/workflow/templates/types/template.ts @@ -18,6 +18,10 @@ export interface TemplateInfo { date?: string useCase?: string license?: string + /** + * Estimated VRAM requirement in bytes. + */ + vram?: number size?: number } diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetFileUpload.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetFileUpload.vue index 61a9f1048..4eea704b1 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetFileUpload.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetFileUpload.vue @@ -25,7 +25,8 @@ class="max-w-[20em] min-w-[8em] text-xs" size="small" :pt="{ - option: 'text-xs' + option: 'text-xs', + dropdownIcon: 'text-button-icon' }" />