fix: replace uuids with displaynames rather than shas

This commit is contained in:
Richard Yu
2025-09-03 22:06:12 -07:00
committed by Arjan Singh
parent d6b533124b
commit 89a68b92d2
4 changed files with 87 additions and 53 deletions

View File

@@ -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

View File

@@ -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) {

View File

@@ -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
}
}

View File

@@ -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')
})
})