mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-15 09:57:33 +00:00
Compare commits
2 Commits
fix/codera
...
remove-cac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e04225dd49 | ||
|
|
ffda940e5a |
@@ -59,10 +59,7 @@ function mkFileUrl(props: { ref: ImageRef; preview?: boolean }): string {
|
||||
}
|
||||
|
||||
const pathPlusQueryParams = api.apiURL(
|
||||
'/view?' +
|
||||
params.toString() +
|
||||
app.getPreviewFormatParam() +
|
||||
app.getRandParam()
|
||||
'/view?' + params.toString() + app.getPreviewFormatParam()
|
||||
)
|
||||
const imageElement = new Image()
|
||||
imageElement.crossOrigin = 'anonymous'
|
||||
|
||||
@@ -17,7 +17,7 @@ type MockTask = {
|
||||
executionEndTimestamp?: number
|
||||
previewOutput?: {
|
||||
isImage: boolean
|
||||
urlWithTimestamp: string
|
||||
url: string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ describe(useQueueNotificationBanners, () => {
|
||||
if (previewUrl) {
|
||||
task.previewOutput = {
|
||||
isImage,
|
||||
urlWithTimestamp: previewUrl
|
||||
url: previewUrl
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -231,7 +231,7 @@ export const useQueueNotificationBanners = () => {
|
||||
completedCount++
|
||||
const preview = task.previewOutput
|
||||
if (preview?.isImage) {
|
||||
imagePreviews.push(preview.urlWithTimestamp)
|
||||
imagePreviews.push(preview.url)
|
||||
}
|
||||
} else if (state === 'failed') {
|
||||
failedCount++
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { NodeOutputWith } from '@/schemas/apiSchema'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useExtensionService } from '@/services/extensionService'
|
||||
|
||||
type ImageCompareOutput = NodeOutputWith<{
|
||||
@@ -24,11 +23,10 @@ useExtensionService().registerExtension({
|
||||
onExecuted?.call(this, output)
|
||||
|
||||
const { a_images: aImages, b_images: bImages } = output
|
||||
const rand = app.getRandParam()
|
||||
|
||||
const toUrl = (record: Record<string, string>) => {
|
||||
const params = new URLSearchParams(record)
|
||||
return api.apiURL(`/view?${params}${rand}`)
|
||||
return api.apiURL(`/view?${params}`)
|
||||
}
|
||||
|
||||
const beforeImages =
|
||||
|
||||
@@ -2,7 +2,6 @@ import type Load3d from '@/extensions/core/load3d/Load3d'
|
||||
import { t } from '@/i18n'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
|
||||
class Load3dUtils {
|
||||
static async generateThumbnailIfNeeded(
|
||||
@@ -133,8 +132,7 @@ class Load3dUtils {
|
||||
const params = [
|
||||
'filename=' + encodeURIComponent(filename),
|
||||
'type=' + type,
|
||||
'subfolder=' + subfolder,
|
||||
app.getRandParam().substring(1)
|
||||
'subfolder=' + subfolder
|
||||
].join('&')
|
||||
|
||||
return `/view?${params}`
|
||||
|
||||
@@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { api } from '@/scripts/api'
|
||||
|
||||
import { refreshRemoteConfig } from './refreshRemoteConfig'
|
||||
import { remoteConfig, remoteConfigState } from './remoteConfig'
|
||||
import { remoteConfig } from './remoteConfig'
|
||||
|
||||
vi.mock('@/scripts/api', () => ({
|
||||
api: {
|
||||
@@ -16,7 +16,7 @@ vi.stubGlobal('fetch', vi.fn())
|
||||
describe('refreshRemoteConfig', () => {
|
||||
const mockConfig = { feature1: true, feature2: 'value' }
|
||||
|
||||
function mockSuccessResponse(config: Record<string, unknown> = mockConfig) {
|
||||
function mockSuccessResponse(config = mockConfig) {
|
||||
return {
|
||||
ok: true,
|
||||
json: async () => config
|
||||
@@ -123,67 +123,4 @@ describe('refreshRemoteConfig', () => {
|
||||
expect(window.__CONFIG__).toEqual(existingConfig)
|
||||
})
|
||||
})
|
||||
|
||||
describe('schema validation', () => {
|
||||
it('accepts a valid remote config response', async () => {
|
||||
const validConfig = {
|
||||
team_workspaces_enabled: true,
|
||||
subscription_required: false,
|
||||
max_upload_size: 1024
|
||||
}
|
||||
vi.mocked(api.fetchApi).mockResolvedValue(
|
||||
mockSuccessResponse(validConfig)
|
||||
)
|
||||
|
||||
await refreshRemoteConfig()
|
||||
|
||||
expect(remoteConfig.value).toEqual(validConfig)
|
||||
expect(window.__CONFIG__).toEqual(validConfig)
|
||||
expect(remoteConfigState.value).toBe('authenticated')
|
||||
})
|
||||
|
||||
it('rejects response with invalid type for boolean flag', async () => {
|
||||
const invalidConfig = {
|
||||
team_workspaces_enabled: 'not-a-boolean'
|
||||
}
|
||||
vi.mocked(api.fetchApi).mockResolvedValue(
|
||||
mockSuccessResponse(invalidConfig)
|
||||
)
|
||||
|
||||
await refreshRemoteConfig()
|
||||
|
||||
expect(remoteConfig.value).toEqual({})
|
||||
expect(window.__CONFIG__).toEqual({})
|
||||
expect(remoteConfigState.value).toBe('error')
|
||||
})
|
||||
|
||||
it('rejects response with invalid type for number field', async () => {
|
||||
const invalidConfig = {
|
||||
max_upload_size: 'not-a-number'
|
||||
}
|
||||
vi.mocked(api.fetchApi).mockResolvedValue(
|
||||
mockSuccessResponse(invalidConfig)
|
||||
)
|
||||
|
||||
await refreshRemoteConfig()
|
||||
|
||||
expect(remoteConfig.value).toEqual({})
|
||||
expect(window.__CONFIG__).toEqual({})
|
||||
expect(remoteConfigState.value).toBe('error')
|
||||
})
|
||||
|
||||
it('preserves unknown keys via passthrough', async () => {
|
||||
const configWithExtra = {
|
||||
team_workspaces_enabled: true,
|
||||
some_future_flag: 'new-value'
|
||||
}
|
||||
vi.mocked(api.fetchApi).mockResolvedValue(
|
||||
mockSuccessResponse(configWithExtra)
|
||||
)
|
||||
|
||||
await refreshRemoteConfig()
|
||||
|
||||
expect(remoteConfig.value).toEqual(configWithExtra)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { fromZodError } from 'zod-validation-error'
|
||||
|
||||
import { api } from '@/scripts/api'
|
||||
|
||||
import { remoteConfig, remoteConfigState } from './remoteConfig'
|
||||
import { remoteConfigSchema } from './remoteConfigSchema'
|
||||
import type { RemoteConfig } from './types'
|
||||
|
||||
interface RefreshRemoteConfigOptions {
|
||||
/**
|
||||
@@ -34,21 +30,7 @@ export async function refreshRemoteConfig(
|
||||
: await fetch('/api/features', { cache: 'no-store' })
|
||||
|
||||
if (response.ok) {
|
||||
const json = await response.json()
|
||||
const result = remoteConfigSchema.safeParse(json)
|
||||
|
||||
if (!result.success) {
|
||||
console.warn(
|
||||
'Invalid remote config response:',
|
||||
fromZodError(result.error).message
|
||||
)
|
||||
window.__CONFIG__ = {}
|
||||
remoteConfig.value = {}
|
||||
remoteConfigState.value = 'error'
|
||||
return
|
||||
}
|
||||
|
||||
const config = result.data as RemoteConfig
|
||||
const config = await response.json()
|
||||
window.__CONFIG__ = config
|
||||
remoteConfig.value = config
|
||||
remoteConfigState.value = useAuth ? 'authenticated' : 'anonymous'
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
const zServerHealthAlert = z.object({
|
||||
message: z.string(),
|
||||
tooltip: z.string().optional(),
|
||||
severity: z.enum(['info', 'warning', 'error']).optional(),
|
||||
badge: z.string().optional()
|
||||
})
|
||||
|
||||
const zFirebaseRuntimeConfig = z.object({
|
||||
apiKey: z.string(),
|
||||
authDomain: z.string(),
|
||||
databaseURL: z.string().optional(),
|
||||
projectId: z.string(),
|
||||
storageBucket: z.string(),
|
||||
messagingSenderId: z.string(),
|
||||
appId: z.string(),
|
||||
measurementId: z.string().optional()
|
||||
})
|
||||
|
||||
export const remoteConfigSchema = z
|
||||
.object({
|
||||
gtm_container_id: z.string().optional(),
|
||||
ga_measurement_id: z.string().optional(),
|
||||
mixpanel_token: z.string().optional(),
|
||||
posthog_project_token: z.string().optional(),
|
||||
posthog_api_host: z.string().optional(),
|
||||
posthog_config: z.record(z.unknown()).optional(),
|
||||
subscription_required: z.boolean().optional(),
|
||||
server_health_alert: zServerHealthAlert.optional(),
|
||||
max_upload_size: z.number().optional(),
|
||||
comfy_api_base_url: z.string().optional(),
|
||||
comfy_platform_base_url: z.string().optional(),
|
||||
firebase_config: zFirebaseRuntimeConfig.optional(),
|
||||
telemetry_disabled_events: z.array(z.string()).optional(),
|
||||
model_upload_button_enabled: z.boolean().optional(),
|
||||
asset_rename_enabled: z.boolean().optional(),
|
||||
private_models_enabled: z.boolean().optional(),
|
||||
onboarding_survey_enabled: z.boolean().optional(),
|
||||
linear_toggle_enabled: z.boolean().optional(),
|
||||
team_workspaces_enabled: z.boolean().optional(),
|
||||
user_secrets_enabled: z.boolean().optional(),
|
||||
node_library_essentials_enabled: z.boolean().optional(),
|
||||
free_tier_credits: z.number().optional(),
|
||||
new_free_tier_subscriptions: z.boolean().optional(),
|
||||
workflow_sharing_enabled: z.boolean().optional(),
|
||||
comfyhub_upload_enabled: z.boolean().optional(),
|
||||
comfyhub_profile_gate_enabled: z.boolean().optional()
|
||||
})
|
||||
.passthrough()
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<WidgetLayoutField :widget>
|
||||
<WidgetLayoutField v-slot="{ borderStyle }" :widget :no-border="!hasLabels">
|
||||
<!-- Use ToggleGroup when explicit labels are provided -->
|
||||
<ToggleGroup
|
||||
v-if="hasLabels"
|
||||
@@ -25,7 +25,13 @@
|
||||
<!-- Use ToggleSwitch for implicit boolean states -->
|
||||
<div
|
||||
v-else
|
||||
:class="cn('flex w-fit items-center gap-2', hideLayoutField || 'ml-auto')"
|
||||
:class="
|
||||
cn(
|
||||
'-m-1 flex w-fit items-center gap-2 rounded-full p-1',
|
||||
hideLayoutField || 'ml-auto',
|
||||
borderStyle
|
||||
)
|
||||
"
|
||||
>
|
||||
<ToggleSwitch
|
||||
v-model="modelValue"
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||
import { useHideLayoutField } from '@/types/widgetTypes'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
const { rootClass } = defineProps<{
|
||||
const { widget, rootClass } = defineProps<{
|
||||
widget: Pick<
|
||||
SimplifiedWidget<string | number | undefined>,
|
||||
'name' | 'label' | 'borderStyle'
|
||||
>
|
||||
rootClass?: string
|
||||
noBorder?: boolean
|
||||
}>()
|
||||
|
||||
const hideLayoutField = useHideLayoutField()
|
||||
const borderStyle = computed(() =>
|
||||
cn(
|
||||
'focus-within:ring focus-within:ring-component-node-widget-background-highlighted',
|
||||
widget.borderStyle
|
||||
)
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -33,15 +42,15 @@ const hideLayoutField = useHideLayoutField()
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'min-w-0 cursor-default rounded-lg transition-all has-focus-visible:ring has-focus-visible:ring-component-node-widget-background-highlighted',
|
||||
widget.borderStyle
|
||||
'min-w-0 cursor-default rounded-lg transition-all',
|
||||
!noBorder && borderStyle
|
||||
)
|
||||
"
|
||||
@pointerdown.stop
|
||||
@pointermove.stop
|
||||
@pointerup.stop
|
||||
>
|
||||
<slot />
|
||||
<slot :border-style />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { ResultItemType } from '@/schemas/apiSchema'
|
||||
import { app } from '@/scripts/app'
|
||||
|
||||
/**
|
||||
* Format time in MM:SS format
|
||||
@@ -20,8 +19,7 @@ export function getResourceURL(
|
||||
const params = [
|
||||
'filename=' + encodeURIComponent(filename),
|
||||
'type=' + type,
|
||||
'subfolder=' + subfolder,
|
||||
app.getRandParam().substring(1)
|
||||
'subfolder=' + subfolder
|
||||
].join('&')
|
||||
|
||||
return `/view?${params}`
|
||||
|
||||
@@ -382,11 +382,6 @@ export class ComfyApp {
|
||||
else return ''
|
||||
}
|
||||
|
||||
getRandParam() {
|
||||
if (isCloud) return ''
|
||||
return '&rand=' + Math.random()
|
||||
}
|
||||
|
||||
static onClipspaceEditorSave() {
|
||||
if (ComfyApp.clipspace_return_node) {
|
||||
ComfyApp.pasteFromClipspace(ComfyApp.clipspace_return_node)
|
||||
|
||||
@@ -117,12 +117,11 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => {
|
||||
const outputs = getNodeOutputs(node)
|
||||
if (!outputs?.images?.length) return
|
||||
|
||||
const rand = app.getRandParam()
|
||||
const previewParam = getPreviewParam(node, outputs)
|
||||
|
||||
return outputs.images.map((image) => {
|
||||
const params = new URLSearchParams(image)
|
||||
return api.apiURL(`/view?${params}${previewParam}${rand}`)
|
||||
return api.apiURL(`/view?${params}${previewParam}`)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -104,10 +104,6 @@ export class ResultItemImpl {
|
||||
return api.apiURL('/view?' + params)
|
||||
}
|
||||
|
||||
get urlWithTimestamp(): string {
|
||||
return `${this.url}&t=${+new Date()}`
|
||||
}
|
||||
|
||||
get isVhsFormat(): boolean {
|
||||
return !!this.format && !!this.frame_rate
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user