feat: add editable Model Type select to ModelInfoPanel

This commit is contained in:
Alexander Brown
2026-01-16 16:33:03 -08:00
parent d6a64cadb8
commit d284c0eaf7
5 changed files with 81 additions and 16 deletions

View File

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

View File

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

View File

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

View File

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

View File

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