From c24ec68b5672d2cd0fc1a1b347eabe3e564bfc71 Mon Sep 17 00:00:00 2001 From: jaeone94 <89377375+jaeone94@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:06:29 +0900 Subject: [PATCH 1/9] feat: add gated Hugging Face model access hints --- .../comfyDesktopBridge.d.ts | 3 + .../comfyui-desktop-bridge-types/package.json | 2 +- src/locales/en/main.json | 3 + .../components/MissingModelCard.test.ts | 64 +++++++++++ .../components/MissingModelCard.vue | 29 ++++- .../components/MissingModelRow.test.ts | 103 +++++++++++++++++- .../components/MissingModelRow.vue | 59 ++++++---- .../composables/useMissingModelDownload.ts | 54 +++++++++ .../missingModel/missingModelDownload.test.ts | 52 +++++++-- .../missingModel/missingModelDownload.ts | 20 +++- .../missingModel/missingModelPipeline.test.ts | 45 +++++++- .../missingModel/missingModelPipeline.ts | 4 + .../missingModel/missingModelStore.test.ts | 16 +++ .../missingModel/missingModelStore.ts | 10 +- 14 files changed, 425 insertions(+), 39 deletions(-) create mode 100644 src/platform/missingModel/composables/useMissingModelDownload.ts diff --git a/packages/comfyui-desktop-bridge-types/comfyDesktopBridge.d.ts b/packages/comfyui-desktop-bridge-types/comfyDesktopBridge.d.ts index a7c51e63870..629b3f0f2eb 100644 --- a/packages/comfyui-desktop-bridge-types/comfyDesktopBridge.d.ts +++ b/packages/comfyui-desktop-bridge-types/comfyDesktopBridge.d.ts @@ -70,6 +70,9 @@ export interface ComfyDesktop2TelemetryBridge { export interface ComfyDesktop2Bridge { isRemote(): boolean + /** Opens a model provider access page in the hosted frontend's browser session. + * Resolves `true` when the host accepts the request. */ + openModelAccessPage?: (url: string) => Promise downloadModel?: ( url: string, filename: string, diff --git a/packages/comfyui-desktop-bridge-types/package.json b/packages/comfyui-desktop-bridge-types/package.json index c3cdbe2a5db..d61214b04f6 100644 --- a/packages/comfyui-desktop-bridge-types/package.json +++ b/packages/comfyui-desktop-bridge-types/package.json @@ -1,6 +1,6 @@ { "name": "@comfyorg/comfyui-desktop-bridge-types", - "version": "0.1.3", + "version": "0.1.4", "description": "TypeScript definitions for the Comfy Desktop hosted frontend bridge", "homepage": "https://comfy.org", "license": "MIT", diff --git a/src/locales/en/main.json b/src/locales/en/main.json index 399226430d0..37eed13eaca 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -3991,7 +3991,10 @@ "importNotSupported": "Import Not Supported", "copyModelName": "Copy model name", "copyUrl": "Copy URL", + "gatedModelsHint": "Some models are gated. To download them, sign in to Hugging Face and accept the model license agreement.", + "gatedModelTooltip": "This model is gated and requires you to be logged in to Hugging Face and to accept its license agreement.", "locateNode": "Locate node on canvas", + "openHuggingFaceRepo": "Open Hugging Face repo", "expandNodes": "Show referencing nodes", "collapseNodes": "Hide referencing nodes", "unknownCategory": "Unknown", diff --git a/src/platform/missingModel/components/MissingModelCard.test.ts b/src/platform/missingModel/components/MissingModelCard.test.ts index e5eccda9834..1e4ed26036d 100644 --- a/src/platform/missingModel/components/MissingModelCard.test.ts +++ b/src/platform/missingModel/components/MissingModelCard.test.ts @@ -3,6 +3,7 @@ import { render, screen, within } from '@testing-library/vue' import userEvent from '@testing-library/user-event' import PrimeVue from 'primevue/config' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { nextTick } from 'vue' import { createI18n } from 'vue-i18n' import enMessages from '@/locales/en/main.json' with { type: 'json' } @@ -10,6 +11,20 @@ import type { MissingModelGroup, MissingModelViewModel } from '@/platform/missingModel/types' +import type * as MissingModelDownload from '@/platform/missingModel/missingModelDownload' +import { useMissingModelStore } from '@/platform/missingModel/missingModelStore' + +const mockDownloadModel = vi.hoisted(() => vi.fn()) + +vi.mock('@/platform/missingModel/missingModelDownload', async () => { + const actual = await vi.importActual( + '@/platform/missingModel/missingModelDownload' + ) + return { + ...actual, + downloadModel: mockDownloadModel + } +}) vi.mock('./MissingModelRow.vue', () => ({ default: { @@ -131,6 +146,7 @@ function getRowsIn(testId: string) { describe('MissingModelCard', () => { beforeEach(() => { + vi.clearAllMocks() mockIsCloud.value = true }) @@ -241,6 +257,21 @@ describe('MissingModelCard', () => { screen.queryByTestId('missing-model-actions') ).not.toBeInTheDocument() }) + + it('does not show gated model guidance in cloud', async () => { + const group = makeGroup({ withDownloadUrls: true }) + const url = + 'https://huggingface.co/comfy/test/resolve/main/model.safetensors' + mountCard({ missingModelGroups: [group] }) + + useMissingModelStore().gatedRepoUrls[url] = + 'https://huggingface.co/comfy/test' + await nextTick() + + expect( + screen.queryByTestId('missing-model-gated-hint') + ).not.toBeInTheDocument() + }) }) describe('Event Handling', () => { @@ -256,6 +287,7 @@ describe('MissingModelCard', () => { describe('MissingModelCard (OSS)', () => { beforeEach(() => { + vi.clearAllMocks() mockIsCloud.value = false }) @@ -293,6 +325,38 @@ describe('MissingModelCard (OSS)', () => { ).toBeVisible() }) + it('shows gated model guidance in OSS', async () => { + const group = makeGroup({ withDownloadUrls: true }) + const url = + 'https://huggingface.co/comfy/test/resolve/main/model.safetensors' + mountCard({ missingModelGroups: [group] }) + + useMissingModelStore().gatedRepoUrls[url] = + 'https://huggingface.co/comfy/test' + await nextTick() + + expect(screen.getByTestId('missing-model-gated-hint')).toHaveTextContent( + 'Some models are gated. To download them, sign in to Hugging Face and accept the model license agreement.' + ) + }) + + it('routes Download all through the shared missing-model download handler', async () => { + mountCard({ + missingModelGroups: [makeGroup({ withDownloadUrls: true })] + }) + + await userEvent.click(screen.getByTestId('missing-model-download-all')) + + expect(mockDownloadModel).toHaveBeenCalledWith( + { + name: 'model.safetensors', + url: 'https://huggingface.co/comfy/test/resolve/main/model.safetensors', + directory: 'checkpoints' + }, + {} + ) + }) + it('hides Download all when no model is downloadable', () => { mountCard() diff --git a/src/platform/missingModel/components/MissingModelCard.vue b/src/platform/missingModel/components/MissingModelCard.vue index 6e7ec5a1ffd..7316ea07895 100644 --- a/src/platform/missingModel/components/MissingModelCard.vue +++ b/src/platform/missingModel/components/MissingModelCard.vue @@ -1,5 +1,20 @@