Files
ComfyUI_frontend/src/platform/missingModel/components/MissingModelCard.vue
jaeone94 e2ef041170 feat: surface missing models in Error Tab for OSS and remove legacy dialog (#9921)
## Summary
- Surface missing models in the Error Tab for OSS environments,
replacing the legacy modal dialog
- Add Download button per model and Download All button in group header
with file size display
- Move download business logic from `components/dialog/content` to
`platform/missingModel`
- Remove legacy missing models dialog components and composable

## Changes
- **Pipeline**: Remove `isCloud` guard from `scanAllModelCandidates` and
`surfaceMissingModels` so OSS detects missing models
- **Grouping**: Group non-asset-supported models by directory in OSS
instead of lumping into UNSUPPORTED
- **UI**: Add Download button (matching Install Node Pack design) and
Download All header button
- **Store**: Add `folderPaths`/`fileSizes` state with setter methods,
race condition guard
- **Cleanup**: Delete `MissingModelsContent`, `MissingModelsHeader`,
`MissingModelsFooter`, `useMissingModelsDialog`, `missingModelsUtils`
- **Tests**: Add OSS/Cloud grouping tests, migrate Playwright E2E to
Error Tab, improve test isolation
- **Snapshots**: Reset Playwright screenshot expectations since OSS
missing model error detection now causes red highlights on affected
nodes
- **Accessibility**: Add `aria-label` with model name, `aria-expanded`
on toggle, warning icon for unknown category

## Test plan
- [x] Unit tests pass (86 tests)
- [x] TypeScript typecheck passes
- [x] knip passes
- [x] Load workflow with missing models in OSS → Error Tab shows missing
models grouped by directory
- [x] Download button triggers browser download with correct URL
- [x] Download All button downloads all downloadable models
- [x] Cloud environment behavior unchanged
- [x] Playwright E2E: `pnpm test:browser:local -- --grep "Missing models
in Error Tab"`

## Screenshots


https://github.com/user-attachments/assets/12f15e09-215a-4c58-87ed-39bbffd1359c

 


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9921-feat-surface-missing-models-in-Error-Tab-for-OSS-and-remove-legacy-dialog-3236d73d365081f0a9dfc291978f5ecf)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-03-15 22:46:47 +09:00

85 lines
2.7 KiB
Vue

<template>
<div class="px-4 pb-2">
<!-- Category groups (by directory) -->
<div
v-for="group in missingModelGroups"
:key="`${group.isAssetSupported ? 'supported' : 'unsupported'}::${group.directory ?? '__unknown__'}`"
class="flex w-full flex-col border-t border-interface-stroke py-2 first:border-t-0 first:pt-0"
>
<!-- Category header -->
<div class="flex h-8 w-full items-center">
<p
class="min-w-0 flex-1 truncate text-sm font-medium"
:class="
(isCloud && !group.isAssetSupported) || group.directory === null
? 'text-warning-background'
: 'text-destructive-background-hover'
"
>
<span v-if="isCloud && !group.isAssetSupported">
{{ t('rightSidePanel.missingModels.importNotSupported') }}
({{ group.models.length }})
</span>
<span v-else>
<i
v-if="group.directory === null"
aria-hidden="true"
class="mr-1 icon-[lucide--triangle-alert] size-3.5 align-text-bottom"
/>
{{
group.directory ??
t('rightSidePanel.missingModels.unknownCategory')
}}
({{ group.models.length }})
</span>
</p>
</div>
<!-- Asset unsupported group notice -->
<div
v-if="isCloud && !group.isAssetSupported"
class="flex items-start gap-1.5 px-0.5 py-1 pl-2"
>
<i
aria-hidden="true"
class="mt-0.5 icon-[lucide--info] size-3.5 shrink-0 text-muted-foreground"
/>
<span class="text-xs/tight text-muted-foreground">
{{ t('rightSidePanel.missingModels.customNodeDownloadDisabled') }}
</span>
</div>
<!-- Model rows -->
<div class="flex flex-col gap-1 overflow-hidden pl-2">
<MissingModelRow
v-for="model in group.models"
:key="model.name"
:model="model"
:directory="group.directory"
:show-node-id-badge="showNodeIdBadge"
:is-asset-supported="group.isAssetSupported"
@locate-model="emit('locateModel', $event)"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import type { MissingModelGroup } from '@/platform/missingModel/types'
import { isCloud } from '@/platform/distribution/types'
import MissingModelRow from '@/platform/missingModel/components/MissingModelRow.vue'
const { missingModelGroups, showNodeIdBadge } = defineProps<{
missingModelGroups: MissingModelGroup[]
showNodeIdBadge: boolean
}>()
const emit = defineEmits<{
locateModel: [nodeId: string]
}>()
const { t } = useI18n()
</script>