mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-02 22:37:32 +00:00
fix: replace uuids with displaynames rather than shas
This commit is contained in:
@@ -64,18 +64,27 @@ const FILE_EXTENSIONS = [
|
||||
]
|
||||
|
||||
/**
|
||||
* Check if options contain filename-like values
|
||||
* Check if options contain filename-like values (UUIDs or legacy hashes)
|
||||
*/
|
||||
function hasFilenameOptions(options: any[]): boolean {
|
||||
return options.some((opt: any) => {
|
||||
if (typeof opt !== 'string') return false
|
||||
// Check for common file extensions
|
||||
|
||||
// Check for UUID format (new system)
|
||||
const isUUID =
|
||||
/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(
|
||||
opt
|
||||
)
|
||||
|
||||
// Check for common file extensions (legacy)
|
||||
const hasExtension = FILE_EXTENSIONS.some((ext) =>
|
||||
opt.toLowerCase().endsWith(ext)
|
||||
)
|
||||
// Check for hash-like filenames (ComfyUI hashed files)
|
||||
|
||||
// Check for hash-like filenames (legacy ComfyUI hashed files)
|
||||
const isHashLike = /^[a-f0-9]{8,}\./i.test(opt)
|
||||
return hasExtension || isHashLike
|
||||
|
||||
return isUUID || hasExtension || isHashLike
|
||||
})
|
||||
}
|
||||
|
||||
@@ -98,9 +107,9 @@ function applyFilenameMappingToWidget(
|
||||
// Cast to extended interface for type safety
|
||||
const mappingWidget = widget as IFilenameMappingWidget
|
||||
|
||||
// Override serializeValue to ensure hash is used for API
|
||||
// Override serializeValue to ensure asset ID is used for API
|
||||
mappingWidget.serializeValue = function () {
|
||||
// Always return the actual widget value (hash) for serialization
|
||||
// Always return the actual widget value (asset ID) for serialization
|
||||
return mappingWidget.value
|
||||
}
|
||||
|
||||
@@ -110,16 +119,16 @@ function applyFilenameMappingToWidget(
|
||||
get() {
|
||||
if (mappingWidget.computedDisabled) return ''
|
||||
|
||||
// Get current hash value
|
||||
const hashValue = mappingWidget.value
|
||||
if (typeof hashValue !== 'string') return String(hashValue)
|
||||
// Get current asset ID value
|
||||
const assetId = mappingWidget.value
|
||||
if (typeof assetId !== 'string') return String(assetId)
|
||||
|
||||
// Try to get human-readable name from cache (deduplicated for display)
|
||||
const mapping = fileNameMappingService.getCachedMapping('input', true)
|
||||
const humanName = mapping[hashValue]
|
||||
const humanName = mapping[assetId]
|
||||
|
||||
// Return human name for display, fallback to hash
|
||||
return humanName || hashValue
|
||||
// Return human name for display, fallback to asset ID
|
||||
return humanName || assetId
|
||||
},
|
||||
configurable: true
|
||||
})
|
||||
@@ -237,19 +246,19 @@ function applyFilenameMappingToWidget(
|
||||
'input',
|
||||
true
|
||||
)
|
||||
const hashValue = reverseMapping[selectedValue] || selectedValue
|
||||
const assetId = reverseMapping[selectedValue] || selectedValue
|
||||
|
||||
// Set the hash value
|
||||
mappingWidget.value = hashValue
|
||||
// Set the asset ID
|
||||
mappingWidget.value = assetId
|
||||
|
||||
// Call original setValue with hash value if it exists
|
||||
// Call original setValue with asset ID if it exists
|
||||
if (originalSetValue) {
|
||||
originalSetValue.call(mappingWidget, hashValue, options)
|
||||
originalSetValue.call(mappingWidget, assetId, options)
|
||||
}
|
||||
|
||||
// Trigger callback with hash value
|
||||
// Trigger callback with asset ID
|
||||
if (mappingWidget.callback) {
|
||||
mappingWidget.callback.call(mappingWidget, hashValue)
|
||||
mappingWidget.callback.call(mappingWidget, assetId)
|
||||
}
|
||||
} else {
|
||||
mappingWidget.value = selectedValue
|
||||
@@ -273,14 +282,14 @@ function applyFilenameMappingToWidget(
|
||||
'input',
|
||||
true
|
||||
)
|
||||
const hashValue = reverseMapping[selectedValue] || selectedValue
|
||||
const assetId = reverseMapping[selectedValue] || selectedValue
|
||||
|
||||
// Set the hash value
|
||||
mappingWidget.value = hashValue
|
||||
// Set the asset ID
|
||||
mappingWidget.value = assetId
|
||||
|
||||
// Call original callback with hash value
|
||||
// Call original callback with asset ID
|
||||
if (originalCallback) {
|
||||
originalCallback.call(mappingWidget, hashValue)
|
||||
originalCallback.call(mappingWidget, assetId)
|
||||
}
|
||||
} else {
|
||||
mappingWidget.value = selectedValue
|
||||
|
||||
@@ -951,6 +951,16 @@ export class ComfyApp {
|
||||
async registerNodes() {
|
||||
// Load node definitions from the backend
|
||||
const defs = await this.getNodeDefs()
|
||||
|
||||
// Load filename mappings alongside node definitions for better UX
|
||||
import('@/services/fileNameMappingService').then(
|
||||
({ fileNameMappingService }) => {
|
||||
fileNameMappingService.ensureMappingsLoaded('input').catch(() => {
|
||||
// Silently fail - lazy loading will still work
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
await this.registerNodesFromDefs(defs)
|
||||
await useExtensionService().invokeExtensionsAsync('registerCustomNodes')
|
||||
if (this.vueAppReady) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { api } from '@/scripts/api'
|
||||
export type FileType = 'input' | 'output' | 'temp'
|
||||
|
||||
export interface FileNameMapping {
|
||||
[hashFilename: string]: string // hash -> human readable name
|
||||
[assetId: string]: string // asset-id -> asset name
|
||||
}
|
||||
|
||||
export interface CacheEntry {
|
||||
@@ -16,8 +16,8 @@ export interface CacheEntry {
|
||||
}
|
||||
|
||||
/**
|
||||
* Service for fetching and caching filename mappings from the backend.
|
||||
* Maps SHA256 hash filenames to their original human-readable names.
|
||||
* Service for fetching and caching asset mappings from the backend.
|
||||
* Maps asset IDs (UUIDs) to their human-readable asset names.
|
||||
*/
|
||||
export class FileNameMappingService {
|
||||
private cache = new Map<FileType, CacheEntry>()
|
||||
@@ -49,46 +49,46 @@ export class FileNameMappingService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get human-readable filename from hash filename.
|
||||
* @param hashFilename - The SHA256 hash filename
|
||||
* Get human-readable asset name from asset ID.
|
||||
* @param assetId - The asset ID (UUID)
|
||||
* @param fileType - The type of file
|
||||
* @returns Promise resolving to human-readable name or original if not found
|
||||
*/
|
||||
async getHumanReadableName(
|
||||
hashFilename: string,
|
||||
assetId: string,
|
||||
fileType: FileType = 'input'
|
||||
): Promise<string> {
|
||||
try {
|
||||
const mapping = await this.getMapping(fileType)
|
||||
return mapping[hashFilename] ?? hashFilename
|
||||
return mapping[assetId] ?? assetId
|
||||
} catch (error) {
|
||||
// Log error without exposing file paths
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn('Failed to get human readable name:', error)
|
||||
}
|
||||
return hashFilename
|
||||
return assetId
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filename mapping to an array of hash filenames.
|
||||
* @param hashFilenames - Array of SHA256 hash filenames
|
||||
* Apply asset mapping to an array of asset IDs.
|
||||
* @param assetIds - Array of asset IDs (UUIDs)
|
||||
* @param fileType - The type of files
|
||||
* @returns Promise resolving to array of human-readable names
|
||||
*/
|
||||
async applyMappingToArray(
|
||||
hashFilenames: string[],
|
||||
assetIds: string[],
|
||||
fileType: FileType = 'input'
|
||||
): Promise<string[]> {
|
||||
try {
|
||||
const mapping = await this.getMapping(fileType)
|
||||
return hashFilenames.map((filename) => mapping[filename] ?? filename)
|
||||
return assetIds.map((assetId) => mapping[assetId] ?? assetId)
|
||||
} catch (error) {
|
||||
// Log error without exposing sensitive data
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn('Failed to apply filename mapping')
|
||||
console.warn('Failed to apply asset mapping')
|
||||
}
|
||||
return hashFilenames
|
||||
return assetIds
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,12 +137,12 @@ export class FileNameMappingService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a human-readable name back to its hash filename.
|
||||
* @param humanName - The human-readable filename
|
||||
* Convert a human-readable name back to its asset ID.
|
||||
* @param humanName - The human-readable asset name
|
||||
* @param fileType - The file type
|
||||
* @returns The hash filename or the original if no mapping exists
|
||||
* @returns The asset ID or the original if no mapping exists
|
||||
*/
|
||||
getHashFromHumanName(
|
||||
getAssetIdFromHumanName(
|
||||
humanName: string,
|
||||
fileType: FileType = 'input'
|
||||
): string {
|
||||
@@ -351,7 +351,7 @@ export class FileNameMappingService {
|
||||
const currentIndex = (nameIndex.get(humanName) || 0) + 1
|
||||
nameIndex.set(humanName, currentIndex)
|
||||
|
||||
// Extract file extension if present
|
||||
// Extract file extension from human name if present
|
||||
const lastDotIndex = humanName.lastIndexOf('.')
|
||||
let baseName = humanName
|
||||
let extension = ''
|
||||
@@ -361,13 +361,25 @@ export class FileNameMappingService {
|
||||
extension = humanName.substring(lastDotIndex)
|
||||
}
|
||||
|
||||
// Add suffix: use first 8 chars of hash (without extension)
|
||||
// Remove extension from hash if present
|
||||
const hashWithoutExt = hash.includes('.')
|
||||
? hash.substring(0, hash.lastIndexOf('.'))
|
||||
: hash
|
||||
const hashSuffix = hashWithoutExt.substring(0, 8)
|
||||
dedupMapping[hash] = `${baseName}_${hashSuffix}${extension}`
|
||||
// Create suffix from hash/UUID
|
||||
let hashSuffix: string
|
||||
if (
|
||||
/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(
|
||||
hash
|
||||
)
|
||||
) {
|
||||
// UUID format - use first 8 characters
|
||||
hashSuffix = hash.substring(0, 8)
|
||||
} else {
|
||||
// Legacy hash format - remove extension if present and take first 8 chars
|
||||
const hashWithoutExt = hash.includes('.')
|
||||
? hash.substring(0, hash.lastIndexOf('.'))
|
||||
: hash
|
||||
hashSuffix = hashWithoutExt.substring(0, 8)
|
||||
}
|
||||
|
||||
const dedupName = `${baseName}_${hashSuffix}${extension}`
|
||||
dedupMapping[hash] = dedupName
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -324,8 +324,11 @@ describe('FileNameMappingService', () => {
|
||||
|
||||
await service.getMapping('input')
|
||||
|
||||
const hash = service.getHashFromHumanName('vacation_photo.png', 'input')
|
||||
expect(hash).toBe('abc123.png')
|
||||
const assetId = service.getAssetIdFromHumanName(
|
||||
'vacation_photo.png',
|
||||
'input'
|
||||
)
|
||||
expect(assetId).toBe('abc123.png')
|
||||
})
|
||||
|
||||
it('should return original name if no mapping exists', async () => {
|
||||
@@ -337,7 +340,7 @@ describe('FileNameMappingService', () => {
|
||||
|
||||
await service.getMapping('input')
|
||||
|
||||
const result = service.getHashFromHumanName('unknown.png', 'input')
|
||||
const result = service.getAssetIdFromHumanName('unknown.png', 'input')
|
||||
expect(result).toBe('unknown.png')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user