mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-07 00:20:07 +00:00
feat: add editable Model Type select to ModelInfoPanel
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<BaseModalLayout
|
||||
:hide-right-panel-button="true"
|
||||
:hide-right-panel-button="!!focusedAsset"
|
||||
:right-panel-open="!!focusedAsset"
|
||||
data-component-id="AssetBrowserModal"
|
||||
class="size-full max-h-full max-w-full min-w-0"
|
||||
@@ -87,6 +87,7 @@ import AssetGrid from '@/platform/assets/components/AssetGrid.vue'
|
||||
import ModelInfoPanel from '@/platform/assets/components/modelInfo/ModelInfoPanel.vue'
|
||||
import type { AssetDisplayItem } from '@/platform/assets/composables/useAssetBrowser'
|
||||
import { useAssetBrowser } from '@/platform/assets/composables/useAssetBrowser'
|
||||
import { useModelTypes } from '@/platform/assets/composables/useModelTypes'
|
||||
import { useModelUpload } from '@/platform/assets/composables/useModelUpload'
|
||||
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
|
||||
import { formatCategoryLabel } from '@/platform/assets/utils/categoryLabel'
|
||||
@@ -149,6 +150,10 @@ async function refreshAssets(): Promise<AssetItem[]> {
|
||||
// Trigger background refresh on mount
|
||||
void refreshAssets()
|
||||
|
||||
// Eagerly fetch model types so they're available when ModelInfoPanel loads
|
||||
const { fetchModelTypes } = useModelTypes()
|
||||
void fetchModelTypes()
|
||||
|
||||
const { isUploadButtonEnabled, showUploadDialog } =
|
||||
useModelUpload(refreshAssets)
|
||||
|
||||
|
||||
@@ -45,11 +45,20 @@
|
||||
{{ $t('assetBrowser.modelInfo.modelTagging') }}
|
||||
</span>
|
||||
</template>
|
||||
<ModelInfoField
|
||||
v-if="modelType"
|
||||
:label="$t('assetBrowser.modelInfo.modelType')"
|
||||
>
|
||||
<span class="text-sm">{{ modelType }}</span>
|
||||
<ModelInfoField :label="$t('assetBrowser.modelInfo.modelType')">
|
||||
<select
|
||||
v-model="selectedModelType"
|
||||
:disabled="isImmutable"
|
||||
class="w-full rounded-lg border-2 border-transparent bg-secondary-background px-3 py-2 text-sm text-base-foreground outline-none focus:border-node-component-border"
|
||||
>
|
||||
<option
|
||||
v-for="option in modelTypes"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.name }}
|
||||
</option>
|
||||
</select>
|
||||
</ModelInfoField>
|
||||
<ModelInfoField
|
||||
:label="$t('assetBrowser.modelInfo.compatibleBaseModels')"
|
||||
@@ -140,11 +149,13 @@ import TagsInputItem from '@/components/ui/tags-input/TagsInputItem.vue'
|
||||
import TagsInputItemDelete from '@/components/ui/tags-input/TagsInputItemDelete.vue'
|
||||
import TagsInputItemText from '@/components/ui/tags-input/TagsInputItemText.vue'
|
||||
import type { AssetDisplayItem } from '@/platform/assets/composables/useAssetBrowser'
|
||||
import { useModelTypes } from '@/platform/assets/composables/useModelTypes'
|
||||
import {
|
||||
getAssetAdditionalTags,
|
||||
getAssetBaseModels,
|
||||
getAssetDescription,
|
||||
getAssetDisplayName,
|
||||
getAssetModelType,
|
||||
getAssetSourceUrl,
|
||||
getAssetTriggerPhrases,
|
||||
getSourceName
|
||||
@@ -167,22 +178,22 @@ const sourceName = computed(() =>
|
||||
)
|
||||
const baseModels = ref<string[]>(getAssetBaseModels(asset))
|
||||
const additionalTags = ref<string[]>(getAssetAdditionalTags(asset))
|
||||
const selectedModelType = ref<string | undefined>(
|
||||
getAssetModelType(asset) ?? undefined
|
||||
)
|
||||
watch(
|
||||
() => asset,
|
||||
() => {
|
||||
baseModels.value = getAssetBaseModels(asset)
|
||||
additionalTags.value = getAssetAdditionalTags(asset)
|
||||
selectedModelType.value = getAssetModelType(asset) ?? undefined
|
||||
}
|
||||
)
|
||||
const description = computed(() => getAssetDescription(asset))
|
||||
const triggerPhrases = computed(() => getAssetTriggerPhrases(asset))
|
||||
const isImmutable = computed(() => asset.is_immutable ?? true)
|
||||
|
||||
const modelType = computed(() => {
|
||||
const typeTag = asset.tags.find((tag) => tag !== 'models')
|
||||
if (!typeTag) return null
|
||||
return typeTag.includes('/') ? typeTag.split('/').pop() : typeTag
|
||||
})
|
||||
const { modelTypes } = useModelTypes()
|
||||
|
||||
const assetsStore = useAssetsStore()
|
||||
|
||||
@@ -200,6 +211,19 @@ async function saveMetadata() {
|
||||
)
|
||||
}
|
||||
|
||||
async function saveModelType(newModelType: string | undefined) {
|
||||
if (isImmutable.value || !newModelType) return
|
||||
|
||||
const currentModelType = getAssetModelType(asset)
|
||||
if (currentModelType === newModelType) return
|
||||
|
||||
const newTags = asset.tags
|
||||
.filter((tag) => tag !== currentModelType)
|
||||
.concat(newModelType)
|
||||
await assetsStore.updateAssetTags(asset.id, newTags, cacheKey)
|
||||
}
|
||||
|
||||
watchDebounced(baseModels, saveMetadata, { debounce: 500 })
|
||||
watchDebounced(additionalTags, saveMetadata, { debounce: 500 })
|
||||
watchDebounced(selectedModelType, saveModelType, { debounce: 500 })
|
||||
</script>
|
||||
|
||||
@@ -46,9 +46,10 @@ const DISALLOWED_MODEL_TYPES = ['nlf'] as const
|
||||
export const useModelTypes = createSharedComposable(() => {
|
||||
const {
|
||||
state: modelTypes,
|
||||
isReady,
|
||||
isLoading,
|
||||
error,
|
||||
execute: fetchModelTypes
|
||||
execute
|
||||
} = useAsyncState(
|
||||
async (): Promise<ModelTypeOption[]> => {
|
||||
const response = await api.getModelFolders()
|
||||
@@ -74,6 +75,11 @@ export const useModelTypes = createSharedComposable(() => {
|
||||
}
|
||||
)
|
||||
|
||||
function fetchModelTypes() {
|
||||
if (isReady.value || isLoading.value) return
|
||||
return execute()
|
||||
}
|
||||
|
||||
return {
|
||||
modelTypes,
|
||||
isLoading,
|
||||
|
||||
@@ -110,3 +110,14 @@ export function getSourceName(url: string): string {
|
||||
if (url.includes('huggingface.co')) return 'Hugging Face'
|
||||
return 'Source'
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the model type from asset tags
|
||||
* @param asset - The asset to extract model type from
|
||||
* @returns The model type string or null if not present
|
||||
*/
|
||||
export function getAssetModelType(asset: AssetItem): string | null {
|
||||
const typeTag = asset.tags?.find((tag) => tag !== 'models')
|
||||
if (!typeTag) return null
|
||||
return typeTag.includes('/') ? (typeTag.split('/').pop() ?? null) : typeTag
|
||||
}
|
||||
|
||||
@@ -384,13 +384,29 @@ export const useAssetsStore = defineStore('assets', () => {
|
||||
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 {
|
||||
modelAssetsByNodeType,
|
||||
modelLoadingByNodeType,
|
||||
modelErrorByNodeType,
|
||||
updateModelsForNodeType,
|
||||
updateModelsForTag,
|
||||
updateAssetMetadata
|
||||
updateAssetMetadata,
|
||||
updateAssetTags
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,7 +416,8 @@ export const useAssetsStore = defineStore('assets', () => {
|
||||
modelErrorByNodeType: shallowReactive(new Map<string, Error | null>()),
|
||||
updateModelsForNodeType: async () => [],
|
||||
updateModelsForTag: async () => [],
|
||||
updateAssetMetadata: async () => {}
|
||||
updateAssetMetadata: async () => {},
|
||||
updateAssetTags: async () => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,7 +427,8 @@ export const useAssetsStore = defineStore('assets', () => {
|
||||
modelErrorByNodeType,
|
||||
updateModelsForNodeType,
|
||||
updateModelsForTag,
|
||||
updateAssetMetadata
|
||||
updateAssetMetadata,
|
||||
updateAssetTags
|
||||
} = getModelState()
|
||||
|
||||
// Watch for completed downloads and refresh model caches
|
||||
@@ -476,6 +494,7 @@ export const useAssetsStore = defineStore('assets', () => {
|
||||
modelErrorByNodeType,
|
||||
updateModelsForNodeType,
|
||||
updateModelsForTag,
|
||||
updateAssetMetadata
|
||||
updateAssetMetadata,
|
||||
updateAssetTags
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user