Compare commits

...

3 Commits

Author SHA1 Message Date
Terry Jia
2dbec2473b chore: tidy up retain-view-on-reload follow-ups 2026-05-23 08:54:39 -04:00
Jukka Seppänen
3da6e1766e feat: optional retain camera view on Load3D model reload (#12440)
When comparing outputs from 3D generations, it's very hard to see small
differences since the camera always resets. This adds an option to lock
the camera, so only the model refreshes.

## Summary

Adds an opt-in per-node toggle that preserves the current camera view
(position, target, zoom, camera type) across model loads in Load3D /
Load3DAnimation nodes, instead of resetting to default framing.

## Changes

- **What**: New `retainViewOnReload?: boolean` field on `CameraConfig`,
a `Load3d.setRetainViewOnReload()` setter wired through the existing
`useLoad3d` camera-config watcher, capture/restore logic in
`Load3d._loadModelInternal`, and a lock-icon toggle button in
`CameraControls.vue` below the FOV slider. Preference persists via the
existing `node.properties['Camera Config']` mechanism.

## Review Focus

- **First-load semantics**: retain only kicks in once a model has
successfully loaded at least once (`hasLoadedModel` flag), so the
default `setupForModel` framing wins on a fresh node. `clearModel()`
resets the flag so the next load also reframes.
- **Restore order vs. `SceneModelManager.setupModel`**: the scene model
manager unconditionally calls `setupForModel` during a load, which
clobbers the camera. The restore in `_loadModelInternal` runs *after*
the load completes, on top of that framing.
- **Camera-type mismatch**: if the saved state's `cameraType` differs
from the currently active camera, `toggleCamera()` runs before
`setCameraState()` so the perspective/orthographic camera being restored
is actually the active one. Covered by a dedicated test.
- **Scope**: only wired through `useLoad3d` (LiteGraph node controls).
The full-page viewer (`useLoad3dViewer` / `ViewerCameraControls`) is
deliberately not extended — the modal is mostly a one-shot
view-and-close flow, so retain there would add surface area for an
uncommon use case.
- **Failed loads**: `hasLoadedModel` only flips inside `if
(modelManager.currentModel)`, so a load that produces no model leaves
the flag where it was. Captured camera state is still applied on top,
which effectively no-ops since nothing reset it.


## Video


https://github.com/user-attachments/assets/880d6ad1-28a9-4413-83a3-8323d05d904a
2026-05-23 08:47:30 -04:00
Comfy Org PR Bot
52830a9e73 1.46.0 (#12439)
Minor version increment to 1.46.0

**Base branch:** `main`

---------

Co-authored-by: dante01yoon <6510430+dante01yoon@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-05-23 18:16:38 +09:00
20 changed files with 615 additions and 160 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@comfyorg/comfyui-frontend",
"version": "1.45.14",
"version": "1.46.0",
"private": true,
"description": "Official front-end implementation of ComfyUI",
"homepage": "https://comfy.org",

View File

@@ -71,6 +71,7 @@
v-if="showCameraControls"
v-model:camera-type="cameraConfig!.cameraType"
v-model:fov="cameraConfig!.fov"
v-model:retain-view-on-reload="cameraConfig!.retainViewOnReload"
/>
<div v-if="showLightControls" class="flex flex-col">

View File

@@ -11,17 +11,39 @@
:aria-label="$t('load3d.switchCamera')"
@click="switchCamera"
>
<i :class="['pi', 'pi-camera', 'text-lg text-base-foreground']" />
<i class="pi pi-camera text-lg text-base-foreground" />
</Button>
<PopupSlider
v-if="showFOVButton"
v-model="fov"
:tooltip-text="$t('load3d.fov')"
/>
<Button
v-tooltip.right="{
value: $t('load3d.retainViewOnReload'),
showDelay: 300
}"
size="icon"
variant="textonly"
class="rounded-full"
:aria-label="$t('load3d.retainViewOnReload')"
:aria-pressed="retainViewOnReload"
@click="retainViewOnReload = !retainViewOnReload"
>
<i
:class="
cn(
'pi text-lg text-base-foreground',
retainViewOnReload ? 'pi-lock' : 'pi-lock-open'
)
"
/>
</Button>
</div>
</template>
<script setup lang="ts">
import { cn } from '@comfyorg/tailwind-utils'
import { computed } from 'vue'
import PopupSlider from '@/components/load3d/controls/PopupSlider.vue'
@@ -30,6 +52,9 @@ import type { CameraType } from '@/extensions/core/load3d/interfaces'
const cameraType = defineModel<CameraType>('cameraType')
const fov = defineModel<number>('fov')
const retainViewOnReload = defineModel<boolean>('retainViewOnReload', {
default: false
})
const showFOVButton = computed(() => cameraType.value === 'perspective')
const switchCamera = () => {

View File

@@ -144,6 +144,7 @@ describe('useLoad3d', () => {
setMaterialMode: vi.fn(),
toggleCamera: vi.fn(),
setFOV: vi.fn(),
setRetainViewOnReload: vi.fn(),
setLightIntensity: vi.fn(),
setCameraState: vi.fn(),
loadModel: vi.fn().mockResolvedValue(undefined),
@@ -568,17 +569,21 @@ describe('useLoad3d', () => {
vi.mocked(mockLoad3d.toggleCamera!).mockClear()
vi.mocked(mockLoad3d.setFOV!).mockClear()
vi.mocked(mockLoad3d.setRetainViewOnReload!).mockClear()
composable.cameraConfig.value.cameraType = 'orthographic'
composable.cameraConfig.value.fov = 90
composable.cameraConfig.value.retainViewOnReload = true
await nextTick()
expect(mockLoad3d.toggleCamera).toHaveBeenCalledWith('orthographic')
expect(mockLoad3d.setFOV).toHaveBeenCalledWith(90)
expect(mockLoad3d.setRetainViewOnReload).toHaveBeenCalledWith(true)
expect(mockNode.properties['Camera Config']).toEqual({
cameraType: 'orthographic',
fov: 90,
state: null
state: null,
retainViewOnReload: true
})
})

View File

@@ -483,6 +483,7 @@ export const useLoad3d = (nodeOrRef: MaybeRef<LGraphNode | null>) => {
nodeRef.value.properties['Camera Config'] = newValue
load3d.toggleCamera(newValue.cameraType)
load3d.setFOV(newValue.fov)
load3d.setRetainViewOnReload(newValue.retainViewOnReload ?? false)
}
},
{ deep: true }

View File

@@ -2,7 +2,10 @@ import * as THREE from 'three'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import Load3d from '@/extensions/core/load3d/Load3d'
import type { GizmoMode } from '@/extensions/core/load3d/interfaces'
import type {
CameraState,
GizmoMode
} from '@/extensions/core/load3d/interfaces'
const {
cloneSkinnedMock,
@@ -769,6 +772,133 @@ describe('Load3d', () => {
})
})
describe('retainViewOnReload', () => {
function setupLoadInternal(initialFlag: boolean) {
const getCameraState = vi.fn<() => CameraState>(() => ({
position: new THREE.Vector3(1, 2, 3),
target: new THREE.Vector3(),
zoom: 1,
cameraType: 'perspective'
}))
const setCameraState = vi.fn()
const getCurrentCameraType = vi.fn(() => 'perspective' as const)
const loaderLoadModel = vi.fn().mockResolvedValue(undefined)
Object.assign(ctx.load3d, {
cameraManager: {
...ctx.cameraManager,
getCameraState,
setCameraState,
getCurrentCameraType
},
controlsManager: { ...ctx.controlsManager, reset: vi.fn() },
loaderManager: { loadModel: loaderLoadModel },
modelManager: {
...ctx.modelManager,
currentModel: new THREE.Group(),
originalModel: null
},
animationManager: {
...ctx.animationManager,
setupModelAnimations: vi.fn()
},
handleResize: vi.fn(),
retainViewOnReload: initialFlag,
hasLoadedModel: false
})
return { getCameraState, setCameraState, getCurrentCameraType }
}
it('first load uses default framing even with retain enabled', async () => {
const mocks = setupLoadInternal(true)
await ctx.load3d.loadModel('a.glb')
// hasLoadedModel started false, so retain shouldn't kick in yet.
expect(ctx.cameraManager.reset).toHaveBeenCalledOnce()
expect(mocks.getCameraState).not.toHaveBeenCalled()
expect(mocks.setCameraState).not.toHaveBeenCalled()
})
it('subsequent load captures camera state, skips reset, and restores it', async () => {
const mocks = setupLoadInternal(true)
await ctx.load3d.loadModel('a.glb')
;(ctx.cameraManager.reset as ReturnType<typeof vi.fn>).mockClear()
mocks.getCameraState.mockClear()
mocks.setCameraState.mockClear()
await ctx.load3d.loadModel('b.glb')
expect(ctx.cameraManager.reset).not.toHaveBeenCalled()
expect(mocks.getCameraState).toHaveBeenCalledOnce()
expect(mocks.setCameraState).toHaveBeenCalledOnce()
})
it('does not retain when the flag is off, even after a prior load', async () => {
const mocks = setupLoadInternal(false)
await ctx.load3d.loadModel('a.glb')
;(ctx.cameraManager.reset as ReturnType<typeof vi.fn>).mockClear()
mocks.getCameraState.mockClear()
mocks.setCameraState.mockClear()
await ctx.load3d.loadModel('b.glb')
expect(ctx.cameraManager.reset).toHaveBeenCalledOnce()
expect(mocks.getCameraState).not.toHaveBeenCalled()
expect(mocks.setCameraState).not.toHaveBeenCalled()
})
it('toggles to the saved camera type before restoring state when types differ', async () => {
const mocks = setupLoadInternal(true)
mocks.getCameraState.mockImplementation(() => ({
position: new THREE.Vector3(0, 0, 5),
target: new THREE.Vector3(),
zoom: 1,
cameraType: 'orthographic'
}))
// First load (active type stays perspective per the default mock).
await ctx.load3d.loadModel('a.glb')
;(ctx.cameraManager.toggleCamera as ReturnType<typeof vi.fn>).mockClear()
await ctx.load3d.loadModel('b.glb')
expect(ctx.cameraManager.toggleCamera).toHaveBeenCalledWith(
'orthographic'
)
expect(mocks.setCameraState).toHaveBeenCalledOnce()
})
it('resets hasLoadedModel on clearModel so the next load uses default framing', async () => {
const mocks = setupLoadInternal(true)
await ctx.load3d.loadModel('a.glb')
ctx.load3d.clearModel()
;(ctx.cameraManager.reset as ReturnType<typeof vi.fn>).mockClear()
mocks.getCameraState.mockClear()
await ctx.load3d.loadModel('b.glb')
expect(ctx.cameraManager.reset).toHaveBeenCalledOnce()
expect(mocks.getCameraState).not.toHaveBeenCalled()
})
it('setRetainViewOnReload flips the runtime behavior between loads', async () => {
const mocks = setupLoadInternal(false)
await ctx.load3d.loadModel('a.glb')
ctx.load3d.setRetainViewOnReload(true)
;(ctx.cameraManager.reset as ReturnType<typeof vi.fn>).mockClear()
mocks.getCameraState.mockClear()
mocks.setCameraState.mockClear()
await ctx.load3d.loadModel('b.glb')
expect(ctx.cameraManager.reset).not.toHaveBeenCalled()
expect(mocks.getCameraState).toHaveBeenCalledOnce()
expect(mocks.setCameraState).toHaveBeenCalledOnce()
})
})
describe('captureScene', () => {
it('hides the gizmo helper during capture and restores it after success', async () => {
const captureResult = { scene: 'a', mask: 'b', normal: 'c' }

View File

@@ -104,6 +104,8 @@ class Load3d {
private disposeContextMenuGuard: (() => void) | null = null
private resizeObserver: ResizeObserver | null = null
private getZoomScaleCallback: (() => number) | undefined
private retainViewOnReload: boolean = false
private hasLoadedModel: boolean = false
constructor(
container: Element | HTMLElement,
@@ -564,13 +566,25 @@ class Load3d {
}
}
public setRetainViewOnReload(value: boolean): void {
this.retainViewOnReload = value
}
private async _loadModelInternal(
url: string,
originalFileName?: string,
options?: LoadModelOptions
): Promise<void> {
this.cameraManager.reset()
this.controlsManager.reset()
// First load always uses default framing; retain only applies on reload.
const shouldRetainView = this.retainViewOnReload && this.hasLoadedModel
const savedCameraState = shouldRetainView
? this.cameraManager.getCameraState()
: null
if (!shouldRetainView) {
this.cameraManager.reset()
this.controlsManager.reset()
}
this.gizmoManager.detach()
this.modelManager.clearModel()
this.animationManager.dispose()
@@ -583,6 +597,18 @@ class Load3d {
this.modelManager.currentModel,
this.modelManager.originalModel
)
this.hasLoadedModel = true
}
if (savedCameraState) {
// setupForModel runs during loadModel and clobbers the camera; restore on top.
if (
savedCameraState.cameraType !==
this.cameraManager.getCurrentCameraType()
) {
this.toggleCamera(savedCameraState.cameraType)
}
this.cameraManager.setCameraState(savedCameraState)
}
this.handleResize()
@@ -607,6 +633,7 @@ class Load3d {
this.gizmoManager.detach()
this.modelManager.clearModel()
this.adapterRef.current = null
this.hasLoadedModel = false
this.forceRender()
}

View File

@@ -50,6 +50,7 @@ export interface CameraConfig {
cameraType: CameraType
fov: number
state?: CameraState
retainViewOnReload?: boolean
}
export interface LightConfig {

View File

@@ -911,6 +911,44 @@
"paused": "تم الإيقاف مؤقتًا",
"resume": "استئناف التنزيل"
},
"errorCatalog": {
"fallbacks": {
"inputName": "مدخل غير معروف",
"nodeName": "هذه العقدة"
},
"promptErrors": {
"no_prompt": {
"desc": "بيانات سير العمل المرسلة إلى الخادم فارغة. قد يكون هذا خطأ غير متوقع في النظام."
},
"prompt_no_outputs": {
"desc": "سير العمل لا يحتوي على أي عقدة إخراج (مثل حفظ الصورة أو معاينة الصورة) لإنتاج نتيجة."
},
"server_error_cloud": {
"desc": "واجه الخادم خطأ غير متوقع. يرجى المحاولة لاحقاً."
},
"server_error_local": {
"desc": "واجه الخادم خطأ غير متوقع. يرجى مراجعة سجلات الخادم."
}
},
"runtimeErrors": {
"execution_failed": {
"itemLabel": "{nodeName}",
"toastMessageCloud": "حدث خطأ أثناء تنفيذ هذه العقدة. تحقق من المدخلات أو جرّب إعداداً مختلفاً. لم يتم خصم أي رصيد.",
"toastMessageLocal": "حدث خطأ أثناء تنفيذ هذه العقدة. تحقق من المدخلات أو جرّب إعداداً مختلفاً.",
"toastTitle": "فشل {nodeName}"
}
},
"validationErrors": {
"required_input_missing": {
"details": "{nodeName} تفتقد إلى مدخل مطلوب: {inputName}",
"itemLabel": "{nodeName} - {inputName}",
"message": "بعض منافذ الإدخال المطلوبة غير متصلة.",
"title": "الاتصال مفقود",
"toastMessage": "{nodeName} تفتقد إلى مدخل مطلوب: {inputName}",
"toastTitle": "مدخل مطلوب مفقود"
}
}
},
"errorDialog": {
"accessRestrictedMessage": "Your account is not authorized for this feature.",
"accessRestrictedTitle": "Access Restricted",
@@ -2701,20 +2739,6 @@
"normal": "عادي",
"parameters": "المعلمات",
"pinned": "مثبت",
"promptErrors": {
"no_prompt": {
"desc": "بيانات سير العمل المرسلة إلى الخادم فارغة. قد يكون هذا خطأ غير متوقع في النظام."
},
"prompt_no_outputs": {
"desc": "سير العمل لا يحتوي على أي عقدة إخراج (مثل حفظ الصورة، معاينة الصورة) لإنتاج نتيجة."
},
"server_error_cloud": {
"desc": "واجه الخادم خطأ غير متوقع. يرجى المحاولة لاحقاً."
},
"server_error_local": {
"desc": "واجه الخادم خطأ غير متوقع. يرجى مراجعة سجلات الخادم."
}
},
"properties": "الخصائص",
"removeFavorite": "إزالة من المفضلة",
"resetAllParameters": "إعادة تعيين جميع المعلمات",

View File

@@ -1952,6 +1952,7 @@
},
"load3d": {
"switchCamera": "Switch Camera",
"retainViewOnReload": "Lock camera view across model reloads",
"showGrid": "Show Grid",
"backgroundColor": "Background Color",
"lightIntensity": "Light Intensity",

View File

@@ -911,6 +911,44 @@
"paused": "Pausado",
"resume": "Reanudar descarga"
},
"errorCatalog": {
"fallbacks": {
"inputName": "entrada desconocida",
"nodeName": "Este nodo"
},
"promptErrors": {
"no_prompt": {
"desc": "Los datos del flujo de trabajo enviados al servidor están vacíos. Esto puede ser un error inesperado del sistema."
},
"prompt_no_outputs": {
"desc": "El flujo de trabajo no contiene ningún nodo de salida (por ejemplo, Guardar imagen, Vista previa de imagen) para producir un resultado."
},
"server_error_cloud": {
"desc": "El servidor encontró un error inesperado. Por favor, inténtalo de nuevo más tarde."
},
"server_error_local": {
"desc": "El servidor encontró un error inesperado. Por favor, revisa los registros del servidor."
}
},
"runtimeErrors": {
"execution_failed": {
"itemLabel": "{nodeName}",
"toastMessageCloud": "Este nodo generó un error durante la ejecución. Revisa sus entradas o prueba una configuración diferente. No se cobraron créditos.",
"toastMessageLocal": "Este nodo generó un error durante la ejecución. Revisa sus entradas o prueba una configuración diferente.",
"toastTitle": "{nodeName} falló"
}
},
"validationErrors": {
"required_input_missing": {
"details": "{nodeName} carece de una entrada requerida: {inputName}",
"itemLabel": "{nodeName} - {inputName}",
"message": "Las ranuras de entrada requeridas no tienen ninguna conexión.",
"title": "Conexión faltante",
"toastMessage": "{nodeName} carece de una entrada requerida: {inputName}",
"toastTitle": "Falta entrada requerida"
}
}
},
"errorDialog": {
"accessRestrictedMessage": "Your account is not authorized for this feature.",
"accessRestrictedTitle": "Access Restricted",
@@ -2701,20 +2739,6 @@
"normal": "Normal",
"parameters": "Parámetros",
"pinned": "Fijado",
"promptErrors": {
"no_prompt": {
"desc": "Los datos del flujo de trabajo enviados al servidor están vacíos. Esto puede ser un error inesperado del sistema."
},
"prompt_no_outputs": {
"desc": "El flujo de trabajo no contiene ningún nodo de salida (por ejemplo, Guardar imagen, Vista previa de imagen) para producir un resultado."
},
"server_error_cloud": {
"desc": "El servidor encontró un error inesperado. Por favor, inténtalo de nuevo más tarde."
},
"server_error_local": {
"desc": "El servidor encontró un error inesperado. Por favor, revisa los registros del servidor."
}
},
"properties": "Propiedades",
"removeFavorite": "Quitar de favoritos",
"resetAllParameters": "Restablecer todos los parámetros",

View File

@@ -911,6 +911,44 @@
"paused": "متوقف شده",
"resume": "ادامه دانلود"
},
"errorCatalog": {
"fallbacks": {
"inputName": "ورودی نامشخص",
"nodeName": "این نود"
},
"promptErrors": {
"no_prompt": {
"desc": "داده‌های ورک‌فلو ارسال‌شده به سرور خالی است. این ممکن است یک خطای غیرمنتظره سیستمی باشد."
},
"prompt_no_outputs": {
"desc": "در این ورک‌فلو هیچ نود خروجی (مانند Save Image یا Preview Image) برای تولید نتیجه وجود ندارد."
},
"server_error_cloud": {
"desc": "سرور با یک خطای غیرمنتظره مواجه شد. لطفاً بعداً دوباره تلاش کنید."
},
"server_error_local": {
"desc": "سرور با یک خطای غیرمنتظره مواجه شد. لطفاً لاگ‌های سرور را بررسی کنید."
}
},
"runtimeErrors": {
"execution_failed": {
"itemLabel": "{nodeName}",
"toastMessageCloud": "این نود هنگام اجرا با خطا مواجه شد. ورودی‌ها را بررسی کنید یا پیکربندی دیگری را امتحان نمایید. هیچ اعتباری کسر نشد.",
"toastMessageLocal": "این نود هنگام اجرا با خطا مواجه شد. ورودی‌ها را بررسی کنید یا پیکربندی دیگری را امتحان نمایید.",
"toastTitle": "{nodeName} با خطا مواجه شد"
}
},
"validationErrors": {
"required_input_missing": {
"details": "{nodeName} یک ورودی ضروری را ندارد: {inputName}",
"itemLabel": "{nodeName} - {inputName}",
"message": "ورودی‌های ضروری بدون اتصال هستند.",
"title": "اتصال وجود ندارد",
"toastMessage": "{nodeName} یک ورودی ضروری را ندارد: {inputName}",
"toastTitle": "ورودی ضروری وجود ندارد"
}
}
},
"errorDialog": {
"accessRestrictedMessage": "Your account is not authorized for this feature.",
"accessRestrictedTitle": "Access Restricted",
@@ -2701,20 +2739,6 @@
"normal": "عادی",
"parameters": "پارامترها",
"pinned": "سنجاق شده",
"promptErrors": {
"no_prompt": {
"desc": "داده‌های گردش‌کار ارسال‌شده به سرور خالی است. این ممکن است یک خطای غیرمنتظره سیستمی باشد."
},
"prompt_no_outputs": {
"desc": "گردش‌کار هیچ نود خروجی (مانند Save Image یا Preview Image) برای تولید نتیجه ندارد."
},
"server_error_cloud": {
"desc": "سرور با خطای غیرمنتظره‌ای مواجه شد. لطفاً بعداً دوباره تلاش کنید."
},
"server_error_local": {
"desc": "سرور با خطای غیرمنتظره‌ای مواجه شد. لطفاً لاگ‌های سرور را بررسی کنید."
}
},
"properties": "ویژگی‌ها",
"removeFavorite": "حذف از علاقه‌مندی‌ها",
"resetAllParameters": "بازنشانی همه پارامترها",

View File

@@ -911,6 +911,44 @@
"paused": "En pause",
"resume": "Reprendre le téléchargement"
},
"errorCatalog": {
"fallbacks": {
"inputName": "entrée inconnue",
"nodeName": "Ce nœud"
},
"promptErrors": {
"no_prompt": {
"desc": "Les données du workflow envoyées au serveur sont vides. Il peut sagir dune erreur système inattendue."
},
"prompt_no_outputs": {
"desc": "Le workflow ne contient aucun nœud de sortie (par exemple, Enregistrer limage, Prévisualiser limage) pour produire un résultat."
},
"server_error_cloud": {
"desc": "Le serveur a rencontré une erreur inattendue. Veuillez réessayer plus tard."
},
"server_error_local": {
"desc": "Le serveur a rencontré une erreur inattendue. Veuillez consulter les journaux du serveur."
}
},
"runtimeErrors": {
"execution_failed": {
"itemLabel": "{nodeName}",
"toastMessageCloud": "Ce nœud a généré une erreur lors de lexécution. Vérifiez ses entrées ou essayez une autre configuration. Aucun crédit na été déduit.",
"toastMessageLocal": "Ce nœud a généré une erreur lors de lexécution. Vérifiez ses entrées ou essayez une autre configuration.",
"toastTitle": "Échec de {nodeName}"
}
},
"validationErrors": {
"required_input_missing": {
"details": "{nodeName} nécessite une entrée obligatoire : {inputName}",
"itemLabel": "{nodeName} - {inputName}",
"message": "Des entrées requises ne sont pas connectées.",
"title": "Connexion manquante",
"toastMessage": "{nodeName} nécessite une entrée obligatoire : {inputName}",
"toastTitle": "Entrée requise manquante"
}
}
},
"errorDialog": {
"accessRestrictedMessage": "Your account is not authorized for this feature.",
"accessRestrictedTitle": "Access Restricted",
@@ -2701,20 +2739,6 @@
"normal": "Normal",
"parameters": "Paramètres",
"pinned": "Épinglé",
"promptErrors": {
"no_prompt": {
"desc": "Les données du flux de travail envoyées au serveur sont vides. Il peut s'agir d'une erreur système inattendue."
},
"prompt_no_outputs": {
"desc": "Le flux de travail ne contient aucun nœud de sortie (par exemple, Enregistrer l'image, Prévisualiser l'image) pour produire un résultat."
},
"server_error_cloud": {
"desc": "Le serveur a rencontré une erreur inattendue. Veuillez réessayer plus tard."
},
"server_error_local": {
"desc": "Le serveur a rencontré une erreur inattendue. Veuillez consulter les journaux du serveur."
}
},
"properties": "Propriétés",
"removeFavorite": "Retirer des favoris",
"resetAllParameters": "Réinitialiser tous les paramètres",

View File

@@ -911,6 +911,44 @@
"paused": "一時停止",
"resume": "ダウンロードを再開"
},
"errorCatalog": {
"fallbacks": {
"inputName": "不明な入力",
"nodeName": "このノード"
},
"promptErrors": {
"no_prompt": {
"desc": "サーバーに送信されたワークフローデータが空です。これは予期しないシステムエラーの可能性があります。"
},
"prompt_no_outputs": {
"desc": "ワークフローに出力ノード(例:画像を保存、画像をプレビュー)が含まれていないため、結果を生成できません。"
},
"server_error_cloud": {
"desc": "サーバーで予期しないエラーが発生しました。しばらくしてから再度お試しください。"
},
"server_error_local": {
"desc": "サーバーで予期しないエラーが発生しました。サーバーログを確認してください。"
}
},
"runtimeErrors": {
"execution_failed": {
"itemLabel": "{nodeName}",
"toastMessageCloud": "このノードの実行中にエラーが発生しました。入力内容を確認するか、別の設定をお試しください。クレジットは消費されていません。",
"toastMessageLocal": "このノードの実行中にエラーが発生しました。入力内容を確認するか、別の設定をお試しください。",
"toastTitle": "{nodeName} の実行に失敗しました"
}
},
"validationErrors": {
"required_input_missing": {
"details": "{nodeName} に必須入力 {inputName} がありません。",
"itemLabel": "{nodeName} - {inputName}",
"message": "必須入力スロットに接続がありません。",
"title": "接続がありません",
"toastMessage": "{nodeName} に必須入力 {inputName} がありません。",
"toastTitle": "必須入力がありません"
}
}
},
"errorDialog": {
"accessRestrictedMessage": "Your account is not authorized for this feature.",
"accessRestrictedTitle": "Access Restricted",
@@ -2701,20 +2739,6 @@
"normal": "ノーマル",
"parameters": "パラメータ",
"pinned": "ピン留め",
"promptErrors": {
"no_prompt": {
"desc": "サーバーに送信されたワークフローデータが空です。これは予期しないシステムエラーの可能性があります。"
},
"prompt_no_outputs": {
"desc": "ワークフローに結果を生成する出力ノード(例:画像を保存、画像をプレビュー)が含まれていません。"
},
"server_error_cloud": {
"desc": "サーバーで予期しないエラーが発生しました。しばらくしてから再度お試しください。"
},
"server_error_local": {
"desc": "サーバーで予期しないエラーが発生しました。サーバーログをご確認ください。"
}
},
"properties": "プロパティ",
"removeFavorite": "お気に入りを解除",
"resetAllParameters": "すべてのパラメータをリセット",

View File

@@ -911,6 +911,44 @@
"paused": "일시 중지됨",
"resume": "다운로드 재개"
},
"errorCatalog": {
"fallbacks": {
"inputName": "알 수 없는 입력",
"nodeName": "이 노드"
},
"promptErrors": {
"no_prompt": {
"desc": "서버로 전송된 워크플로우 데이터가 비어 있습니다. 시스템 오류일 수 있습니다."
},
"prompt_no_outputs": {
"desc": "워크플로우에 결과를 생성할 출력 노드(예: 이미지 저장, 이미지 미리보기)가 없습니다."
},
"server_error_cloud": {
"desc": "서버에서 예기치 않은 오류가 발생했습니다. 잠시 후 다시 시도해 주세요."
},
"server_error_local": {
"desc": "서버에서 예기치 않은 오류가 발생했습니다. 서버 로그를 확인해 주세요."
}
},
"runtimeErrors": {
"execution_failed": {
"itemLabel": "{nodeName}",
"toastMessageCloud": "이 노드는 실행 중 오류가 발생했습니다. 입력값을 확인하거나 다른 설정을 시도해 보세요. 크레딧은 차감되지 않았습니다.",
"toastMessageLocal": "이 노드는 실행 중 오류가 발생했습니다. 입력값을 확인하거나 다른 설정을 시도해 보세요.",
"toastTitle": "{nodeName} 실패"
}
},
"validationErrors": {
"required_input_missing": {
"details": "{nodeName}에 필수 입력이 누락되었습니다: {inputName}",
"itemLabel": "{nodeName} - {inputName}",
"message": "필수 입력 슬롯에 연결이 없습니다.",
"title": "연결 누락",
"toastMessage": "{nodeName}에 필수 입력이 누락되었습니다: {inputName}",
"toastTitle": "필수 입력 누락"
}
}
},
"errorDialog": {
"accessRestrictedMessage": "Your account is not authorized for this feature.",
"accessRestrictedTitle": "Access Restricted",
@@ -2701,20 +2739,6 @@
"normal": "일반",
"parameters": "파라미터",
"pinned": "고정됨",
"promptErrors": {
"no_prompt": {
"desc": "서버로 전송된 워크플로우 데이터가 비어 있습니다. 이는 예기치 않은 시스템 오류일 수 있습니다."
},
"prompt_no_outputs": {
"desc": "워크플로우에 결과를 생성할 출력 노드(예: 이미지 저장, 이미지 미리보기)가 포함되어 있지 않습니다."
},
"server_error_cloud": {
"desc": "서버에서 예기치 않은 오류가 발생했습니다. 나중에 다시 시도해 주세요."
},
"server_error_local": {
"desc": "서버에서 예기치 않은 오류가 발생했습니다. 서버 로그를 확인해 주세요."
}
},
"properties": "속성",
"removeFavorite": "즐겨찾기 해제",
"resetAllParameters": "모든 매개변수 재설정",

View File

@@ -911,6 +911,44 @@
"paused": "Pausado",
"resume": "Retomar download"
},
"errorCatalog": {
"fallbacks": {
"inputName": "entrada desconhecida",
"nodeName": "Este nó"
},
"promptErrors": {
"no_prompt": {
"desc": "Os dados do fluxo de trabalho enviados ao servidor estão vazios. Isso pode ser um erro inesperado do sistema."
},
"prompt_no_outputs": {
"desc": "O fluxo de trabalho não contém nenhum nó de saída (ex: Salvar Imagem, Visualizar Imagem) para produzir um resultado."
},
"server_error_cloud": {
"desc": "O servidor encontrou um erro inesperado. Por favor, tente novamente mais tarde."
},
"server_error_local": {
"desc": "O servidor encontrou um erro inesperado. Por favor, verifique os logs do servidor."
}
},
"runtimeErrors": {
"execution_failed": {
"itemLabel": "{nodeName}",
"toastMessageCloud": "Este nó gerou um erro durante a execução. Verifique suas entradas ou tente uma configuração diferente. Nenhum crédito foi cobrado.",
"toastMessageLocal": "Este nó gerou um erro durante a execução. Verifique suas entradas ou tente uma configuração diferente.",
"toastTitle": "{nodeName} falhou"
}
},
"validationErrors": {
"required_input_missing": {
"details": "{nodeName} está sem uma entrada obrigatória: {inputName}",
"itemLabel": "{nodeName} - {inputName}",
"message": "Entradas obrigatórias não possuem conexão.",
"title": "Conexão ausente",
"toastMessage": "{nodeName} está sem uma entrada obrigatória: {inputName}",
"toastTitle": "Entrada obrigatória ausente"
}
}
},
"errorDialog": {
"accessRestrictedMessage": "Your account is not authorized for this feature.",
"accessRestrictedTitle": "Access Restricted",
@@ -2701,20 +2739,6 @@
"normal": "Normal",
"parameters": "Parâmetros",
"pinned": "Fixado",
"promptErrors": {
"no_prompt": {
"desc": "Os dados do fluxo de trabalho enviados ao servidor estão vazios. Isso pode ser um erro de sistema inesperado."
},
"prompt_no_outputs": {
"desc": "O fluxo de trabalho não contém nenhum nó de saída (por exemplo, Salvar Imagem, Visualizar Imagem) para produzir um resultado."
},
"server_error_cloud": {
"desc": "O servidor encontrou um erro inesperado. Por favor, tente novamente mais tarde."
},
"server_error_local": {
"desc": "O servidor encontrou um erro inesperado. Por favor, verifique os logs do servidor."
}
},
"properties": "Propriedades",
"removeFavorite": "Desfavoritar",
"resetAllParameters": "Redefinir todos os parâmetros",

View File

@@ -911,6 +911,44 @@
"paused": "Приостановлено",
"resume": "Возобновить загрузку"
},
"errorCatalog": {
"fallbacks": {
"inputName": "неизвестный вход",
"nodeName": "Этот узел"
},
"promptErrors": {
"no_prompt": {
"desc": "Данные рабочего процесса, отправленные на сервер, пусты. Это может быть неожиданной системной ошибкой."
},
"prompt_no_outputs": {
"desc": "В рабочем процессе отсутствуют выходные узлы (например, Сохранить изображение, Просмотр изображения) для получения результата."
},
"server_error_cloud": {
"desc": "На сервере произошла непредвиденная ошибка. Пожалуйста, попробуйте позже."
},
"server_error_local": {
"desc": "На сервере произошла непредвиденная ошибка. Пожалуйста, проверьте журналы сервера."
}
},
"runtimeErrors": {
"execution_failed": {
"itemLabel": "{nodeName}",
"toastMessageCloud": "Этот узел вызвал ошибку во время выполнения. Проверьте его входные данные или попробуйте другую конфигурацию. Кредиты не списаны.",
"toastMessageLocal": "Этот узел вызвал ошибку во время выполнения. Проверьте его входные данные или попробуйте другую конфигурацию.",
"toastTitle": "{nodeName} завершился с ошибкой"
}
},
"validationErrors": {
"required_input_missing": {
"details": "{nodeName} отсутствует обязательный вход: {inputName}",
"itemLabel": "{nodeName} - {inputName}",
"message": "Требуемые входные слоты не имеют подключений.",
"title": "Отсутствует соединение",
"toastMessage": "{nodeName} отсутствует обязательный вход: {inputName}",
"toastTitle": "Отсутствует обязательный вход"
}
}
},
"errorDialog": {
"accessRestrictedMessage": "Your account is not authorized for this feature.",
"accessRestrictedTitle": "Access Restricted",
@@ -2701,20 +2739,6 @@
"normal": "Обычный",
"parameters": "Параметры",
"pinned": "Закреплено",
"promptErrors": {
"no_prompt": {
"desc": "Данные рабочего процесса, отправленные на сервер, пусты. Это может быть неожиданной системной ошибкой."
},
"prompt_no_outputs": {
"desc": "В рабочем процессе отсутствуют выходные узлы (например, Сохранить изображение, Предпросмотр изображения) для получения результата."
},
"server_error_cloud": {
"desc": "На сервере произошла непредвиденная ошибка. Пожалуйста, попробуйте позже."
},
"server_error_local": {
"desc": "На сервере произошла непредвиденная ошибка. Пожалуйста, проверьте логи сервера."
}
},
"properties": "Свойства",
"removeFavorite": "Убрать из избранного",
"resetAllParameters": "Сбросить все параметры",

View File

@@ -911,6 +911,44 @@
"paused": "Duraklatıldı",
"resume": "İndirmeye Devam Et"
},
"errorCatalog": {
"fallbacks": {
"inputName": "bilinmeyen giriş",
"nodeName": "Bu düğüm"
},
"promptErrors": {
"no_prompt": {
"desc": "Sunucuya gönderilen çalışma akışı verisi boş. Bu beklenmeyen bir sistem hatası olabilir."
},
"prompt_no_outputs": {
"desc": "Çalışma akışında sonuç üretecek herhangi bir çıktı düğümü (örn. Görseli Kaydet, Görseli Önizle) bulunmuyor."
},
"server_error_cloud": {
"desc": "Sunucuda beklenmeyen bir hata oluştu. Lütfen daha sonra tekrar deneyin."
},
"server_error_local": {
"desc": "Sunucuda beklenmeyen bir hata oluştu. Lütfen sunucu günlüklerini kontrol edin."
}
},
"runtimeErrors": {
"execution_failed": {
"itemLabel": "{nodeName}",
"toastMessageCloud": "Bu düğüm çalıştırılırken bir hata oluştu. Girişlerini kontrol edin veya farklı bir yapılandırmayı deneyin. Kredi harcanmadı.",
"toastMessageLocal": "Bu düğüm çalıştırılırken bir hata oluştu. Girişlerini kontrol edin veya farklı bir yapılandırmayı deneyin.",
"toastTitle": "{nodeName} başarısız oldu"
}
},
"validationErrors": {
"required_input_missing": {
"details": "{nodeName} için gerekli bir giriş eksik: {inputName}",
"itemLabel": "{nodeName} - {inputName}",
"message": "Gerekli giriş yuvalarına bağlantı yapılmamış.",
"title": "Eksik bağlantı",
"toastMessage": "{nodeName} için gerekli bir giriş eksik: {inputName}",
"toastTitle": "Gerekli giriş eksik"
}
}
},
"errorDialog": {
"accessRestrictedMessage": "Your account is not authorized for this feature.",
"accessRestrictedTitle": "Access Restricted",
@@ -2701,20 +2739,6 @@
"normal": "Normal",
"parameters": "Parametreler",
"pinned": "Sabitlendi",
"promptErrors": {
"no_prompt": {
"desc": "Sunucuya gönderilen çalışma akışı verisi boş. Bu beklenmeyen bir sistem hatası olabilir."
},
"prompt_no_outputs": {
"desc": "Çalışma akışında sonuç üretecek herhangi bir çıktı düğümü (ör. Görüntüyü Kaydet, Görüntüyü Önizle) bulunmuyor."
},
"server_error_cloud": {
"desc": "Sunucu beklenmeyen bir hata ile karşılaştı. Lütfen daha sonra tekrar deneyin."
},
"server_error_local": {
"desc": "Sunucu beklenmeyen bir hata ile karşılaştı. Lütfen sunucu günlüklerini kontrol edin."
}
},
"properties": "Özellikler",
"removeFavorite": "Favorilerden Kaldır",
"resetAllParameters": "Tüm parametreleri sıfırla",

View File

@@ -911,6 +911,44 @@
"paused": "已暫停",
"resume": "繼續下載"
},
"errorCatalog": {
"fallbacks": {
"inputName": "未知輸入",
"nodeName": "此節點"
},
"promptErrors": {
"no_prompt": {
"desc": "傳送到伺服器的工作流程資料為空。這可能是系統的非預期錯誤。"
},
"prompt_no_outputs": {
"desc": "工作流程中沒有包含任何輸出節點(例如:儲存圖像、預覽圖像),無法產生結果。"
},
"server_error_cloud": {
"desc": "伺服器發生非預期錯誤。請稍後再試。"
},
"server_error_local": {
"desc": "伺服器發生非預期錯誤。請檢查伺服器日誌。"
}
},
"runtimeErrors": {
"execution_failed": {
"itemLabel": "{nodeName}",
"toastMessageCloud": "此節點在執行時發生錯誤。請檢查其輸入或嘗試其他設定。未扣除點數。",
"toastMessageLocal": "此節點在執行時發生錯誤。請檢查其輸入或嘗試其他設定。",
"toastTitle": "{nodeName} 執行失敗"
}
},
"validationErrors": {
"required_input_missing": {
"details": "{nodeName} 缺少必要的輸入:{inputName}",
"itemLabel": "{nodeName} - {inputName}",
"message": "必要的輸入插槽沒有連接來源。",
"title": "缺少連接",
"toastMessage": "{nodeName} 缺少必要的輸入:{inputName}",
"toastTitle": "缺少必要輸入"
}
}
},
"errorDialog": {
"accessRestrictedMessage": "您的帳戶無權使用此功能。",
"accessRestrictedTitle": "存取受限",
@@ -2701,20 +2739,6 @@
"normal": "一般",
"parameters": "參數",
"pinned": "已釘選",
"promptErrors": {
"no_prompt": {
"desc": "傳送到伺服器的工作流程資料為空。這可能是系統發生了非預期的錯誤。"
},
"prompt_no_outputs": {
"desc": "工作流程中沒有任何輸出節點(例如:儲存圖像、預覽圖像),無法產生結果。"
},
"server_error_cloud": {
"desc": "伺服器發生非預期錯誤。請稍後再試。"
},
"server_error_local": {
"desc": "伺服器發生非預期錯誤。請檢查伺服器日誌。"
}
},
"properties": "屬性",
"removeFavorite": "取消收藏",
"resetAllParameters": "重設所有參數",

View File

@@ -911,6 +911,44 @@
"paused": "已暂停",
"resume": "恢复下载"
},
"errorCatalog": {
"fallbacks": {
"inputName": "未知输入",
"nodeName": "此节点"
},
"promptErrors": {
"no_prompt": {
"desc": "发送到服务器的工作流数据为空。这可能是系统的意外错误。"
},
"prompt_no_outputs": {
"desc": "工作流未包含任何输出节点(如保存图像、预览图像),无法生成结果。"
},
"server_error_cloud": {
"desc": "服务器遇到意外错误。请稍后再试。"
},
"server_error_local": {
"desc": "服务器遇到意外错误。请检查服务器日志。"
}
},
"runtimeErrors": {
"execution_failed": {
"itemLabel": "{nodeName}",
"toastMessageCloud": "此节点在执行过程中发生错误。请检查其输入或尝试其他配置。未扣除积分。",
"toastMessageLocal": "此节点在执行过程中发生错误。请检查其输入或尝试其他配置。",
"toastTitle": "{nodeName} 执行失败"
}
},
"validationErrors": {
"required_input_missing": {
"details": "{nodeName} 缺少必需的输入:{inputName}",
"itemLabel": "{nodeName} - {inputName}",
"message": "必需的输入插槽没有连接。",
"title": "缺少连接",
"toastMessage": "{nodeName} 缺少必需的输入:{inputName}",
"toastTitle": "缺少必需输入"
}
}
},
"errorDialog": {
"accessRestrictedMessage": "您的账户无权使用此功能。",
"accessRestrictedTitle": "访问受限",
@@ -2701,20 +2739,6 @@
"normal": "正常",
"parameters": "参数",
"pinned": "顶固",
"promptErrors": {
"no_prompt": {
"desc": "发送到服务器的工作流数据为空。这可能是一个意外的系统错误。"
},
"prompt_no_outputs": {
"desc": "工作流中没有包含任何输出节点(例如:保存图像、预览图像),无法生成结果。"
},
"server_error_cloud": {
"desc": "服务器遇到意外错误。请稍后再试。"
},
"server_error_local": {
"desc": "服务器遇到意外错误。请检查服务器日志。"
}
},
"properties": "属性",
"removeFavorite": "取消收藏",
"resetAllParameters": "重置所有参数",