mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-20 14:54:12 +00:00
[Hotfix] Cherry-pick fixes to core/1.24 (#4790)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -9,8 +9,7 @@ test.describe('Graph Canvas Menu', () => {
|
||||
await comfyPage.setSetting('Comfy.LinkRenderMode', 2)
|
||||
})
|
||||
|
||||
test.skip('Can toggle link visibility', async ({ comfyPage }) => {
|
||||
// Skipped for 1.24.x: Screenshot includes minimap button which has different visual state
|
||||
test('Can toggle link visibility', async ({ comfyPage }) => {
|
||||
// Note: `Comfy.Graph.CanvasMenu` is disabled in comfyPage setup.
|
||||
// so no cleanup is needed.
|
||||
await comfyPage.setSetting('Comfy.Graph.CanvasMenu', true)
|
||||
|
||||
@@ -767,7 +767,6 @@ test.describe('Viewport settings', () => {
|
||||
comfyPage,
|
||||
comfyMouse
|
||||
}) => {
|
||||
// Skipped for 1.24.x: Minimap is disabled by default in this branch
|
||||
// Screenshot the canvas element
|
||||
await comfyPage.setSetting('Comfy.Graph.CanvasMenu', true)
|
||||
const toggleButton = comfyPage.page.getByTestId('toggle-minimap-button')
|
||||
|
||||
@@ -919,6 +919,33 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
return `$${price.toFixed(2)}/Run`
|
||||
}
|
||||
},
|
||||
Veo3VideoGenerationNode: {
|
||||
displayPrice: (node: LGraphNode): string => {
|
||||
const modelWidget = node.widgets?.find(
|
||||
(w) => w.name === 'model'
|
||||
) as IComboWidget
|
||||
const generateAudioWidget = node.widgets?.find(
|
||||
(w) => w.name === 'generate_audio'
|
||||
) as IComboWidget
|
||||
|
||||
if (!modelWidget || !generateAudioWidget) {
|
||||
return '$2.00-6.00/Run (varies with model & audio generation)'
|
||||
}
|
||||
|
||||
const model = String(modelWidget.value)
|
||||
const generateAudio =
|
||||
String(generateAudioWidget.value).toLowerCase() === 'true'
|
||||
|
||||
if (model.includes('veo-3.0-fast-generate-001')) {
|
||||
return generateAudio ? '$3.20/Run' : '$2.00/Run'
|
||||
} else if (model.includes('veo-3.0-generate-001')) {
|
||||
return generateAudio ? '$6.00/Run' : '$4.00/Run'
|
||||
}
|
||||
|
||||
// Default fallback
|
||||
return '$2.00-6.00/Run'
|
||||
}
|
||||
},
|
||||
LumaImageNode: {
|
||||
displayPrice: (node: LGraphNode): string => {
|
||||
const modelWidget = node.widgets?.find(
|
||||
@@ -1340,6 +1367,7 @@ export const useNodePricing = () => {
|
||||
FluxProKontextProNode: [],
|
||||
FluxProKontextMaxNode: [],
|
||||
VeoVideoGenerationNode: ['duration_seconds'],
|
||||
Veo3VideoGenerationNode: ['model', 'generate_audio'],
|
||||
LumaVideoNode: ['model', 'resolution', 'duration'],
|
||||
LumaImageToVideoNode: ['model', 'resolution', 'duration'],
|
||||
LumaImageNode: ['model', 'aspect_ratio'],
|
||||
|
||||
@@ -813,7 +813,7 @@ export const CORE_SETTINGS: SettingParams[] = [
|
||||
id: 'Comfy.Minimap.Visible',
|
||||
name: 'Display minimap on canvas',
|
||||
type: 'hidden',
|
||||
defaultValue: false,
|
||||
defaultValue: true,
|
||||
versionAdded: '1.25.0'
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ import { computed, ref } from 'vue'
|
||||
import { type ReleaseNote, useReleaseService } from '@/services/releaseService'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
||||
import { isElectron } from '@/utils/envUtil'
|
||||
import { compareVersions, stringToLocale } from '@/utils/formatUtil'
|
||||
|
||||
// Store for managing release notes
|
||||
@@ -76,6 +77,11 @@ export const useReleaseStore = defineStore('release', () => {
|
||||
|
||||
// Show toast if needed
|
||||
const shouldShowToast = computed(() => {
|
||||
// Only show on desktop version
|
||||
if (!isElectron()) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Skip if notifications are disabled
|
||||
if (!showVersionUpdates.value) {
|
||||
return false
|
||||
@@ -103,6 +109,11 @@ export const useReleaseStore = defineStore('release', () => {
|
||||
|
||||
// Show red-dot indicator
|
||||
const shouldShowRedDot = computed(() => {
|
||||
// Only show on desktop version
|
||||
if (!isElectron()) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Skip if notifications are disabled
|
||||
if (!showVersionUpdates.value) {
|
||||
return false
|
||||
@@ -145,6 +156,11 @@ export const useReleaseStore = defineStore('release', () => {
|
||||
|
||||
// Show "What's New" popup
|
||||
const shouldShowPopup = computed(() => {
|
||||
// Only show on desktop version
|
||||
if (!isElectron()) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Skip if notifications are disabled
|
||||
if (!showVersionUpdates.value) {
|
||||
return false
|
||||
|
||||
@@ -393,6 +393,86 @@ describe('useNodePricing', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('dynamic pricing - Veo3VideoGenerationNode', () => {
|
||||
it('should return $2.00 for veo-3.0-fast-generate-001 without audio', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('Veo3VideoGenerationNode', [
|
||||
{ name: 'model', value: 'veo-3.0-fast-generate-001' },
|
||||
{ name: 'generate_audio', value: false }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$2.00/Run')
|
||||
})
|
||||
|
||||
it('should return $3.20 for veo-3.0-fast-generate-001 with audio', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('Veo3VideoGenerationNode', [
|
||||
{ name: 'model', value: 'veo-3.0-fast-generate-001' },
|
||||
{ name: 'generate_audio', value: true }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$3.20/Run')
|
||||
})
|
||||
|
||||
it('should return $4.00 for veo-3.0-generate-001 without audio', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('Veo3VideoGenerationNode', [
|
||||
{ name: 'model', value: 'veo-3.0-generate-001' },
|
||||
{ name: 'generate_audio', value: false }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$4.00/Run')
|
||||
})
|
||||
|
||||
it('should return $6.00 for veo-3.0-generate-001 with audio', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('Veo3VideoGenerationNode', [
|
||||
{ name: 'model', value: 'veo-3.0-generate-001' },
|
||||
{ name: 'generate_audio', value: true }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$6.00/Run')
|
||||
})
|
||||
|
||||
it('should return range when widgets are missing', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('Veo3VideoGenerationNode', [])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe(
|
||||
'$2.00-6.00/Run (varies with model & audio generation)'
|
||||
)
|
||||
})
|
||||
|
||||
it('should return range when only model widget is present', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('Veo3VideoGenerationNode', [
|
||||
{ name: 'model', value: 'veo-3.0-generate-001' }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe(
|
||||
'$2.00-6.00/Run (varies with model & audio generation)'
|
||||
)
|
||||
})
|
||||
|
||||
it('should return range when only generate_audio widget is present', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('Veo3VideoGenerationNode', [
|
||||
{ name: 'generate_audio', value: true }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe(
|
||||
'$2.00-6.00/Run (varies with model & audio generation)'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('dynamic pricing - LumaVideoNode', () => {
|
||||
it('should return $2.19 for ray-flash-2 4K 5s', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
@@ -736,6 +816,13 @@ describe('useNodePricing', () => {
|
||||
expect(widgetNames).toEqual(['duration_seconds'])
|
||||
})
|
||||
|
||||
it('should return correct widget names for Veo3VideoGenerationNode', () => {
|
||||
const { getRelevantWidgetNames } = useNodePricing()
|
||||
|
||||
const widgetNames = getRelevantWidgetNames('Veo3VideoGenerationNode')
|
||||
expect(widgetNames).toEqual(['model', 'generate_audio'])
|
||||
})
|
||||
|
||||
it('should return correct widget names for LumaVideoNode', () => {
|
||||
const { getRelevantWidgetNames } = useNodePricing()
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useReleaseStore } from '@/stores/releaseStore'
|
||||
|
||||
// Mock the dependencies
|
||||
vi.mock('@/utils/formatUtil')
|
||||
vi.mock('@/utils/envUtil')
|
||||
vi.mock('@/services/releaseService')
|
||||
vi.mock('@/stores/settingStore')
|
||||
vi.mock('@/stores/systemStatsStore')
|
||||
@@ -56,10 +57,12 @@ describe('useReleaseStore', () => {
|
||||
const { useReleaseService } = await import('@/services/releaseService')
|
||||
const { useSettingStore } = await import('@/stores/settingStore')
|
||||
const { useSystemStatsStore } = await import('@/stores/systemStatsStore')
|
||||
const { isElectron } = await import('@/utils/envUtil')
|
||||
|
||||
vi.mocked(useReleaseService).mockReturnValue(mockReleaseService)
|
||||
vi.mocked(useSettingStore).mockReturnValue(mockSettingStore)
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue(mockSystemStatsStore)
|
||||
vi.mocked(isElectron).mockReturnValue(true)
|
||||
|
||||
// Default showVersionUpdates to true
|
||||
mockSettingStore.get.mockImplementation((key: string) => {
|
||||
@@ -444,4 +447,118 @@ describe('useReleaseStore', () => {
|
||||
expect(mockReleaseService.getReleases).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isElectron environment checks', () => {
|
||||
beforeEach(() => {
|
||||
// Set up a new version available
|
||||
store.releases = [mockRelease]
|
||||
mockSettingStore.get.mockImplementation((key: string) => {
|
||||
if (key === 'Comfy.Notification.ShowVersionUpdates') return true
|
||||
return null
|
||||
})
|
||||
})
|
||||
|
||||
describe('when running in Electron (desktop)', () => {
|
||||
beforeEach(async () => {
|
||||
const { isElectron } = await import('@/utils/envUtil')
|
||||
vi.mocked(isElectron).mockReturnValue(true)
|
||||
})
|
||||
|
||||
it('should show toast when conditions are met', async () => {
|
||||
const { compareVersions } = await import('@/utils/formatUtil')
|
||||
vi.mocked(compareVersions).mockReturnValue(1)
|
||||
|
||||
// Need multiple releases for hasMediumOrHighAttention
|
||||
const mediumRelease = {
|
||||
...mockRelease,
|
||||
id: 2,
|
||||
attention: 'medium' as const
|
||||
}
|
||||
store.releases = [mockRelease, mediumRelease]
|
||||
|
||||
expect(store.shouldShowToast).toBe(true)
|
||||
})
|
||||
|
||||
it('should show red dot when new version available', async () => {
|
||||
const { compareVersions } = await import('@/utils/formatUtil')
|
||||
vi.mocked(compareVersions).mockReturnValue(1)
|
||||
|
||||
expect(store.shouldShowRedDot).toBe(true)
|
||||
})
|
||||
|
||||
it('should show popup for latest version', async () => {
|
||||
mockSystemStatsStore.systemStats.system.comfyui_version = '1.2.0'
|
||||
const { compareVersions } = await import('@/utils/formatUtil')
|
||||
vi.mocked(compareVersions).mockReturnValue(0)
|
||||
|
||||
expect(store.shouldShowPopup).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when NOT running in Electron (web)', () => {
|
||||
beforeEach(async () => {
|
||||
const { isElectron } = await import('@/utils/envUtil')
|
||||
vi.mocked(isElectron).mockReturnValue(false)
|
||||
})
|
||||
|
||||
it('should NOT show toast even when all other conditions are met', async () => {
|
||||
const { compareVersions } = await import('@/utils/formatUtil')
|
||||
vi.mocked(compareVersions).mockReturnValue(1)
|
||||
|
||||
// Set up all conditions that would normally show toast
|
||||
const mediumRelease = {
|
||||
...mockRelease,
|
||||
id: 2,
|
||||
attention: 'medium' as const
|
||||
}
|
||||
store.releases = [mockRelease, mediumRelease]
|
||||
|
||||
expect(store.shouldShowToast).toBe(false)
|
||||
})
|
||||
|
||||
it('should NOT show red dot even when new version available', async () => {
|
||||
const { compareVersions } = await import('@/utils/formatUtil')
|
||||
vi.mocked(compareVersions).mockReturnValue(1)
|
||||
|
||||
expect(store.shouldShowRedDot).toBe(false)
|
||||
})
|
||||
|
||||
it('should NOT show toast regardless of attention level', async () => {
|
||||
const { compareVersions } = await import('@/utils/formatUtil')
|
||||
vi.mocked(compareVersions).mockReturnValue(1)
|
||||
|
||||
// Test with high attention releases
|
||||
const highRelease = {
|
||||
...mockRelease,
|
||||
id: 2,
|
||||
attention: 'high' as const
|
||||
}
|
||||
const mediumRelease = {
|
||||
...mockRelease,
|
||||
id: 3,
|
||||
attention: 'medium' as const
|
||||
}
|
||||
store.releases = [highRelease, mediumRelease]
|
||||
|
||||
expect(store.shouldShowToast).toBe(false)
|
||||
})
|
||||
|
||||
it('should NOT show red dot even with high attention release', async () => {
|
||||
const { compareVersions } = await import('@/utils/formatUtil')
|
||||
vi.mocked(compareVersions).mockReturnValue(1)
|
||||
|
||||
store.releases = [{ ...mockRelease, attention: 'high' as const }]
|
||||
|
||||
expect(store.shouldShowRedDot).toBe(false)
|
||||
})
|
||||
|
||||
it('should NOT show popup even for latest version', async () => {
|
||||
mockSystemStatsStore.systemStats.system.comfyui_version = '1.2.0'
|
||||
const { compareVersions } = await import('@/utils/formatUtil')
|
||||
vi.mocked(compareVersions).mockReturnValue(0)
|
||||
|
||||
expect(store.shouldShowPopup).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user