mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-29 10:42:44 +00:00
feat(assets): add ModelInfoPanel for asset browser right panel (#8090)
## Summary Adds an editable Model Info Panel to show and modify asset details in the asset browser. ## Changes - Add `ModelInfoPanel` component with editable display name, description, model type, base models, and tags - Add `updateAssetMetadata` action in `assetsStore` with optimistic cache updates - Add shadcn-vue `Select` components with design system styling - Add utility functions in `assetMetadataUtils` for extracting model metadata - Convert `BaseModalLayout` right panel state to `defineModel` pattern - Add slide-in animation and collapse button for right panel - Add `class` prop to `PropertiesAccordionItem` for custom styling - Fix keyboard handling: Escape in TagsInput/TextArea doesn't close parent modal ## Testing - Unit tests for `ModelInfoPanel` component - Unit tests for `assetMetadataUtils` functions --------- Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
@@ -319,8 +319,8 @@ describe('assetsStore - Refactored (Option A)', () => {
|
||||
|
||||
// Verify sorting (newest first - lower index = newer)
|
||||
for (let i = 1; i < store.historyAssets.length; i++) {
|
||||
const prevDate = new Date(store.historyAssets[i - 1].created_at)
|
||||
const currDate = new Date(store.historyAssets[i].created_at)
|
||||
const prevDate = new Date(store.historyAssets[i - 1].created_at ?? 0)
|
||||
const currDate = new Date(store.historyAssets[i].created_at ?? 0)
|
||||
expect(prevDate.getTime()).toBeGreaterThanOrEqual(currDate.getTime())
|
||||
}
|
||||
})
|
||||
@@ -435,8 +435,8 @@ describe('assetsStore - Refactored (Option A)', () => {
|
||||
|
||||
// Should still maintain sorting
|
||||
for (let i = 1; i < store.historyAssets.length; i++) {
|
||||
const prevDate = new Date(store.historyAssets[i - 1].created_at)
|
||||
const currDate = new Date(store.historyAssets[i].created_at)
|
||||
const prevDate = new Date(store.historyAssets[i - 1].created_at ?? 0)
|
||||
const currDate = new Date(store.historyAssets[i].created_at ?? 0)
|
||||
expect(prevDate.getTime()).toBeGreaterThanOrEqual(currDate.getTime())
|
||||
}
|
||||
})
|
||||
|
||||
@@ -78,7 +78,8 @@ function mapHistoryToAssets(historyItems: JobListItem[]): AssetItem[] {
|
||||
|
||||
return assetItems.sort(
|
||||
(a, b) =>
|
||||
new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
||||
new Date(b.created_at ?? 0).getTime() -
|
||||
new Date(a.created_at ?? 0).getTime()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -145,9 +146,9 @@ export const useAssetsStore = defineStore('assets', () => {
|
||||
loadedIds.add(asset.id)
|
||||
|
||||
// Find insertion index to maintain sorted order (newest first)
|
||||
const assetTime = new Date(asset.created_at).getTime()
|
||||
const assetTime = new Date(asset.created_at ?? 0).getTime()
|
||||
const insertIndex = allHistoryItems.value.findIndex(
|
||||
(item) => new Date(item.created_at).getTime() < assetTime
|
||||
(item) => new Date(item.created_at ?? 0).getTime() < assetTime
|
||||
)
|
||||
|
||||
if (insertIndex === -1) {
|
||||
@@ -321,6 +322,10 @@ export const useAssetsStore = defineStore('assets', () => {
|
||||
return modelStateByKey.value.get(key)?.hasMore ?? false
|
||||
}
|
||||
|
||||
function hasAssetKey(key: string): boolean {
|
||||
return modelStateByKey.value.has(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper to fetch and cache assets with a given key and fetcher.
|
||||
* Loads first batch immediately, then progressively loads remaining batches.
|
||||
@@ -419,13 +424,75 @@ export const useAssetsStore = defineStore('assets', () => {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimistically update an asset in the cache
|
||||
* @param assetId The asset ID to update
|
||||
* @param updates Partial asset data to merge
|
||||
* @param cacheKey Optional cache key to target (nodeType or 'tag:xxx')
|
||||
*/
|
||||
function updateAssetInCache(
|
||||
assetId: string,
|
||||
updates: Partial<AssetItem>,
|
||||
cacheKey?: string
|
||||
) {
|
||||
const keysToCheck = cacheKey
|
||||
? [cacheKey]
|
||||
: Array.from(modelStateByKey.value.keys())
|
||||
|
||||
for (const key of keysToCheck) {
|
||||
const state = modelStateByKey.value.get(key)
|
||||
if (!state?.assets) continue
|
||||
|
||||
const existingAsset = state.assets.get(assetId)
|
||||
if (existingAsset) {
|
||||
const updatedAsset = { ...existingAsset, ...updates }
|
||||
state.assets.set(assetId, updatedAsset)
|
||||
assetsArrayCache.delete(key)
|
||||
if (cacheKey) return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update asset metadata with optimistic cache update
|
||||
* @param assetId The asset ID to update
|
||||
* @param userMetadata The user_metadata to save
|
||||
* @param cacheKey Optional cache key to target for optimistic update
|
||||
*/
|
||||
async function updateAssetMetadata(
|
||||
assetId: string,
|
||||
userMetadata: Record<string, unknown>,
|
||||
cacheKey?: string
|
||||
) {
|
||||
updateAssetInCache(assetId, { user_metadata: userMetadata }, cacheKey)
|
||||
await assetService.updateAsset(assetId, { user_metadata: userMetadata })
|
||||
}
|
||||
|
||||
/**
|
||||
* Update asset tags with optimistic cache update
|
||||
* @param assetId The asset ID to update
|
||||
* @param tags The tags array to save
|
||||
* @param cacheKey Optional cache key to target for optimistic update
|
||||
*/
|
||||
async function updateAssetTags(
|
||||
assetId: string,
|
||||
tags: string[],
|
||||
cacheKey?: string
|
||||
) {
|
||||
updateAssetInCache(assetId, { tags }, cacheKey)
|
||||
await assetService.updateAsset(assetId, { tags })
|
||||
}
|
||||
|
||||
return {
|
||||
getAssets,
|
||||
isLoading,
|
||||
getError,
|
||||
hasMore,
|
||||
hasAssetKey,
|
||||
updateModelsForNodeType,
|
||||
updateModelsForTag
|
||||
updateModelsForTag,
|
||||
updateAssetMetadata,
|
||||
updateAssetTags
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,8 +502,11 @@ export const useAssetsStore = defineStore('assets', () => {
|
||||
isLoading: () => false,
|
||||
getError: () => undefined,
|
||||
hasMore: () => false,
|
||||
hasAssetKey: () => false,
|
||||
updateModelsForNodeType: async () => {},
|
||||
updateModelsForTag: async () => {}
|
||||
updateModelsForTag: async () => {},
|
||||
updateAssetMetadata: async () => {},
|
||||
updateAssetTags: async () => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,8 +515,11 @@ export const useAssetsStore = defineStore('assets', () => {
|
||||
isLoading: isModelLoading,
|
||||
getError,
|
||||
hasMore,
|
||||
hasAssetKey,
|
||||
updateModelsForNodeType,
|
||||
updateModelsForTag
|
||||
updateModelsForTag,
|
||||
updateAssetMetadata,
|
||||
updateAssetTags
|
||||
} = getModelState()
|
||||
|
||||
// Watch for completed downloads and refresh model caches
|
||||
@@ -511,9 +584,12 @@ export const useAssetsStore = defineStore('assets', () => {
|
||||
isModelLoading,
|
||||
getError,
|
||||
hasMore,
|
||||
hasAssetKey,
|
||||
|
||||
// Model assets - actions
|
||||
updateModelsForNodeType,
|
||||
updateModelsForTag
|
||||
updateModelsForTag,
|
||||
updateAssetMetadata,
|
||||
updateAssetTags
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user