mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-21 21:09:00 +00:00
Compare commits
1 Commits
fix/node-l
...
remove-tea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e587824d84 |
@@ -117,17 +117,7 @@
|
||||
<template #body>
|
||||
<NodeDragPreview />
|
||||
<div class="flex h-full flex-col">
|
||||
<div
|
||||
v-if="hasNoMatches"
|
||||
class="flex min-h-0 flex-1 items-center justify-center px-6 py-8 text-center text-sm text-muted-foreground"
|
||||
>
|
||||
{{
|
||||
$t('sideToolbar.nodeLibraryTab.noMatchingNodes', {
|
||||
query: searchQuery
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
<div v-else class="min-h-0 flex-1 overflow-y-auto py-2">
|
||||
<div class="min-h-0 flex-1 overflow-y-auto py-2">
|
||||
<TabPanel
|
||||
v-if="flags.nodeLibraryEssentialsEnabled"
|
||||
:model-value="selectedTab"
|
||||
@@ -284,13 +274,9 @@ const filteredNodeDefs = computed(() => {
|
||||
})
|
||||
|
||||
const activeNodes = computed(() =>
|
||||
searchQuery.value.length === 0
|
||||
? nodeDefStore.visibleNodeDefs
|
||||
: filteredNodeDefs.value
|
||||
)
|
||||
|
||||
const hasNoMatches = computed(
|
||||
() => searchQuery.value.length > 0 && filteredNodeDefs.value.length === 0
|
||||
filteredNodeDefs.value.length > 0
|
||||
? filteredNodeDefs.value
|
||||
: nodeDefStore.visibleNodeDefs
|
||||
)
|
||||
|
||||
const sections = computed(() => {
|
||||
|
||||
@@ -903,7 +903,6 @@
|
||||
"alphabetical": "A-Z",
|
||||
"alphabeticalDesc": "Sort alphabetically within groups"
|
||||
},
|
||||
"noMatchingNodes": "No nodes match \"{query}\"",
|
||||
"sections": {
|
||||
"favorites": "Bookmarks",
|
||||
"favoriteNode": "Bookmark Node",
|
||||
@@ -2342,8 +2341,6 @@
|
||||
"plansForWorkspace": "Plans for {workspace}",
|
||||
"personalWorkspace": "Personal Workspace",
|
||||
"teamWorkspace": "Team Workspace",
|
||||
"soloUseOnly": "Solo use only",
|
||||
"needTeamWorkspace": "Need team workspace?",
|
||||
"inviteUpTo": "Invite up to",
|
||||
"title": "Subscription",
|
||||
"titleUnsubscribed": "Subscribe to Comfy Cloud",
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { mapInputFileToAssetItem } from './assetMappers'
|
||||
|
||||
vi.mock('@/scripts/api', () => ({
|
||||
api: {
|
||||
apiURL: (path: string) => `/api${path}`
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/platform/distribution/cloudPreviewUtil', () => ({
|
||||
appendCloudResParam: vi.fn()
|
||||
}))
|
||||
|
||||
describe('mapInputFileToAssetItem', () => {
|
||||
it('preserves a clean filename', () => {
|
||||
const asset = mapInputFileToAssetItem('photo.png', 0, 'input')
|
||||
|
||||
expect(asset.name).toBe('photo.png')
|
||||
expect(asset.id).toBe('input-0-photo.png')
|
||||
expect(asset.preview_url).toBe('/api/view?filename=photo.png&type=input')
|
||||
})
|
||||
|
||||
it.each([
|
||||
['photo.png [input]', 'photo.png'],
|
||||
['photo.png [output]', 'photo.png'],
|
||||
['photo.png [temp]', 'photo.png'],
|
||||
['clip.mp4[input]', 'clip.mp4'],
|
||||
['MyFile.WEBP [Input]', 'MyFile.WEBP']
|
||||
])('strips ComfyUI directory annotation: %s -> %s', (input, expectedName) => {
|
||||
const asset = mapInputFileToAssetItem(input, 1, 'input')
|
||||
|
||||
expect(asset.name).toBe(expectedName)
|
||||
expect(asset.id).toBe(`input-1-${expectedName}`)
|
||||
expect(asset.preview_url).toBe(
|
||||
`/api/view?filename=${encodeURIComponent(expectedName)}&type=input`
|
||||
)
|
||||
})
|
||||
|
||||
it('leaves non-annotation brackets in the filename intact', () => {
|
||||
const asset = mapInputFileToAssetItem('my [draft] image.png', 0, 'input')
|
||||
|
||||
expect(asset.name).toBe('my [draft] image.png')
|
||||
})
|
||||
|
||||
it('uses the directory passed in for the type query param', () => {
|
||||
const asset = mapInputFileToAssetItem('clip.mp4 [output]', 0, 'output')
|
||||
|
||||
expect(asset.preview_url).toBe('/api/view?filename=clip.mp4&type=output')
|
||||
expect(asset.tags).toEqual(['output'])
|
||||
})
|
||||
})
|
||||
@@ -51,17 +51,6 @@ export function mapTaskOutputToAssetItem(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips ComfyUI's trailing directory-type annotation (e.g. ` [input]`,
|
||||
* ` [output]`, `[temp]`) from a filename returned by the OSS internal
|
||||
* `/internal/files/{type}` endpoint. The annotation is part of the wire
|
||||
* format LoadImage-style widgets expect, but for the assets sidebar we
|
||||
* want the canonical on-disk filename so type detection / titles work.
|
||||
*/
|
||||
function stripDirectoryAnnotation(filename: string): string {
|
||||
return filename.replace(/\s*\[(?:input|output|temp)\]\s*$/i, '')
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps input directory file to AssetItem format
|
||||
* @param filename The filename
|
||||
@@ -74,14 +63,13 @@ export function mapInputFileToAssetItem(
|
||||
index: number,
|
||||
directory: 'input' | 'output' = 'input'
|
||||
): AssetItem {
|
||||
const cleanName = stripDirectoryAnnotation(filename)
|
||||
const params = new URLSearchParams({ filename: cleanName, type: directory })
|
||||
const params = new URLSearchParams({ filename, type: directory })
|
||||
const preview_url = api.apiURL(`/view?${params}`)
|
||||
appendCloudResParam(params, cleanName)
|
||||
appendCloudResParam(params, filename)
|
||||
|
||||
return {
|
||||
id: `${directory}-${index}-${cleanName}`,
|
||||
name: cleanName,
|
||||
id: `${directory}-${index}-${filename}`,
|
||||
name: filename,
|
||||
size: 0,
|
||||
created_at: new Date().toISOString(),
|
||||
tags: [directory],
|
||||
|
||||
@@ -52,6 +52,9 @@ export const MODEL_NODE_MAPPINGS: ReadonlyArray<
|
||||
// ---- SAM3 3D segmentation (comfyui-sam3) ----
|
||||
['sam3', 'LoadSAM3Model', 'model_path'],
|
||||
|
||||
// ---- Ultralytics detection (comfyui-impact-subpack) ----
|
||||
['ultralytics', 'UltralyticsDetectorProvider', 'model_name'],
|
||||
|
||||
// ---- DepthAnything (comfyui-depthanythingv2, comfyui-depthanythingv3) ----
|
||||
['depthanything', 'DownloadAndLoadDepthAnythingV2Model', 'model'],
|
||||
['depthanything3', 'DownloadAndLoadDepthAnythingV3Model', 'model'],
|
||||
|
||||
@@ -151,8 +151,6 @@ const i18n = createI18n({
|
||||
videoEstimateHelp: 'How is this calculated?',
|
||||
videoEstimateExplanation: 'Based on average usage.',
|
||||
videoEstimateTryTemplate: 'Try template',
|
||||
soloUseOnly: 'Solo use only',
|
||||
needTeamWorkspace: 'Need team workspace?',
|
||||
maxDuration: {
|
||||
standard: '30 min',
|
||||
creator: '30 min',
|
||||
@@ -177,9 +175,6 @@ const i18n = createI18n({
|
||||
|
||||
function renderComponent() {
|
||||
return render(PricingTable, {
|
||||
props: {
|
||||
onChooseTeamWorkspace: onChooseTeamWorkspace
|
||||
},
|
||||
global: {
|
||||
plugins: [createTestingPinia({ createSpy: vi.fn }), i18n],
|
||||
components: {
|
||||
@@ -210,8 +205,6 @@ function renderComponent() {
|
||||
})
|
||||
}
|
||||
|
||||
const onChooseTeamWorkspace = vi.fn()
|
||||
|
||||
describe('PricingTable', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
@@ -423,20 +416,4 @@ describe('PricingTable', () => {
|
||||
expect(mockAccessBillingPortal).toHaveBeenCalledWith('standard-yearly')
|
||||
})
|
||||
})
|
||||
|
||||
describe('team workspace link', () => {
|
||||
it('should emit chooseTeamWorkspace when clicking "Need team workspace?" link', async () => {
|
||||
renderComponent()
|
||||
await flushPromises()
|
||||
|
||||
const teamLink = screen
|
||||
.getAllByRole('button')
|
||||
.find((b) => b.textContent?.includes('Need team workspace?'))
|
||||
|
||||
expect(teamLink).toBeDefined()
|
||||
await userEvent.click(teamLink!)
|
||||
|
||||
expect(onChooseTeamWorkspace).toHaveBeenCalledOnce()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -95,21 +95,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p
|
||||
role="note"
|
||||
:aria-label="t('subscription.soloUseOnly')"
|
||||
class="m-0 flex h-10 items-center rounded-lg bg-muted-foreground/30 px-3 text-sm text-muted-foreground"
|
||||
>
|
||||
{{ t('subscription.soloUseOnly') }}
|
||||
<span class="mx-1 text-muted-foreground">–</span>
|
||||
<button
|
||||
class="text-primary-foreground cursor-pointer border-none bg-transparent p-0 text-sm font-medium underline hover:text-base-foreground focus-visible:ring-1 focus-visible:outline-none"
|
||||
@click="emit('chooseTeamWorkspace')"
|
||||
>
|
||||
{{ t('subscription.needTeamWorkspace') }}
|
||||
</button>
|
||||
</p>
|
||||
|
||||
<div class="flex flex-1 flex-col gap-3 pb-0">
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<span
|
||||
@@ -318,10 +303,6 @@ interface PricingTierConfig {
|
||||
isPopular?: boolean
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
chooseTeamWorkspace: []
|
||||
}>()
|
||||
|
||||
const { t, n } = useI18n()
|
||||
|
||||
const billingCycleOptions: BillingCycleOption[] = [
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<PricingTable class="flex-1" @choose-team-workspace="handleChooseTeam" />
|
||||
<PricingTable class="flex-1" />
|
||||
|
||||
<!-- Contact and Enterprise Links -->
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
@@ -159,10 +159,9 @@ import { useTelemetry } from '@/platform/telemetry'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import type { SubscriptionDialogReason } from '@/platform/cloud/subscription/composables/useSubscriptionDialog'
|
||||
|
||||
const { onClose, reason, onChooseTeam } = defineProps<{
|
||||
const { onClose, reason } = defineProps<{
|
||||
onClose: () => void
|
||||
reason?: SubscriptionDialogReason
|
||||
onChooseTeam?: () => void
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -203,14 +202,6 @@ const handleSubscribed = () => {
|
||||
emit('close', true)
|
||||
}
|
||||
|
||||
const handleChooseTeam = () => {
|
||||
if (onChooseTeam) {
|
||||
onChooseTeam()
|
||||
} else {
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
onClose()
|
||||
}
|
||||
|
||||
@@ -43,12 +43,7 @@ export const useSubscriptionDialog = () => {
|
||||
import('@/platform/cloud/subscription/components/SubscriptionRequiredDialogContent.vue')
|
||||
)
|
||||
|
||||
const personalProps = {
|
||||
onClose: hide,
|
||||
reason: options?.reason,
|
||||
onChooseTeam: () => startTeamWorkspaceUpgradeFlow()
|
||||
}
|
||||
const workspaceProps = {
|
||||
const dialogProps = {
|
||||
onClose: hide,
|
||||
reason: options?.reason
|
||||
}
|
||||
@@ -56,7 +51,7 @@ export const useSubscriptionDialog = () => {
|
||||
dialogService.showLayoutDialog({
|
||||
key: DIALOG_KEY,
|
||||
component,
|
||||
props: useWorkspaceVariant ? workspaceProps : personalProps,
|
||||
props: dialogProps,
|
||||
dialogComponentProps: {
|
||||
style: 'width: min(1328px, 95vw); max-height: 958px;',
|
||||
pt: {
|
||||
|
||||
@@ -31,6 +31,7 @@ const EXPECTED_DEFAULT_TYPES = [
|
||||
'latent_upscale_models',
|
||||
'sam2',
|
||||
'sams',
|
||||
'ultralytics',
|
||||
'depthanything',
|
||||
'ipadapter',
|
||||
'segformer_b2_clothes',
|
||||
@@ -83,6 +84,7 @@ const MOCK_NODE_NAMES = [
|
||||
'LatentUpscaleModelLoader',
|
||||
'DownloadAndLoadSAM2Model',
|
||||
'SAMLoader',
|
||||
'UltralyticsDetectorProvider',
|
||||
'DownloadAndLoadDepthAnythingV2Model',
|
||||
'IPAdapterModelLoader',
|
||||
'LS_LoadSegformerModel',
|
||||
@@ -258,6 +260,8 @@ describe('useModelToNodeStore', () => {
|
||||
['sams', 'SAMLoader', 'model_name'],
|
||||
['ipadapter', 'IPAdapterModelLoader', 'ipadapter_file'],
|
||||
['depthanything', 'DownloadAndLoadDepthAnythingV2Model', 'model'],
|
||||
['ultralytics/bbox', 'UltralyticsDetectorProvider', 'model_name'],
|
||||
['ultralytics/segm', 'UltralyticsDetectorProvider', 'model_name'],
|
||||
['FlashVSR', 'FlashVSRNode', ''],
|
||||
['FlashVSR-v1.1', 'FlashVSRNode', ''],
|
||||
['segformer_b2_clothes', 'LS_LoadSegformerModel', 'model_name'],
|
||||
@@ -273,16 +277,6 @@ describe('useModelToNodeStore', () => {
|
||||
expect(provider?.key).toBe(expectedKey)
|
||||
}
|
||||
)
|
||||
|
||||
it.each([['ultralytics'], ['ultralytics/bbox'], ['ultralytics/segm']])(
|
||||
'should not register %s as a default provider, so the node falls back to its static combo (regression for #8468)',
|
||||
(modelType) => {
|
||||
const modelToNodeStore = useModelToNodeStore()
|
||||
modelToNodeStore.registerDefaults()
|
||||
|
||||
expect(modelToNodeStore.getNodeProvider(modelType)).toBeUndefined()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('getAllNodeProviders', () => {
|
||||
|
||||
Reference in New Issue
Block a user