Files
ComfyUI_frontend/src/components/dialog/content/MissingModelsWarning.vue
Johnpaul Chiwetelu 25696ffe03 fix: remove false 'invalid directory' errors when asset API is enabled (#8961)
## Summary
- When `Comfy.Assets.UseAssetAPI` is enabled, `getAssetModelFolders()`
only discovers folders containing assets. Empty folders (e.g.
`text_encoders`, `vae`) were falsely flagged as invalid, showing
"Invalid directory specified" on every missing model dialog.
- Removes the `directory_invalid` concept entirely — the existing
`!paths` check via `getFolderPaths()` already correctly validates all
registered directories including empty ones, making `directory_invalid`
redundant.

## Before
<img width="1841" height="954" alt="Screenshot 2026-02-18 at 21 09 55"
src="https://github.com/user-attachments/assets/09cf4f28-5175-4ff6-aa9d-916568c6d9b3"
/>


## After
<img width="1134" height="738" alt="Screenshot 2026-02-18 at 21 23 29"
src="https://github.com/user-attachments/assets/578d2fa5-3fb8-401a-beee-0fd74667f08b"
/>

## Test plan
- [ ] Enable `Comfy.Assets.UseAssetAPI` setting
- [ ] Open a template referencing models in empty folders (e.g. "Image
Editing (New)")
- [ ] Verify the missing models dialog shows download buttons instead of
"Invalid directory specified" error
- [ ] Disable `Comfy.Assets.UseAssetAPI` and verify behavior is
unchanged

Fixes #8583
2026-02-19 20:51:21 -08:00

178 lines
5.3 KiB
Vue

<template>
<NoResultsPlaceholder
class="pb-0"
icon="pi pi-exclamation-circle"
:title="t('missingModelsDialog.missingModels')"
:message="t('missingModelsDialog.missingModelsMessage')"
/>
<div class="mb-4 flex flex-col gap-1">
<div class="flex gap-1">
<input
id="doNotAskAgain"
v-model="doNotAskAgain"
type="checkbox"
class="h-4 w-4 cursor-pointer"
/>
<label for="doNotAskAgain">{{
t('missingModelsDialog.doNotAskAgain')
}}</label>
</div>
<i18n-t
v-if="doNotAskAgain"
keypath="missingModelsDialog.reEnableInSettings"
tag="span"
class="text-sm text-muted-foreground ml-6"
>
<template #link>
<Button
variant="textonly"
class="underline cursor-pointer p-0 text-sm text-muted-foreground hover:bg-transparent"
@click="openShowMissingModelsSetting"
>
{{ t('missingModelsDialog.reEnableInSettingsLink') }}
</Button>
</template>
</i18n-t>
</div>
<ListBox :options="missingModels" class="comfy-missing-models">
<template #option="{ option }">
<Suspense v-if="isDesktop">
<ElectronFileDownload
:url="option.url"
:label="option.label"
:error="option.error"
/>
</Suspense>
<FileDownload
v-else
:url="option.url"
:label="option.label"
:error="option.error"
/>
</template>
</ListBox>
</template>
<script setup lang="ts">
import ListBox from 'primevue/listbox'
import { computed, onBeforeUnmount, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import ElectronFileDownload from '@/components/common/ElectronFileDownload.vue'
import FileDownload from '@/components/common/FileDownload.vue'
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import { isDesktop } from '@/platform/distribution/types'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useSettingsDialog } from '@/platform/settings/composables/useSettingsDialog'
import { useDialogStore } from '@/stores/dialogStore'
// TODO: Read this from server internal API rather than hardcoding here
// as some installations may wish to use custom sources
const allowedSources = [
'https://civitai.com/',
'https://huggingface.co/',
'http://localhost:' // Included for testing usage only
]
const allowedSuffixes = ['.safetensors', '.sft']
// Models that fail above conditions but are still allowed
const whiteListedUrls = new Set([
'https://huggingface.co/stabilityai/stable-zero123/resolve/main/stable_zero123.ckpt',
'https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_depth_sd14v1.pth?download=true',
'https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth'
])
interface ModelInfo {
name: string
directory: string
url: string
downloading?: boolean
completed?: boolean
progress?: number
error?: string
folder_path?: string
}
const props = defineProps<{
missingModels: ModelInfo[]
paths: Record<string, string[]>
}>()
const { t } = useI18n()
const doNotAskAgain = ref(false)
function openShowMissingModelsSetting() {
useDialogStore().closeDialog({ key: 'global-missing-models-warning' })
useSettingsDialog().show(undefined, 'Comfy.Workflow.ShowMissingModelsWarning')
}
const modelDownloads = ref<Record<string, ModelInfo>>({})
const missingModels = computed(() => {
return props.missingModels.map((model) => {
const paths = props.paths[model.directory]
if (!paths) {
return {
label: `${model.directory} / ${model.name}`,
url: model.url,
error: 'Invalid directory specified (does this require custom nodes?)'
}
}
const downloadInfo: ModelInfo = modelDownloads.value[model.name] ?? {
downloading: false,
completed: false,
progress: 0,
error: null,
name: model.name,
directory: model.directory,
url: model.url,
folder_path: paths[0]
}
modelDownloads.value[model.name] = downloadInfo
if (!whiteListedUrls.has(model.url)) {
if (!allowedSources.some((source) => model.url.startsWith(source))) {
return {
label: `${model.directory} / ${model.name}`,
url: model.url,
error: `Download not allowed from source '${model.url}', only allowed from '${allowedSources.join("', '")}'`
}
}
if (!allowedSuffixes.some((suffix) => model.name.endsWith(suffix))) {
return {
label: `${model.directory} / ${model.name}`,
url: model.url,
error: `Only allowed suffixes are: '${allowedSuffixes.join("', '")}'`
}
}
}
return {
url: model.url,
label: `${model.directory} / ${model.name}`,
downloading: downloadInfo.downloading,
completed: downloadInfo.completed,
progress: downloadInfo.progress,
error: downloadInfo.error,
name: model.name,
paths: paths,
folderPath: downloadInfo.folder_path
}
})
})
onBeforeUnmount(async () => {
if (doNotAskAgain.value) {
await useSettingStore().set(
'Comfy.Workflow.ShowMissingModelsWarning',
false
)
}
})
</script>
<style scoped>
.comfy-missing-models {
max-height: 300px;
overflow-y: auto;
}
</style>