diff --git a/.claude/commands/setup_repo.md b/.claude/commands/setup_repo.md index d82e22ec6..71dee96a5 100644 --- a/.claude/commands/setup_repo.md +++ b/.claude/commands/setup_repo.md @@ -122,7 +122,7 @@ echo " pnpm build - Build for production" echo " pnpm test:unit - Run unit tests" echo " pnpm typecheck - Run TypeScript checks" echo " pnpm lint - Run ESLint" -echo " pnpm format - Format code with Prettier" +echo " pnpm format - Format code with oxfmt" echo "" echo "Next steps:" echo "1. Run 'pnpm dev' to start developing" diff --git a/.cursor/rules/unit-test.mdc b/.cursor/rules/unit-test.mdc deleted file mode 100644 index 2c6704f3e..000000000 --- a/.cursor/rules/unit-test.mdc +++ /dev/null @@ -1,21 +0,0 @@ ---- -description: Creating unit tests -globs: -alwaysApply: false ---- - -# Creating unit tests - -- This project uses `vitest` for unit testing -- Tests are stored in the `test/` directory -- Tests should be cross-platform compatible; able to run on Windows, macOS, and linux - - e.g. the use of `path.resolve`, or `path.join` and `path.sep` to ensure that tests work the same on all platforms -- Tests should be mocked properly - - Mocks should be cleanly written and easy to understand - - Mocks should be re-usable where possible - -## Unit test style - -- Prefer the use of `test.extend` over loose variables - - To achieve this, import `test as baseTest` from `vitest` -- Never use `it`; `test` should be used in place of this \ No newline at end of file diff --git a/.cursorrules b/.cursorrules deleted file mode 100644 index 6f43623a3..000000000 --- a/.cursorrules +++ /dev/null @@ -1,61 +0,0 @@ -# Vue 3 Composition API Project Rules - -## Vue 3 Composition API Best Practices -- Use setup() function for component logic -- Utilize ref and reactive for reactive state -- Implement computed properties with computed() -- Use watch and watchEffect for side effects -- Implement lifecycle hooks with onMounted, onUpdated, etc. -- Utilize provide/inject for dependency injection -- Use vue 3.5 style of default prop declaration. Example: - -```typescript -const { nodes, showTotal = true } = defineProps<{ - nodes: ApiNodeCost[] - showTotal?: boolean -}>() -``` - -- Organize vue component in diff --git a/src/components/MenuHamburger.vue b/src/components/MenuHamburger.vue index 467561b54..fa99e032c 100644 --- a/src/components/MenuHamburger.vue +++ b/src/components/MenuHamburger.vue @@ -1,27 +1,27 @@ - - diff --git a/src/components/TopMenuSection.test.ts b/src/components/TopMenuSection.test.ts new file mode 100644 index 000000000..8d4f3673d --- /dev/null +++ b/src/components/TopMenuSection.test.ts @@ -0,0 +1,232 @@ +import { createTestingPinia } from '@pinia/testing' +import { mount } from '@vue/test-utils' +import type { MenuItem } from 'primevue/menuitem' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { computed, nextTick } from 'vue' +import { createI18n } from 'vue-i18n' + +import TopMenuSection from '@/components/TopMenuSection.vue' +import CurrentUserButton from '@/components/topbar/CurrentUserButton.vue' +import LoginButton from '@/components/topbar/LoginButton.vue' +import type { + JobListItem, + JobStatus +} from '@/platform/remote/comfyui/jobs/jobTypes' +import { useSettingStore } from '@/platform/settings/settingStore' +import { useCommandStore } from '@/stores/commandStore' +import { TaskItemImpl, useQueueStore } from '@/stores/queueStore' +import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore' +import { isElectron } from '@/utils/envUtil' + +const mockData = vi.hoisted(() => ({ isLoggedIn: false })) + +vi.mock('@/composables/auth/useCurrentUser', () => ({ + useCurrentUser: () => { + return { + isLoggedIn: computed(() => mockData.isLoggedIn) + } + } +})) + +vi.mock('@/utils/envUtil') +vi.mock('@/stores/firebaseAuthStore', () => ({ + useFirebaseAuthStore: vi.fn(() => ({ + currentUser: null, + loading: false + })) +})) + +function createWrapper(pinia = createTestingPinia({ createSpy: vi.fn })) { + const i18n = createI18n({ + legacy: false, + locale: 'en', + messages: { + en: { + sideToolbar: { + queueProgressOverlay: { + viewJobHistory: 'View job history', + expandCollapsedQueue: 'Expand collapsed queue', + activeJobsShort: '{count} active | {count} active', + clearQueueTooltip: 'Clear queue' + } + } + } + } + }) + + return mount(TopMenuSection, { + global: { + plugins: [pinia, i18n], + stubs: { + SubgraphBreadcrumb: true, + QueueProgressOverlay: true, + CurrentUserButton: true, + LoginButton: true, + ContextMenu: { + name: 'ContextMenu', + props: ['model'], + template: '
' + } + }, + directives: { + tooltip: () => {} + } + } + }) +} + +function createJob(id: string, status: JobStatus): JobListItem { + return { + id, + status, + create_time: 0, + priority: 0 + } +} + +function createTask(id: string, status: JobStatus): TaskItemImpl { + return new TaskItemImpl(createJob(id, status)) +} + +describe('TopMenuSection', () => { + beforeEach(() => { + vi.resetAllMocks() + }) + + describe('authentication state', () => { + describe('when user is logged in', () => { + beforeEach(() => { + mockData.isLoggedIn = true + }) + + it('should display CurrentUserButton and not display LoginButton', () => { + const wrapper = createWrapper() + expect(wrapper.findComponent(CurrentUserButton).exists()).toBe(true) + expect(wrapper.findComponent(LoginButton).exists()).toBe(false) + }) + }) + + describe('when user is not logged in', () => { + beforeEach(() => { + mockData.isLoggedIn = false + }) + + describe('on desktop platform', () => { + it('should display LoginButton and not display CurrentUserButton', () => { + vi.mocked(isElectron).mockReturnValue(true) + const wrapper = createWrapper() + expect(wrapper.findComponent(LoginButton).exists()).toBe(true) + expect(wrapper.findComponent(CurrentUserButton).exists()).toBe(false) + }) + }) + + describe('on web platform', () => { + it('should not display CurrentUserButton and not display LoginButton', () => { + const wrapper = createWrapper() + expect(wrapper.findComponent(CurrentUserButton).exists()).toBe(false) + expect(wrapper.findComponent(LoginButton).exists()).toBe(false) + }) + }) + }) + }) + + it('shows the active jobs label with the current count', async () => { + const wrapper = createWrapper() + const queueStore = useQueueStore() + queueStore.pendingTasks = [createTask('pending-1', 'pending')] + queueStore.runningTasks = [ + createTask('running-1', 'in_progress'), + createTask('running-2', 'in_progress') + ] + + await nextTick() + + const queueButton = wrapper.find('[data-testid="queue-overlay-toggle"]') + expect(queueButton.text()).toContain('3 active') + }) + + it('hides queue progress overlay when QPO V2 is enabled', async () => { + const pinia = createTestingPinia({ createSpy: vi.fn }) + const settingStore = useSettingStore(pinia) + vi.mocked(settingStore.get).mockImplementation((key) => + key === 'Comfy.Queue.QPOV2' ? true : undefined + ) + const wrapper = createWrapper(pinia) + + await nextTick() + + expect(wrapper.find('[data-testid="queue-overlay-toggle"]').exists()).toBe( + true + ) + expect( + wrapper.findComponent({ name: 'QueueProgressOverlay' }).exists() + ).toBe(false) + }) + + it('toggles the queue progress overlay when QPO V2 is disabled', async () => { + const pinia = createTestingPinia({ createSpy: vi.fn, stubActions: false }) + const settingStore = useSettingStore(pinia) + vi.mocked(settingStore.get).mockImplementation((key) => + key === 'Comfy.Queue.QPOV2' ? false : undefined + ) + const wrapper = createWrapper(pinia) + const commandStore = useCommandStore(pinia) + + await wrapper.find('[data-testid="queue-overlay-toggle"]').trigger('click') + + expect(commandStore.execute).toHaveBeenCalledWith( + 'Comfy.Queue.ToggleOverlay' + ) + }) + + it('opens the assets sidebar tab when QPO V2 is enabled', async () => { + const pinia = createTestingPinia({ createSpy: vi.fn, stubActions: false }) + const settingStore = useSettingStore(pinia) + vi.mocked(settingStore.get).mockImplementation((key) => + key === 'Comfy.Queue.QPOV2' ? true : undefined + ) + const wrapper = createWrapper(pinia) + const sidebarTabStore = useSidebarTabStore(pinia) + + await wrapper.find('[data-testid="queue-overlay-toggle"]').trigger('click') + + expect(sidebarTabStore.activeSidebarTabId).toBe('assets') + }) + + it('toggles the assets sidebar tab when QPO V2 is enabled', async () => { + const pinia = createTestingPinia({ createSpy: vi.fn, stubActions: false }) + const settingStore = useSettingStore(pinia) + vi.mocked(settingStore.get).mockImplementation((key) => + key === 'Comfy.Queue.QPOV2' ? true : undefined + ) + const wrapper = createWrapper(pinia) + const sidebarTabStore = useSidebarTabStore(pinia) + const toggleButton = wrapper.find('[data-testid="queue-overlay-toggle"]') + + await toggleButton.trigger('click') + expect(sidebarTabStore.activeSidebarTabId).toBe('assets') + + await toggleButton.trigger('click') + expect(sidebarTabStore.activeSidebarTabId).toBe(null) + }) + + it('disables the clear queue context menu item when no queued jobs exist', () => { + const wrapper = createWrapper() + const menu = wrapper.findComponent({ name: 'ContextMenu' }) + const model = menu.props('model') as MenuItem[] + expect(model[0]?.label).toBe('Clear queue') + expect(model[0]?.disabled).toBe(true) + }) + + it('enables the clear queue context menu item when queued jobs exist', async () => { + const wrapper = createWrapper() + const queueStore = useQueueStore() + queueStore.pendingTasks = [createTask('pending-1', 'pending')] + + await nextTick() + + const menu = wrapper.findComponent({ name: 'ContextMenu' }) + const model = menu.props('model') as MenuItem[] + expect(model[0]?.disabled).toBe(false) + }) +}) diff --git a/src/components/TopMenuSection.vue b/src/components/TopMenuSection.vue index 55c78dd5a..05149589c 100644 --- a/src/components/TopMenuSection.vue +++ b/src/components/TopMenuSection.vue @@ -10,42 +10,84 @@
-
- - +
- - - - - {{ queuedCount }} - - - - + + + +
+ +
+ + +
+ + + + + + +
@@ -54,38 +96,103 @@ - - diff --git a/src/components/actionbar/ComfyActionbar.vue b/src/components/actionbar/ComfyActionbar.vue index d7d92c7de..5ea860852 100644 --- a/src/components/actionbar/ComfyActionbar.vue +++ b/src/components/actionbar/ComfyActionbar.vue @@ -1,5 +1,5 @@ @@ -36,25 +37,30 @@ diff --git a/src/components/breadcrumb/SubgraphBreadcrumb.vue b/src/components/breadcrumb/SubgraphBreadcrumb.vue index 6795ee97d..bf4ba405a 100644 --- a/src/components/breadcrumb/SubgraphBreadcrumb.vue +++ b/src/components/breadcrumb/SubgraphBreadcrumb.vue @@ -1,6 +1,6 @@ diff --git a/src/components/templates/thumbnails/BaseThumbnail.test.ts b/src/components/templates/thumbnails/BaseThumbnail.test.ts index ecb03df41..f1d8571ee 100644 --- a/src/components/templates/thumbnails/BaseThumbnail.test.ts +++ b/src/components/templates/thumbnails/BaseThumbnail.test.ts @@ -4,6 +4,10 @@ import { nextTick } from 'vue' import BaseThumbnail from '@/components/templates/thumbnails/BaseThumbnail.vue' +type ComponentInstance = InstanceType & { + error: boolean +} + vi.mock('@vueuse/core', () => ({ useEventListener: vi.fn() })) @@ -45,7 +49,7 @@ describe('BaseThumbnail', () => { it('shows error state when image fails to load', async () => { const wrapper = mountThumbnail() - const vm = wrapper.vm as any + const vm = wrapper.vm as ComponentInstance // Manually set error since useEventListener is mocked vm.error = true diff --git a/src/components/toast/ProgressToastItem.stories.ts b/src/components/toast/ProgressToastItem.stories.ts new file mode 100644 index 000000000..cdfa8e28e --- /dev/null +++ b/src/components/toast/ProgressToastItem.stories.ts @@ -0,0 +1,95 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite' + +import type { AssetDownload } from '@/stores/assetDownloadStore' + +import ProgressToastItem from './ProgressToastItem.vue' + +const meta: Meta = { + title: 'Toast/ProgressToastItem', + component: ProgressToastItem, + parameters: { + layout: 'padded' + }, + decorators: [ + () => ({ + template: '
' + }) + ] +} + +export default meta +type Story = StoryObj + +function createMockJob(overrides: Partial = {}): AssetDownload { + return { + taskId: 'task-1', + assetId: 'asset-1', + assetName: 'model-v1.safetensors', + bytesTotal: 1000000, + bytesDownloaded: 0, + progress: 0, + status: 'created', + lastUpdate: Date.now(), + ...overrides + } +} + +export const Pending: Story = { + args: { + job: createMockJob({ + status: 'created', + assetName: 'sd-xl-base-1.0.safetensors' + }) + } +} + +export const Running: Story = { + args: { + job: createMockJob({ + status: 'running', + progress: 0.45, + assetName: 'lora-detail-enhancer.safetensors' + }) + } +} + +export const RunningAlmostComplete: Story = { + args: { + job: createMockJob({ + status: 'running', + progress: 0.92, + assetName: 'vae-ft-mse-840000.safetensors' + }) + } +} + +export const Completed: Story = { + args: { + job: createMockJob({ + status: 'completed', + progress: 1, + assetName: 'controlnet-canny.safetensors' + }) + } +} + +export const Failed: Story = { + args: { + job: createMockJob({ + status: 'failed', + progress: 0.23, + assetName: 'unreachable-model.safetensors' + }) + } +} + +export const LongFileName: Story = { + args: { + job: createMockJob({ + status: 'running', + progress: 0.67, + assetName: + 'very-long-model-name-with-lots-of-descriptive-text-v2.1-final-release.safetensors' + }) + } +} diff --git a/src/components/toast/ProgressToastItem.vue b/src/components/toast/ProgressToastItem.vue new file mode 100644 index 000000000..079be418f --- /dev/null +++ b/src/components/toast/ProgressToastItem.vue @@ -0,0 +1,65 @@ + + + diff --git a/src/components/toast/RerouteMigrationToast.vue b/src/components/toast/RerouteMigrationToast.vue index c084926d1..e807994a3 100644 --- a/src/components/toast/RerouteMigrationToast.vue +++ b/src/components/toast/RerouteMigrationToast.vue @@ -5,13 +5,9 @@
{{ t('toastMessages.migrateToLitegraphReroute') }}
-
@@ -19,10 +15,10 @@ diff --git a/src/components/topbar/ActionBarButtons.vue b/src/components/topbar/ActionBarButtons.vue index 81b5c42ad..e87b1d62f 100644 --- a/src/components/topbar/ActionBarButtons.vue +++ b/src/components/topbar/ActionBarButtons.vue @@ -1,29 +1,30 @@ diff --git a/src/components/topbar/CurrentUserButton.test.ts b/src/components/topbar/CurrentUserButton.test.ts index 60c46ff4a..db5349b49 100644 --- a/src/components/topbar/CurrentUserButton.test.ts +++ b/src/components/topbar/CurrentUserButton.test.ts @@ -1,6 +1,6 @@ import type { VueWrapper } from '@vue/test-utils' import { mount } from '@vue/test-utils' -import Button from 'primevue/button' +import Button from '@/components/ui/button/Button.vue' import { beforeEach, describe, expect, it, vi } from 'vitest' import { h } from 'vue' import { createI18n } from 'vue-i18n' diff --git a/src/components/topbar/CurrentUserButton.vue b/src/components/topbar/CurrentUserButton.vue index d64a6dfe2..fde213498 100644 --- a/src/components/topbar/CurrentUserButton.vue +++ b/src/components/topbar/CurrentUserButton.vue @@ -1,46 +1,100 @@ - + diff --git a/src/components/topbar/LoginButton.vue b/src/components/topbar/LoginButton.vue index 7ab857a66..060e1f809 100644 --- a/src/components/topbar/LoginButton.vue +++ b/src/components/topbar/LoginButton.vue @@ -1,17 +1,19 @@ @@ -133,19 +125,17 @@ diff --git a/src/components/widget/nav/NavIcon.vue b/src/components/widget/nav/NavIcon.vue index bffa99da6..91f70e23a 100644 --- a/src/components/widget/nav/NavIcon.vue +++ b/src/components/widget/nav/NavIcon.vue @@ -1,5 +1,5 @@ diff --git a/src/components/widget/panel/LeftSidePanel.stories.ts b/src/components/widget/panel/LeftSidePanel.stories.ts index bbfee54c4..98199ad87 100644 --- a/src/components/widget/panel/LeftSidePanel.stories.ts +++ b/src/components/widget/panel/LeftSidePanel.stories.ts @@ -7,20 +7,6 @@ const meta: Meta = { title: 'Components/Widget/Panel/LeftSidePanel', component: LeftSidePanel, argTypes: { - 'header-icon': { - table: { - type: { summary: 'slot' }, - defaultValue: { summary: 'undefined' } - }, - control: false - }, - 'header-title': { - table: { - type: { summary: 'slot' }, - defaultValue: { summary: 'undefined' } - }, - control: false - }, 'onUpdate:modelValue': { table: { disable: true } } @@ -59,14 +45,7 @@ export const Default: Story = { }, template: `
- - - - +
` }) @@ -126,14 +105,7 @@ export const WithGroups: Story = { }, template: `
- - - - +
Selected: {{ selectedItem }}
@@ -176,14 +148,7 @@ export const DefaultIcons: Story = { }, template: `
- - - - +
` }) @@ -228,14 +193,7 @@ export const LongLabels: Story = { }, template: `
- - - - +
` }) diff --git a/src/components/widget/panel/LeftSidePanel.vue b/src/components/widget/panel/LeftSidePanel.vue index 94b3104eb..3711897f8 100644 --- a/src/components/widget/panel/LeftSidePanel.vue +++ b/src/components/widget/panel/LeftSidePanel.vue @@ -1,45 +1,41 @@ @@ -50,8 +46,6 @@ import NavItem from '@/components/widget/nav/NavItem.vue' import NavTitle from '@/components/widget/nav/NavTitle.vue' import type { NavGroupData, NavItemData } from '@/types/navTypes' -import PanelHeader from './PanelHeader.vue' - const { navItems = [], modelValue } = defineProps<{ navItems?: (NavItemData | NavGroupData)[] modelValue?: string | null diff --git a/src/components/widget/panel/PanelHeader.vue b/src/components/widget/panel/PanelHeader.vue deleted file mode 100644 index cdcc4eb20..000000000 --- a/src/components/widget/panel/PanelHeader.vue +++ /dev/null @@ -1,12 +0,0 @@ - diff --git a/src/components/widget/panel/RightSidePanel.vue b/src/components/widget/panel/RightSidePanel.vue deleted file mode 100644 index 85e5aee69..000000000 --- a/src/components/widget/panel/RightSidePanel.vue +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/src/composables/auth/useCurrentUser.ts b/src/composables/auth/useCurrentUser.ts index cc5634cae..b43191fbb 100644 --- a/src/composables/auth/useCurrentUser.ts +++ b/src/composables/auth/useCurrentUser.ts @@ -41,8 +41,8 @@ export const useCurrentUser = () => { whenever(() => authStore.tokenRefreshTrigger, callback) const onUserLogout = (callback: () => void) => { - watch(resolvedUserInfo, (user) => { - if (!user) callback() + watch(resolvedUserInfo, (user, prevUser) => { + if (prevUser && !user) callback() }) } diff --git a/src/composables/auth/useFirebaseAuthActions.ts b/src/composables/auth/useFirebaseAuthActions.ts index eed7eb021..319dfa851 100644 --- a/src/composables/auth/useFirebaseAuthActions.ts +++ b/src/composables/auth/useFirebaseAuthActions.ts @@ -11,6 +11,7 @@ import { useTelemetry } from '@/platform/telemetry' import { useToastStore } from '@/platform/updates/common/toastStore' import { useDialogService } from '@/services/dialogService' import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore' +import type { BillingPortalTargetTier } from '@/stores/firebaseAuthStore' import { usdToMicros } from '@/utils/formatUtil' /** @@ -102,8 +103,11 @@ export const useFirebaseAuthActions = () => { window.open(response.checkout_url, '_blank') }, reportError) - const accessBillingPortal = wrapWithErrorHandlingAsync(async () => { - const response = await authStore.accessBillingPortal() + const accessBillingPortal = wrapWithErrorHandlingAsync< + [targetTier?: BillingPortalTargetTier, openInNewTab?: boolean], + void + >(async (targetTier, openInNewTab = true) => { + const response = await authStore.accessBillingPortal(targetTier) if (!response.billing_portal_url) { throw new Error( t('toastMessages.failedToAccessBillingPortal', { @@ -111,7 +115,11 @@ export const useFirebaseAuthActions = () => { }) ) } - window.open(response.billing_portal_url, '_blank') + if (openInNewTab) { + window.open(response.billing_portal_url, '_blank') + } else { + globalThis.location.href = response.billing_portal_url + } }, reportError) const fetchBalance = wrapWithErrorHandlingAsync(async () => { diff --git a/tests-ui/tests/composables/canvas/useSelectedLiteGraphItems.test.ts b/src/composables/canvas/useSelectedLiteGraphItems.test.ts similarity index 90% rename from tests-ui/tests/composables/canvas/useSelectedLiteGraphItems.test.ts rename to src/composables/canvas/useSelectedLiteGraphItems.test.ts index 303dddc08..8e16448f4 100644 --- a/tests-ui/tests/composables/canvas/useSelectedLiteGraphItems.test.ts +++ b/src/composables/canvas/useSelectedLiteGraphItems.test.ts @@ -2,14 +2,13 @@ import { createPinia, setActivePinia } from 'pinia' import { beforeEach, describe, expect, it, vi } from 'vitest' import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems' -import type { LGraphNode } from '@/lib/litegraph/src/litegraph' -import { - LGraphEventMode, - type Positionable, - Reroute -} from '@/lib/litegraph/src/litegraph' +import type { LGraphNode, Positionable } from '@/lib/litegraph/src/litegraph' +import { LGraphEventMode, Reroute } from '@/lib/litegraph/src/litegraph' import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' import { app } from '@/scripts/app' +import type { NodeId } from '@/renderer/core/layout/types' +import type { ReadOnlyRect } from '@/lib/litegraph/src/interfaces' +import { createMockSubgraphNode } from '@/utils/__tests__/litegraphTestUtils' // Mock the app module vi.mock('@/scripts/app', () => ({ @@ -33,10 +32,12 @@ vi.mock('@/lib/litegraph/src/litegraph', () => ({ })) // Mock Positionable objects -// @ts-expect-error - Mock implementation for testing + class MockNode implements Positionable { pos: [number, number] size: [number, number] + id: NodeId + boundingRect: ReadOnlyRect constructor( pos: [number, number] = [0, 0], @@ -44,6 +45,13 @@ class MockNode implements Positionable { ) { this.pos = pos this.size = size + this.id = 'mock-node' + this.boundingRect = [0, 0, 0, 0] + } + + move(): void {} + snapToGrid(_: number): boolean { + return true } } @@ -65,7 +73,7 @@ class MockReroute extends Reroute implements Positionable { describe('useSelectedLiteGraphItems', () => { let canvasStore: ReturnType - let mockCanvas: any + let mockCanvas: { selectedItems: Set } beforeEach(() => { setActivePinia(createPinia()) @@ -77,7 +85,9 @@ describe('useSelectedLiteGraphItems', () => { } // Mock getCanvas to return our mock canvas - vi.spyOn(canvasStore, 'getCanvas').mockReturnValue(mockCanvas) + vi.spyOn(canvasStore, 'getCanvas').mockReturnValue( + mockCanvas as ReturnType + ) }) describe('isIgnoredItem', () => { @@ -90,7 +100,6 @@ describe('useSelectedLiteGraphItems', () => { it('should return false for non-Reroute items', () => { const { isIgnoredItem } = useSelectedLiteGraphItems() const node = new MockNode() - // @ts-expect-error - Test mock expect(isIgnoredItem(node)).toBe(false) }) }) @@ -102,14 +111,11 @@ describe('useSelectedLiteGraphItems', () => { const node2 = new MockNode([100, 100]) const reroute = new MockReroute([50, 50]) - // @ts-expect-error - Test mocks const items = new Set([node1, node2, reroute]) const filtered = filterSelectableItems(items) expect(filtered.size).toBe(2) - // @ts-expect-error - Test mocks expect(filtered.has(node1)).toBe(true) - // @ts-expect-error - Test mocks expect(filtered.has(node2)).toBe(true) expect(filtered.has(reroute)).toBe(false) }) @@ -147,9 +153,7 @@ describe('useSelectedLiteGraphItems', () => { const selectableItems = getSelectableItems() expect(selectableItems.size).toBe(2) - // @ts-expect-error - Test mock expect(selectableItems.has(node1)).toBe(true) - // @ts-expect-error - Test mock expect(selectableItems.has(node2)).toBe(true) expect(selectableItems.has(reroute)).toBe(false) }) @@ -259,14 +263,7 @@ describe('useSelectedLiteGraphItems', () => { const { getSelectedNodes } = useSelectedLiteGraphItems() const subNode1 = { id: 11, mode: LGraphEventMode.ALWAYS } as LGraphNode const subNode2 = { id: 12, mode: LGraphEventMode.NEVER } as LGraphNode - const subgraphNode = { - id: 1, - mode: LGraphEventMode.ALWAYS, - isSubgraphNode: () => true, - subgraph: { - nodes: [subNode1, subNode2] - } - } as unknown as LGraphNode + const subgraphNode = createMockSubgraphNode([subNode1, subNode2]) const regularNode = { id: 2, mode: LGraphEventMode.NEVER } as LGraphNode app.canvas.selected_nodes = { '0': subgraphNode, '1': regularNode } @@ -283,14 +280,7 @@ describe('useSelectedLiteGraphItems', () => { const { toggleSelectedNodesMode } = useSelectedLiteGraphItems() const subNode1 = { id: 11, mode: LGraphEventMode.ALWAYS } as LGraphNode const subNode2 = { id: 12, mode: LGraphEventMode.NEVER } as LGraphNode - const subgraphNode = { - id: 1, - mode: LGraphEventMode.ALWAYS, - isSubgraphNode: () => true, - subgraph: { - nodes: [subNode1, subNode2] - } - } as unknown as LGraphNode + const subgraphNode = createMockSubgraphNode([subNode1, subNode2]) const regularNode = { id: 2, mode: LGraphEventMode.BYPASS } as LGraphNode app.canvas.selected_nodes = { '0': subgraphNode, '1': regularNode } @@ -314,14 +304,10 @@ describe('useSelectedLiteGraphItems', () => { const { toggleSelectedNodesMode } = useSelectedLiteGraphItems() const subNode1 = { id: 11, mode: LGraphEventMode.ALWAYS } as LGraphNode const subNode2 = { id: 12, mode: LGraphEventMode.BYPASS } as LGraphNode - const subgraphNode = { + const subgraphNode = createMockSubgraphNode([subNode1, subNode2], { id: 1, - mode: LGraphEventMode.NEVER, // Already in NEVER mode - isSubgraphNode: () => true, - subgraph: { - nodes: [subNode1, subNode2] - } - } as unknown as LGraphNode + mode: LGraphEventMode.NEVER // Already in NEVER mode + }) app.canvas.selected_nodes = { '0': subgraphNode } diff --git a/src/composables/canvas/useSelectionToolboxPosition.ts b/src/composables/canvas/useSelectionToolboxPosition.ts index 0d15b3a12..734628bfb 100644 --- a/src/composables/canvas/useSelectionToolboxPosition.ts +++ b/src/composables/canvas/useSelectionToolboxPosition.ts @@ -21,10 +21,10 @@ import { computeUnionBounds } from '@/utils/mathUtil' */ // Shared signals for auxiliary UI (e.g., MoreOptions) to coordinate hide/restore -export const moreOptionsOpen = ref(false) -export const forceCloseMoreOptionsSignal = ref(0) -export const restoreMoreOptionsSignal = ref(0) -export const moreOptionsRestorePending = ref(false) +const moreOptionsOpen = ref(false) +const forceCloseMoreOptionsSignal = ref(0) +const restoreMoreOptionsSignal = ref(0) +const moreOptionsRestorePending = ref(false) let moreOptionsWasOpenBeforeDrag = false let moreOptionsSelectionSignature: string | null = null diff --git a/src/composables/element/useResponsiveCollapse.ts b/src/composables/element/useResponsiveCollapse.ts deleted file mode 100644 index 4f1846865..000000000 --- a/src/composables/element/useResponsiveCollapse.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { breakpointsTailwind, useBreakpoints } from '@vueuse/core' -import { ref, watch } from 'vue' - -type BreakpointKey = keyof typeof breakpointsTailwind - -/** - * Composable for element with responsive collapsed state - * @param breakpointThreshold - Breakpoint at which the element should become collapsible - */ -export const useResponsiveCollapse = ( - breakpointThreshold: BreakpointKey = 'lg' -) => { - const breakpoints = useBreakpoints(breakpointsTailwind) - - const isSmallScreen = breakpoints.smallerOrEqual(breakpointThreshold) - const isOpen = ref(!isSmallScreen.value) - - /** - * Handles screen size changes to automatically open/close the element - * when crossing the breakpoint threshold - */ - const onIsSmallScreenChange = () => { - if (isSmallScreen.value && isOpen.value) { - isOpen.value = false - } else if (!isSmallScreen.value && !isOpen.value) { - isOpen.value = true - } - } - - watch(isSmallScreen, onIsSmallScreenChange) - - return { - breakpoints, - isOpen, - isSmallScreen, - - open: () => (isOpen.value = true), - close: () => (isOpen.value = false), - toggle: () => (isOpen.value = !isOpen.value) - } -} diff --git a/tests-ui/tests/composables/functional/useChainCallback.test.ts b/src/composables/functional/useChainCallback.test.ts similarity index 100% rename from tests-ui/tests/composables/functional/useChainCallback.test.ts rename to src/composables/functional/useChainCallback.test.ts diff --git a/src/composables/functional/useChainCallback.ts b/src/composables/functional/useChainCallback.ts index 47e32b9fb..833cd4a18 100644 --- a/src/composables/functional/useChainCallback.ts +++ b/src/composables/functional/useChainCallback.ts @@ -1,19 +1,3 @@ -/** - * Shorthand for {@link Parameters} of optional callbacks. - * - * @example - * ```ts - * const { onClick } = CustomClass.prototype - * CustomClass.prototype.onClick = function (...args: CallbackParams) { - * const r = onClick?.apply(this, args) - * // ... - * return r - * } - * ``` - */ -export type CallbackParams any) | undefined> = - Parameters> - /** * Chain multiple callbacks together. * @@ -21,15 +5,21 @@ export type CallbackParams any) | undefined> = * @param callbacks - The callbacks to chain. * @returns A new callback that chains the original callback with the callbacks. */ -export const useChainCallback = < - O, - T extends (this: O, ...args: any[]) => void ->( +export function useChainCallback( originalCallback: T | undefined, - ...callbacks: ((this: O, ...args: Parameters) => void)[] -) => { - return function (this: O, ...args: Parameters) { - originalCallback?.call(this, ...args) - for (const callback of callbacks) callback.call(this, ...args) - } + ...callbacks: NonNullable extends (this: O, ...args: infer P) => unknown + ? ((this: O, ...args: P) => void)[] + : never +) { + type Args = NonNullable extends (...args: infer P) => unknown ? P : never + type Ret = NonNullable extends (...args: unknown[]) => infer R ? R : never + + return function (this: O, ...args: Args) { + if (typeof originalCallback === 'function') { + ;(originalCallback as (this: O, ...args: Args) => Ret).call(this, ...args) + } + for (const callback of callbacks) { + callback.call(this, ...args) + } + } as (this: O, ...args: Args) => Ret } diff --git a/src/composables/graph/contextMenuConverter.test.ts b/src/composables/graph/contextMenuConverter.test.ts new file mode 100644 index 000000000..cc157f220 --- /dev/null +++ b/src/composables/graph/contextMenuConverter.test.ts @@ -0,0 +1,190 @@ +import { describe, it, expect } from 'vitest' + +import type { MenuOption } from './useMoreOptionsMenu' +import { + buildStructuredMenu, + convertContextMenuToOptions +} from './contextMenuConverter' + +describe('contextMenuConverter', () => { + describe('buildStructuredMenu', () => { + it('should order core items before extension items', () => { + const options: MenuOption[] = [ + { label: 'Custom Extension Item', source: 'litegraph' }, + { label: 'Copy', source: 'vue' }, + { label: 'Rename', source: 'vue' } + ] + + const result = buildStructuredMenu(options) + + // Core items (Rename, Copy) should come before extension items + const renameIndex = result.findIndex((opt) => opt.label === 'Rename') + const copyIndex = result.findIndex((opt) => opt.label === 'Copy') + const extensionIndex = result.findIndex( + (opt) => opt.label === 'Custom Extension Item' + ) + + expect(renameIndex).toBeLessThan(extensionIndex) + expect(copyIndex).toBeLessThan(extensionIndex) + }) + + it('should add Extensions category label before extension items', () => { + const options: MenuOption[] = [ + { label: 'Copy', source: 'vue' }, + { label: 'My Custom Extension', source: 'litegraph' } + ] + + const result = buildStructuredMenu(options) + + const extensionsLabel = result.find( + (opt) => opt.label === 'Extensions' && opt.type === 'category' + ) + expect(extensionsLabel).toBeDefined() + expect(extensionsLabel?.disabled).toBe(true) + }) + + it('should place Delete at the very end', () => { + const options: MenuOption[] = [ + { label: 'Delete', action: () => {}, source: 'vue' }, + { label: 'Copy', source: 'vue' }, + { label: 'Rename', source: 'vue' } + ] + + const result = buildStructuredMenu(options) + + const lastNonDivider = [...result] + .reverse() + .find((opt) => opt.type !== 'divider') + expect(lastNonDivider?.label).toBe('Delete') + }) + + it('should deduplicate items with same label, preferring vue source', () => { + const options: MenuOption[] = [ + { label: 'Copy', action: () => {}, source: 'litegraph' }, + { label: 'Copy', action: () => {}, source: 'vue' } + ] + + const result = buildStructuredMenu(options) + + const copyItems = result.filter((opt) => opt.label === 'Copy') + expect(copyItems).toHaveLength(1) + expect(copyItems[0].source).toBe('vue') + }) + + it('should preserve dividers between sections', () => { + const options: MenuOption[] = [ + { label: 'Rename', source: 'vue' }, + { label: 'Copy', source: 'vue' }, + { label: 'Pin', source: 'vue' } + ] + + const result = buildStructuredMenu(options) + + const dividers = result.filter((opt) => opt.type === 'divider') + expect(dividers.length).toBeGreaterThan(0) + }) + + it('should handle empty input', () => { + const result = buildStructuredMenu([]) + expect(result).toEqual([]) + }) + + it('should handle only dividers', () => { + const options: MenuOption[] = [{ type: 'divider' }, { type: 'divider' }] + + const result = buildStructuredMenu(options) + + // Should be empty since dividers are filtered initially + expect(result).toEqual([]) + }) + + it('should recognize Remove as equivalent to Delete', () => { + const options: MenuOption[] = [ + { label: 'Remove', action: () => {}, source: 'vue' }, + { label: 'Copy', source: 'vue' } + ] + + const result = buildStructuredMenu(options) + + // Remove should be placed at the end like Delete + const lastNonDivider = [...result] + .reverse() + .find((opt) => opt.type !== 'divider') + expect(lastNonDivider?.label).toBe('Remove') + }) + + it('should group core items in correct section order', () => { + const options: MenuOption[] = [ + { label: 'Color', source: 'vue' }, + { label: 'Node Info', source: 'vue' }, + { label: 'Pin', source: 'vue' }, + { label: 'Rename', source: 'vue' } + ] + + const result = buildStructuredMenu(options) + + // Get indices of items (excluding dividers and categories) + const getIndex = (label: string) => + result.findIndex((opt) => opt.label === label) + + // Rename (section 1) should come before Pin (section 2) + expect(getIndex('Rename')).toBeLessThan(getIndex('Pin')) + // Pin (section 2) should come before Node Info (section 4) + expect(getIndex('Pin')).toBeLessThan(getIndex('Node Info')) + // Node Info (section 4) should come before or with Color (section 4) + expect(getIndex('Node Info')).toBeLessThanOrEqual(getIndex('Color')) + }) + }) + + describe('convertContextMenuToOptions', () => { + it('should convert empty array to empty result', () => { + const result = convertContextMenuToOptions([]) + expect(result).toEqual([]) + }) + + it('should convert null items to dividers', () => { + const result = convertContextMenuToOptions([null], undefined, false) + expect(result).toHaveLength(1) + expect(result[0].type).toBe('divider') + }) + + it('should skip blacklisted items like Properties', () => { + const items = [{ content: 'Properties', callback: () => {} }] + const result = convertContextMenuToOptions(items, undefined, false) + expect(result.find((opt) => opt.label === 'Properties')).toBeUndefined() + }) + + it('should convert basic menu items with content', () => { + const items = [{ content: 'Test Item', callback: () => {} }] + const result = convertContextMenuToOptions(items, undefined, false) + expect(result).toHaveLength(1) + expect(result[0].label).toBe('Test Item') + }) + + it('should mark items as litegraph source', () => { + const items = [{ content: 'Test Item', callback: () => {} }] + const result = convertContextMenuToOptions(items, undefined, false) + expect(result[0].source).toBe('litegraph') + }) + + it('should pass through disabled state', () => { + const items = [{ content: 'Disabled Item', disabled: true }] + const result = convertContextMenuToOptions(items, undefined, false) + expect(result[0].disabled).toBe(true) + }) + + it('should apply structuring by default', () => { + const items = [ + { content: 'Copy', callback: () => {} }, + { content: 'Custom Extension', callback: () => {} } + ] + const result = convertContextMenuToOptions(items) + + // With structuring, there should be Extensions category + const hasExtensionsCategory = result.some( + (opt) => opt.label === 'Extensions' && opt.type === 'category' + ) + expect(hasExtensionsCategory).toBe(true) + }) + }) +}) diff --git a/src/composables/graph/contextMenuConverter.ts b/src/composables/graph/contextMenuConverter.ts new file mode 100644 index 000000000..3395e18be --- /dev/null +++ b/src/composables/graph/contextMenuConverter.ts @@ -0,0 +1,620 @@ +import { default as DOMPurify } from 'dompurify' + +import { LiteGraph } from '@/lib/litegraph/src/litegraph' +import type { + IContextMenuValue, + LGraphNode, + IContextMenuOptions, + ContextMenu +} from '@/lib/litegraph/src/litegraph' + +import type { MenuOption, SubMenuOption } from './useMoreOptionsMenu' +import type { ContextMenuDivElement } from '@/lib/litegraph/src/interfaces' + +/** + * Hard blacklist - items that should NEVER be included + */ +const HARD_BLACKLIST = new Set([ + 'Properties', // Never include Properties submenu + 'Colors', // Use singular "Color" instead + 'Shapes', // Use singular "Shape" instead + 'Title', + 'Mode', + 'Properties Panel', + 'Copy (Clipspace)' +]) + +/** + * Core menu items - items that should appear in the main menu, not under Extensions + * Includes both LiteGraph base menu items and ComfyUI built-in functionality + */ +const CORE_MENU_ITEMS = new Set([ + // Basic operations + 'Rename', + 'Copy', + 'Duplicate', + 'Clone', + // Node state operations + 'Run Branch', + 'Pin', + 'Unpin', + 'Bypass', + 'Remove Bypass', + 'Mute', + // Structure operations + 'Convert to Subgraph', + 'Frame selection', + 'Minimize Node', + 'Expand', + 'Collapse', + // Info and adjustments + 'Node Info', + 'Resize', + 'Title', + 'Properties Panel', + 'Adjust Size', + // Visual + 'Color', + 'Colors', + 'Shape', + 'Shapes', + 'Mode', + // Built-in node operations (node-specific) + 'Open Image', + 'Copy Image', + 'Save Image', + 'Open in Mask Editor', + 'Edit Subgraph Widgets', + 'Unpack Subgraph', + 'Copy (Clipspace)', + 'Paste (Clipspace)', + // Selection and alignment + 'Align Selected To', + 'Distribute Nodes', + // Deletion + 'Delete', + 'Remove', + // LiteGraph base items + 'Show Advanced', + 'Hide Advanced' +]) + +/** + * Normalize menu item label for duplicate detection + * Handles variations like Colors/Color, Shapes/Shape, Pin/Unpin, Remove/Delete + */ +function normalizeLabel(label: string): string { + return label + .toLowerCase() + .replace(/^un/, '') // Remove 'un' prefix (Unpin -> Pin) + .trim() +} + +/** + * Check if a similar menu item already exists in the results + * Returns true if an item with the same normalized label exists + */ +function isDuplicateItem(label: string, existingItems: MenuOption[]): boolean { + const normalizedLabel = normalizeLabel(label) + + // Map of equivalent items + const equivalents: Record = { + color: ['color', 'colors'], + shape: ['shape', 'shapes'], + pin: ['pin', 'unpin'], + delete: ['remove', 'delete'], + duplicate: ['clone', 'duplicate'] + } + + return existingItems.some((item) => { + if (!item.label) return false + + const existingNormalized = normalizeLabel(item.label) + + // Check direct match + if (existingNormalized === normalizedLabel) return true + + // Check if they're in the same equivalence group + for (const values of Object.values(equivalents)) { + if ( + values.includes(normalizedLabel) && + values.includes(existingNormalized) + ) { + return true + } + } + + return false + }) +} + +/** + * Check if a menu item is a core menu item (not an extension) + * Core items include LiteGraph base items and ComfyUI built-in functionality + */ +function isCoreMenuItem(label: string): boolean { + return CORE_MENU_ITEMS.has(label) +} + +/** + * Filter out duplicate menu items based on label + * Gives precedence to Vue hardcoded options over LiteGraph options + */ +function removeDuplicateMenuOptions(options: MenuOption[]): MenuOption[] { + // Group items by label + const itemsByLabel = new Map() + const itemsWithoutLabel: MenuOption[] = [] + + for (const opt of options) { + // Always keep dividers and category items + if (opt.type === 'divider' || opt.type === 'category') { + itemsWithoutLabel.push(opt) + continue + } + + // Items without labels are kept as-is + if (!opt.label) { + itemsWithoutLabel.push(opt) + continue + } + + // Group by label + if (!itemsByLabel.has(opt.label)) { + itemsByLabel.set(opt.label, []) + } + itemsByLabel.get(opt.label)!.push(opt) + } + + // Select best item for each label (prefer vue over litegraph) + const result: MenuOption[] = [] + const seenLabels = new Set() + + for (const opt of options) { + // Add non-labeled items in original order + if (opt.type === 'divider' || opt.type === 'category' || !opt.label) { + if (itemsWithoutLabel.includes(opt)) { + result.push(opt) + const idx = itemsWithoutLabel.indexOf(opt) + itemsWithoutLabel.splice(idx, 1) + } + continue + } + + // Skip if we already processed this label + if (seenLabels.has(opt.label)) { + continue + } + seenLabels.add(opt.label) + + // Get all items with this label + const duplicates = itemsByLabel.get(opt.label)! + + // If only one item, add it + if (duplicates.length === 1) { + result.push(duplicates[0]) + continue + } + + // Multiple items: prefer vue source over litegraph + const vueItem = duplicates.find((item) => item.source === 'vue') + if (vueItem) { + result.push(vueItem) + } else { + // No vue item, just take the first one + result.push(duplicates[0]) + } + } + + return result +} + +/** + * Order groups for menu items - defines the display order of sections + */ +const MENU_ORDER: string[] = [ + // Section 1: Basic operations + 'Rename', + 'Copy', + 'Duplicate', + // Section 2: Node actions + 'Run Branch', + 'Pin', + 'Unpin', + 'Bypass', + 'Remove Bypass', + 'Mute', + // Section 3: Structure operations + 'Convert to Subgraph', + 'Frame selection', + 'Minimize Node', + 'Expand', + 'Collapse', + 'Resize', + 'Clone', + // Section 4: Node properties + 'Node Info', + 'Color', + // Section 5: Node-specific operations + 'Open in Mask Editor', + 'Open Image', + 'Copy Image', + 'Save Image', + 'Copy (Clipspace)', + 'Paste (Clipspace)', + // Fallback for other core items + 'Convert to Group Node (Deprecated)' +] + +/** + * Get the order index for a menu item (lower = earlier in menu) + */ +function getMenuItemOrder(label: string): number { + const index = MENU_ORDER.indexOf(label) + return index === -1 ? 999 : index +} + +/** + * Build structured menu with core items first, then extensions under a labeled section + * Ensures Delete always appears at the bottom + */ +export function buildStructuredMenu(options: MenuOption[]): MenuOption[] { + // First, remove duplicates (giving precedence to Vue hardcoded options) + const deduplicated = removeDuplicateMenuOptions(options) + const coreItemsMap = new Map() + const extensionItems: MenuOption[] = [] + let deleteItem: MenuOption | undefined + + // Separate items into core and extension categories + for (const option of deduplicated) { + // Skip dividers for now - we'll add them between sections later + if (option.type === 'divider') { + continue + } + + // Skip category labels (they'll be added separately) + if (option.type === 'category') { + continue + } + + // Check if this is the Delete/Remove item - save it for the end + const isDeleteItem = option.label === 'Delete' || option.label === 'Remove' + if (isDeleteItem && !option.hasSubmenu) { + deleteItem = option + continue + } + + // Categorize based on label + if (option.label && isCoreMenuItem(option.label)) { + coreItemsMap.set(option.label, option) + } else { + extensionItems.push(option) + } + } + // Build ordered core items based on MENU_ORDER + const orderedCoreItems: MenuOption[] = [] + const coreLabels = Array.from(coreItemsMap.keys()) + coreLabels.sort((a, b) => getMenuItemOrder(a) - getMenuItemOrder(b)) + + // Section boundaries based on MENU_ORDER indices + // Section 1: 0-2 (Rename, Copy, Duplicate) + // Section 2: 3-8 (Run Branch, Pin, Unpin, Bypass, Remove Bypass, Mute) + // Section 3: 9-15 (Convert to Subgraph, Frame selection, Minimize Node, Expand, Collapse, Resize, Clone) + // Section 4: 16-17 (Node Info, Color) + // Section 5: 18+ (Image operations and fallback items) + const getSectionNumber = (index: number): number => { + if (index <= 2) return 1 + if (index <= 8) return 2 + if (index <= 15) return 3 + if (index <= 17) return 4 + return 5 + } + + let lastSection = 0 + for (const label of coreLabels) { + const item = coreItemsMap.get(label)! + const itemIndex = getMenuItemOrder(label) + const currentSection = getSectionNumber(itemIndex) + + // Add divider when moving to a new section + if (lastSection > 0 && currentSection !== lastSection) { + orderedCoreItems.push({ type: 'divider' }) + } + + orderedCoreItems.push(item) + lastSection = currentSection + } + + // Build the final menu structure + const result: MenuOption[] = [] + + // Add ordered core items with their dividers + result.push(...orderedCoreItems) + + // Add extensions section if there are extension items + if (extensionItems.length > 0) { + // Add divider before Extensions section + result.push({ type: 'divider' }) + + // Add non-clickable Extensions label + result.push({ + label: 'Extensions', + type: 'category', + disabled: true + }) + + // Add extension items + result.push(...extensionItems) + } + + // Add Delete at the bottom if it exists + if (deleteItem) { + result.push({ type: 'divider' }) + result.push(deleteItem) + } + + return result +} + +/** + * Convert LiteGraph IContextMenuValue items to Vue MenuOption format + * Used to bridge LiteGraph context menus into Vue node menus + * @param items - The LiteGraph menu items to convert + * @param node - The node context (optional) + * @param applyStructuring - Whether to apply menu structuring (core/extensions separation). Defaults to true. + */ +export function convertContextMenuToOptions( + items: (IContextMenuValue | null)[], + node?: LGraphNode, + applyStructuring: boolean = true +): MenuOption[] { + const result: MenuOption[] = [] + + for (const item of items) { + // Null items are separators in LiteGraph + if (item === null) { + result.push({ type: 'divider' }) + continue + } + + // Skip items without content (shouldn't happen, but be safe) + if (!item.content) { + continue + } + + // Skip hard blacklisted items + if (HARD_BLACKLIST.has(item.content)) { + continue + } + + // Skip if a similar item already exists in results + if (isDuplicateItem(item.content, result)) { + continue + } + + const option: MenuOption = { + label: item.content, + source: 'litegraph' + } + + // Pass through disabled state + if (item.disabled) { + option.disabled = true + } + + // Handle submenus + if (item.has_submenu) { + // Static submenu with pre-defined options + if (item.submenu?.options) { + option.hasSubmenu = true + option.submenu = convertSubmenuToOptions(item.submenu.options) + } + // Dynamic submenu - callback creates it on-demand + else if (item.callback && !item.disabled) { + option.hasSubmenu = true + // Intercept the callback to capture dynamic submenu items + const capturedSubmenu = captureDynamicSubmenu(item, node) + if (capturedSubmenu) { + option.submenu = capturedSubmenu + } else { + console.warn( + '[ContextMenuConverter] Failed to capture submenu for:', + item.content + ) + } + } + } + // Handle callback (only if not disabled and not a submenu) + else if (item.callback && !item.disabled) { + // Wrap the callback to match the () => void signature + option.action = () => { + try { + void item.callback?.call( + item as unknown as ContextMenuDivElement, + item.value, + {}, + undefined, + undefined, + item + ) + } catch (error) { + console.error('Error executing context menu callback:', error) + } + } + } + + result.push(option) + } + + // Apply structured menu with core items and extensions section (if requested) + if (applyStructuring) { + return buildStructuredMenu(result) + } + + return result +} + +/** + * Capture submenu items from a dynamic submenu callback + * Intercepts ContextMenu constructor to extract items without creating HTML menu + */ +function captureDynamicSubmenu( + item: IContextMenuValue, + node?: LGraphNode +): SubMenuOption[] | undefined { + let capturedItems: readonly (IContextMenuValue | string | null)[] | undefined + let capturedOptions: IContextMenuOptions | undefined + + // Store original ContextMenu constructor + const OriginalContextMenu = LiteGraph.ContextMenu + + try { + // Mock ContextMenu constructor to capture submenu items and options + LiteGraph.ContextMenu = function ( + items: readonly (IContextMenuValue | string | null)[], + options?: IContextMenuOptions + ) { + // Capture both items and options + capturedItems = items + capturedOptions = options + // Return a minimal mock object to prevent errors + return { + close: () => {}, + root: document.createElement('div') + } as unknown as ContextMenu + } as unknown as typeof ContextMenu + + // Execute the callback to trigger submenu creation + try { + // Create a mock MouseEvent for the callback + const mockEvent = new MouseEvent('click', { + bubbles: true, + cancelable: true, + clientX: 0, + clientY: 0 + }) + + // Create a mock parent menu + const mockMenu = { + close: () => {}, + root: document.createElement('div') + } as unknown as ContextMenu + + // Call the callback which should trigger ContextMenu constructor + // Callback signature varies, but typically: (value, options, event, menu, node) + void item.callback?.call( + item as unknown as ContextMenuDivElement, + item.value, + {}, + mockEvent, + mockMenu, + node // Pass the node context for callbacks that need it + ) + } catch (error) { + console.warn( + '[ContextMenuConverter] Error executing callback for:', + item.content, + error + ) + } + } finally { + // Always restore original constructor + LiteGraph.ContextMenu = OriginalContextMenu + } + + // Convert captured items to Vue submenu format + if (capturedItems) { + const converted = convertSubmenuToOptions(capturedItems, capturedOptions) + return converted + } + + console.warn('[ContextMenuConverter] No items captured for:', item.content) + return undefined +} + +/** + * Convert LiteGraph submenu items to Vue SubMenuOption format + */ +function convertSubmenuToOptions( + items: readonly (IContextMenuValue | string | null)[], + options?: IContextMenuOptions +): SubMenuOption[] { + const result: SubMenuOption[] = [] + + for (const item of items) { + // Skip null separators + if (item === null) { + continue + } + + // Handle string items (simple labels like in Mode/Shapes menus) + if (typeof item === 'string') { + const subOption: SubMenuOption = { + label: item, + action: () => { + try { + // Call the options callback with the string value + if (options?.callback) { + void options.callback.call( + null, + item, + options, + undefined, + undefined, + options.extra + ) + } + } catch (error) { + console.error('Error executing string item callback:', error) + } + } + } + result.push(subOption) + continue + } + + // Handle object items + if (!item.content) { + continue + } + + // Extract text content from HTML if present + const content = stripHtmlTags(item.content) + + const subOption: SubMenuOption = { + label: content, + action: () => { + try { + void item.callback?.call( + item as unknown as ContextMenuDivElement, + item.value, + {}, + undefined, + undefined, + item + ) + } catch (error) { + console.error('Error executing submenu callback:', error) + } + } + } + + // Pass through disabled state + if (item.disabled) { + subOption.disabled = true + } + + result.push(subOption) + } + return result +} + +/** + * Strip HTML tags from content string safely + * LiteGraph menu items often include HTML for styling + */ +function stripHtmlTags(html: string): string { + // Use DOMPurify to sanitize and strip all HTML tags + const sanitized = DOMPurify.sanitize(html, { ALLOWED_TAGS: [] }) + const result = sanitized.trim() + return result || html.replace(/<[^>]*>/g, '').trim() || html +} diff --git a/src/composables/graph/useGraphHierarchy.test.ts b/src/composables/graph/useGraphHierarchy.test.ts new file mode 100644 index 000000000..6212cdbd6 --- /dev/null +++ b/src/composables/graph/useGraphHierarchy.test.ts @@ -0,0 +1,158 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { Rectangle } from '@/lib/litegraph/src/infrastructure/Rectangle' +import type { LGraphGroup, LGraphNode } from '@/lib/litegraph/src/litegraph' +import * as measure from '@/lib/litegraph/src/measure' +import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' +import { + createMockLGraphNode, + createMockLGraphGroup +} from '@/utils/__tests__/litegraphTestUtils' + +import { useGraphHierarchy } from './useGraphHierarchy' + +vi.mock('@/renderer/core/canvas/canvasStore') + +function createMockNode(overrides: Partial = {}): LGraphNode { + return { + ...createMockLGraphNode(), + boundingRect: new Rectangle(100, 100, 50, 50), + ...overrides + } as LGraphNode +} + +function createMockGroup(overrides: Partial = {}): LGraphGroup { + return createMockLGraphGroup(overrides) +} + +describe('useGraphHierarchy', () => { + let mockCanvasStore: Partial> + let mockNode: LGraphNode + let mockGroups: LGraphGroup[] + + beforeEach(() => { + mockNode = createMockNode() + mockGroups = [] + + mockCanvasStore = { + canvas: { + graph: { + groups: mockGroups + } + }, + $id: 'canvas', + $state: {}, + $patch: vi.fn(), + $reset: vi.fn(), + $subscribe: vi.fn(), + $onAction: vi.fn(), + $dispose: vi.fn(), + _customProperties: new Set(), + _p: {} + } as unknown as Partial> + + vi.mocked(useCanvasStore).mockReturnValue( + mockCanvasStore as ReturnType + ) + }) + + describe('findParentGroup', () => { + it('returns null when no groups exist', () => { + const { findParentGroup } = useGraphHierarchy() + + const result = findParentGroup(mockNode) + + expect(result).toBeNull() + }) + + it('returns null when node is not in any group', () => { + const group = createMockGroup({ + boundingRect: new Rectangle(0, 0, 50, 50) + }) + mockGroups.push(group) + + vi.spyOn(measure, 'containsCentre').mockReturnValue(false) + + const { findParentGroup } = useGraphHierarchy() + const result = findParentGroup(mockNode) + + expect(result).toBeNull() + }) + + it('returns the only group when node is in exactly one group', () => { + const group = createMockGroup({ + boundingRect: new Rectangle(0, 0, 200, 200) + }) + mockGroups.push(group) + + vi.spyOn(measure, 'containsCentre').mockReturnValue(true) + + const { findParentGroup } = useGraphHierarchy() + const result = findParentGroup(mockNode) + + expect(result).toBe(group) + }) + + it('returns the smallest group when node is in multiple groups', () => { + const largeGroup = createMockGroup({ + boundingRect: new Rectangle(0, 0, 300, 300) + }) + const smallGroup = createMockGroup({ + boundingRect: new Rectangle(50, 50, 100, 100) + }) + mockGroups.push(largeGroup, smallGroup) + + vi.spyOn(measure, 'containsCentre').mockReturnValue(true) + vi.spyOn(measure, 'containsRect').mockReturnValue(false) + + const { findParentGroup } = useGraphHierarchy() + const result = findParentGroup(mockNode) + + expect(result).toBe(smallGroup) + }) + + it('returns the inner group when one group contains another', () => { + const outerGroup = createMockGroup({ + boundingRect: new Rectangle(0, 0, 300, 300) + }) + const innerGroup = createMockGroup({ + boundingRect: new Rectangle(50, 50, 100, 100) + }) + mockGroups.push(outerGroup, innerGroup) + + vi.spyOn(measure, 'containsCentre').mockReturnValue(true) + vi.spyOn(measure, 'containsRect').mockImplementation( + (container, contained) => { + // outerGroup contains innerGroup + if (container === outerGroup.boundingRect) { + return contained === innerGroup.boundingRect + } + return false + } + ) + + const { findParentGroup } = useGraphHierarchy() + const result = findParentGroup(mockNode) + + expect(result).toBe(innerGroup) + }) + + it('handles null canvas gracefully', () => { + mockCanvasStore.canvas = null + + const { findParentGroup } = useGraphHierarchy() + const result = findParentGroup(mockNode) + + expect(result).toBeNull() + }) + + it('handles null graph gracefully', () => { + mockCanvasStore.canvas!.graph = null + + const { findParentGroup } = useGraphHierarchy() + const result = findParentGroup(mockNode) + + expect(result).toBeNull() + }) + }) +}) diff --git a/src/composables/graph/useGraphHierarchy.ts b/src/composables/graph/useGraphHierarchy.ts new file mode 100644 index 000000000..14e2e310f --- /dev/null +++ b/src/composables/graph/useGraphHierarchy.ts @@ -0,0 +1,57 @@ +import type { LGraphGroup, LGraphNode } from '@/lib/litegraph/src/litegraph' +import { containsCentre, containsRect } from '@/lib/litegraph/src/measure' +import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' + +/** + * Composable for working with graph hierarchy, specifically group containment. + */ +export function useGraphHierarchy() { + const canvasStore = useCanvasStore() + + /** + * Finds the smallest group that contains the center of a node's bounding box. + * When multiple groups contain the node, returns the one with the smallest area. + * + * TODO: This traverses the entire graph and could be very slow; needs optimization. + * Consider spatial indexing or caching for large graphs. + * + * @param node - The node to find the parent group for + * @returns The parent group if found, otherwise null + */ + function findParentGroup(node: LGraphNode): LGraphGroup | null { + const graphGroups = (canvasStore.canvas?.graph?.groups ?? + []) as LGraphGroup[] + + let parent: LGraphGroup | null = null + + for (const group of graphGroups) { + const groupRect = group.boundingRect + if (!containsCentre(groupRect, node.boundingRect)) continue + + if (!parent) { + parent = group + continue + } + + const parentRect = parent.boundingRect + const candidateInsideParent = containsRect(parentRect, groupRect) + const parentInsideCandidate = containsRect(groupRect, parentRect) + + if (candidateInsideParent && !parentInsideCandidate) { + parent = group + continue + } + + const candidateArea = groupRect[2] * groupRect[3] + const parentArea = parentRect[2] * parentRect[3] + + if (candidateArea < parentArea) parent = group + } + + return parent + } + + return { + findParentGroup + } +} diff --git a/src/composables/graph/useGraphNodeManager.test.ts b/src/composables/graph/useGraphNodeManager.test.ts new file mode 100644 index 000000000..ba22d98cd --- /dev/null +++ b/src/composables/graph/useGraphNodeManager.test.ts @@ -0,0 +1,49 @@ +import { setActivePinia } from 'pinia' +import { createTestingPinia } from '@pinia/testing' +import { describe, expect, it, vi } from 'vitest' +import { nextTick, watch } from 'vue' + +import { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager' +import { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph' +import { NodeSlotType } from '@/lib/litegraph/src/types/globalEnums' + +setActivePinia(createTestingPinia()) + +function createTestGraph() { + const graph = new LGraph() + const node = new LGraphNode('test') + node.addInput('input', 'INT') + node.addWidget('number', 'testnum', 2, () => undefined, {}) + graph.add(node) + + const { vueNodeData } = useGraphNodeManager(graph) + const onReactivityUpdate = vi.fn() + watch(vueNodeData, onReactivityUpdate) + + return [node, graph, onReactivityUpdate] as const +} + +describe('Node Reactivity', () => { + it('should trigger on callback', async () => { + const [node, , onReactivityUpdate] = createTestGraph() + + node.widgets![0].callback!(2) + await nextTick() + expect(onReactivityUpdate).toHaveBeenCalledTimes(1) + }) + + it('should remain reactive after a connection is made', async () => { + const [node, graph, onReactivityUpdate] = createTestGraph() + + graph.trigger('node:slot-links:changed', { + nodeId: '1', + slotType: NodeSlotType.INPUT + }) + await nextTick() + onReactivityUpdate.mockClear() + + node.widgets![0].callback!(2) + await nextTick() + expect(onReactivityUpdate).toHaveBeenCalledTimes(1) + }) +}) diff --git a/src/composables/graph/useGraphNodeManager.ts b/src/composables/graph/useGraphNodeManager.ts index 3d13eb9ec..33822d1c5 100644 --- a/src/composables/graph/useGraphNodeManager.ts +++ b/src/composables/graph/useGraphNodeManager.ts @@ -3,21 +3,26 @@ * Provides event-driven reactivity with performance optimizations */ import { reactiveComputed } from '@vueuse/core' -import { reactive, shallowReactive } from 'vue' +import { customRef, reactive, shallowReactive } from 'vue' import { useChainCallback } from '@/composables/functional/useChainCallback' +import { isProxyWidget } from '@/core/graph/subgraph/proxyWidget' import type { INodeInputSlot, INodeOutputSlot } from '@/lib/litegraph/src/interfaces' -import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets' +import type { + IBaseWidget, + IWidgetOptions +} from '@/lib/litegraph/src/types/widgets' import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations' import { LayoutSource } from '@/renderer/core/layout/types' import type { NodeId } from '@/renderer/core/layout/types' import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2' import { isDOMWidget } from '@/scripts/domWidget' import { useNodeDefStore } from '@/stores/nodeDefStore' -import type { WidgetValue } from '@/types/simplifiedWidget' +import type { WidgetValue, SafeControlWidget } from '@/types/simplifiedWidget' +import { normalizeControlOption } from '@/types/simplifiedWidget' import type { LGraph, @@ -26,8 +31,10 @@ import type { LGraphTriggerAction, LGraphTriggerEvent, LGraphTriggerParam -} from '../../lib/litegraph/src/litegraph' -import { NodeSlotType } from '../../lib/litegraph/src/types/globalEnums' +} from '@/lib/litegraph/src/litegraph' +import type { TitleMode } from '@/lib/litegraph/src/types/globalEnums' +import { NodeSlotType } from '@/lib/litegraph/src/types/globalEnums' +import { app } from '@/scripts/app' export interface WidgetSlotMetadata { index: number @@ -38,34 +45,42 @@ export interface SafeWidgetData { name: string type: string value: WidgetValue - label?: string - options?: Record + borderStyle?: string callback?: ((value: unknown) => void) | undefined + controlWidget?: SafeControlWidget + hasLayoutSize?: boolean + isDOMWidget?: boolean + label?: string + nodeType?: string + options?: IWidgetOptions spec?: InputSpec slotMetadata?: WidgetSlotMetadata - isDOMWidget?: boolean } export interface VueNodeData { + executing: boolean id: NodeId - title: string - type: string mode: number selected: boolean - executing: boolean + title: string + type: string apiNode?: boolean badges?: (LGraphBadge | (() => LGraphBadge))[] - subgraphId?: string | null - widgets?: SafeWidgetData[] - inputs?: INodeInputSlot[] - outputs?: INodeOutputSlot[] - hasErrors?: boolean + bgcolor?: string + color?: string flags?: { collapsed?: boolean pinned?: boolean } - color?: string - bgcolor?: string + hasErrors?: boolean + inputs?: INodeInputSlot[] + outputs?: INodeOutputSlot[] + resizable?: boolean + shape?: number + showAdvanced?: boolean + subgraphId?: string | null + titleMode?: TitleMode + widgets?: SafeWidgetData[] } export interface GraphNodeManager { @@ -75,53 +90,151 @@ export interface GraphNodeManager { // Access to original LiteGraph nodes (non-reactive) getNode(id: string): LGraphNode | undefined - // Update widget options (e.g., hidden, disabled) - triggers Vue reactivity - updateVueWidgetOptions( - nodeId: string, - widgetName: string, - options: Record - ): void - - // Refresh Vue widgets from LiteGraph node - use after modifying node.widgets - refreshVueWidgets(nodeId: string): void - // Lifecycle methods cleanup(): void } -export function safeWidgetMapper( +function widgetWithVueTrack( + widget: IBaseWidget +): asserts widget is IBaseWidget & { vueTrack: () => void } { + if (widget.vueTrack) return + + customRef((track, trigger) => { + widget.callback = useChainCallback(widget.callback, trigger) + widget.vueTrack = track + return { get() {}, set() {} } + }) +} +function useReactiveWidgetValue(widget: IBaseWidget) { + widgetWithVueTrack(widget) + widget.vueTrack() + return widget.value +} + +function getControlWidget(widget: IBaseWidget): SafeControlWidget | undefined { + const cagWidget = widget.linkedWidgets?.find( + (w) => w.name == 'control_after_generate' + ) + if (!cagWidget) return + return { + value: normalizeControlOption(cagWidget.value), + update: (value) => (cagWidget.value = normalizeControlOption(value)) + } +} + +function getNodeType(node: LGraphNode, widget: IBaseWidget) { + if (!node.isSubgraphNode() || !isProxyWidget(widget)) return undefined + const subNode = node.subgraph.getNodeById(widget._overlay.nodeId) + return subNode?.type +} + +/** + * Shared widget enhancements used by both safeWidgetMapper and Right Side Panel + */ +interface SharedWidgetEnhancements { + /** Reactive widget value that updates when the widget changes */ + value: WidgetValue + /** Control widget for seed randomization/increment/decrement */ + controlWidget?: SafeControlWidget + /** Input specification from node definition */ + spec?: InputSpec + /** Node type (for subgraph promoted widgets) */ + nodeType?: string + /** Border style for promoted/advanced widgets */ + borderStyle?: string + /** Widget label */ + label?: string + /** Widget options */ + options?: Record +} + +/** + * Extracts common widget enhancements shared across different rendering contexts. + * This function centralizes the logic for extracting metadata and reactive values + * from widgets, ensuring consistency between Nodes 2.0 and Right Side Panel. + */ +export function getSharedWidgetEnhancements( + node: LGraphNode, + widget: IBaseWidget +): SharedWidgetEnhancements { + const nodeDefStore = useNodeDefStore() + + return { + value: useReactiveWidgetValue(widget), + controlWidget: getControlWidget(widget), + spec: nodeDefStore.getInputSpecForWidget(node, widget.name), + nodeType: getNodeType(node, widget), + borderStyle: widget.promoted + ? 'ring ring-component-node-widget-promoted' + : widget.advanced + ? 'ring ring-component-node-widget-advanced' + : undefined, + label: widget.label, + options: widget.options + } +} + +/** + * Validates that a value is a valid WidgetValue type + */ +const normalizeWidgetValue = (value: unknown): WidgetValue => { + if (value === null || value === undefined || value === void 0) { + return undefined + } + if ( + typeof value === 'string' || + typeof value === 'number' || + typeof value === 'boolean' + ) { + return value + } + if (typeof value === 'object') { + // Check if it's a File array + if ( + Array.isArray(value) && + value.length > 0 && + value.every((item): item is File => item instanceof File) + ) { + return value + } + // Otherwise it's a generic object + return value + } + // If none of the above, return undefined + console.warn(`Invalid widget value type: ${typeof value}`, value) + return undefined +} + +function safeWidgetMapper( node: LGraphNode, slotMetadata: Map ): (widget: IBaseWidget) => SafeWidgetData { - const nodeDefStore = useNodeDefStore() return function (widget) { try { - // TODO: Use widget.getReactiveData() once TypeScript types are updated - let value = widget.value - - // For combo widgets, if value is undefined, use the first option as default - if ( - value === undefined && - widget.type === 'combo' && - widget.options?.values && - Array.isArray(widget.options.values) && - widget.options.values.length > 0 - ) { - value = widget.options.values[0] - } - const spec = nodeDefStore.getInputSpecForWidget(node, widget.name) + // Get shared enhancements used by both Nodes 2.0 and Right Side Panel + const sharedEnhancements = getSharedWidgetEnhancements(node, widget) const slotInfo = slotMetadata.get(widget.name) + // Wrapper callback specific to Nodes 2.0 rendering + const callback = (v: unknown) => { + const value = normalizeWidgetValue(v) + widget.value = value ?? undefined + // Match litegraph callback signature: (value, canvas, node, pos, event) + // Some extensions (e.g., Impact Pack) expect node as the 3rd parameter + widget.callback?.(value, app.canvas, node) + // Trigger redraw for all legacy widgets on this node (e.g., mask preview) + // This ensures widgets that depend on other widget values get updated + node.widgets?.forEach((w) => w.triggerDraw?.()) + } + return { name: widget.name, type: widget.type, - value: value, - label: widget.label, - options: widget.options ? { ...widget.options } : undefined, - callback: widget.callback, - spec, - slotMetadata: slotInfo, - isDOMWidget: isDOMWidget(widget) + ...sharedEnhancements, + callback, + hasLayoutSize: typeof widget.computeLayoutSize === 'function', + isDOMWidget: isDOMWidget(widget), + slotMetadata: slotInfo } } catch (error) { return { @@ -133,15 +246,78 @@ export function safeWidgetMapper( } } -export function isValidWidgetValue(value: unknown): value is WidgetValue { - return ( - value === null || - value === undefined || - typeof value === 'string' || - typeof value === 'number' || - typeof value === 'boolean' || - typeof value === 'object' - ) +// Extract safe data from LiteGraph node for Vue consumption +export function extractVueNodeData(node: LGraphNode): VueNodeData { + // Determine subgraph ID - null for root graph, string for subgraphs + const subgraphId = + node.graph && 'id' in node.graph && node.graph !== node.graph.rootGraph + ? String(node.graph.id) + : null + // Extract safe widget data + const slotMetadata = new Map() + + const reactiveWidgets = shallowReactive(node.widgets ?? []) + Object.defineProperty(node, 'widgets', { + get() { + return reactiveWidgets + }, + set(v) { + reactiveWidgets.splice(0, reactiveWidgets.length, ...v) + } + }) + const reactiveInputs = shallowReactive(node.inputs ?? []) + Object.defineProperty(node, 'inputs', { + get() { + return reactiveInputs + }, + set(v) { + reactiveInputs.splice(0, reactiveInputs.length, ...v) + } + }) + + const safeWidgets = reactiveComputed(() => { + node.inputs?.forEach((input, index) => { + if (!input?.widget?.name) return + slotMetadata.set(input.widget.name, { + index, + linked: input.link != null + }) + }) + return node.widgets?.map(safeWidgetMapper(node, slotMetadata)) ?? [] + }) + + const nodeType = + node.type || + node.constructor?.comfyClass || + node.constructor?.title || + node.constructor?.name || + 'Unknown' + + const apiNode = node.constructor?.nodeData?.api_node ?? false + const badges = node.badges + + return { + id: String(node.id), + title: typeof node.title === 'string' ? node.title : '', + type: nodeType, + mode: node.mode || 0, + titleMode: node.title_mode, + selected: node.selected || false, + executing: false, // Will be updated separately based on execution state + subgraphId, + apiNode, + badges, + hasErrors: !!node.has_errors, + widgets: safeWidgets, + inputs: reactiveInputs, + outputs: node.outputs ? [...node.outputs] : undefined, + flags: node.flags ? { ...node.flags } : undefined, + color: node.color || undefined, + bgcolor: node.bgcolor || undefined, + resizable: node.resizable, + shape: node.shape, + showAdvanced: node.showAdvanced + } } export function useGraphNodeManager(graph: LGraph): GraphNodeManager { @@ -171,77 +347,9 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager { }) // Update only widgets with new slot metadata, keeping other widget data intact - const updatedWidgets = currentData.widgets?.map((widget) => { + for (const widget of currentData.widgets ?? []) { const slotInfo = slotMetadata.get(widget.name) - return slotInfo ? { ...widget, slotMetadata: slotInfo } : widget - }) - - vueNodeData.set(nodeId, { - ...currentData, - widgets: updatedWidgets, - inputs: nodeRef.inputs ? [...nodeRef.inputs] : undefined, - outputs: nodeRef.outputs ? [...nodeRef.outputs] : undefined - }) - } - - // Extract safe data from LiteGraph node for Vue consumption - function extractVueNodeData(node: LGraphNode): VueNodeData { - // Determine subgraph ID - null for root graph, string for subgraphs - const subgraphId = - node.graph && 'id' in node.graph && node.graph !== node.graph.rootGraph - ? String(node.graph.id) - : null - // Extract safe widget data - const slotMetadata = new Map() - - const reactiveWidgets = shallowReactive(node.widgets ?? []) - Object.defineProperty(node, 'widgets', { - get() { - return reactiveWidgets - }, - set(v) { - reactiveWidgets.splice(0, reactiveWidgets.length, ...v) - } - }) - - const safeWidgets = reactiveComputed(() => { - node.inputs?.forEach((input, index) => { - if (!input?.widget?.name) return - slotMetadata.set(input.widget.name, { - index, - linked: input.link != null - }) - }) - return node.widgets?.map(safeWidgetMapper(node, slotMetadata)) ?? [] - }) - - const nodeType = - node.type || - node.constructor?.comfyClass || - node.constructor?.title || - node.constructor?.name || - 'Unknown' - - const apiNode = node.constructor?.nodeData?.api_node ?? false - const badges = node.badges - - return { - id: String(node.id), - title: typeof node.title === 'string' ? node.title : '', - type: nodeType, - mode: node.mode || 0, - selected: node.selected || false, - executing: false, // Will be updated separately based on execution state - subgraphId, - apiNode, - badges, - hasErrors: !!node.has_errors, - widgets: safeWidgets, - inputs: node.inputs ? [...node.inputs] : undefined, - outputs: node.outputs ? [...node.outputs] : undefined, - flags: node.flags ? { ...node.flags } : undefined, - color: node.color || undefined, - bgcolor: node.bgcolor || undefined + if (slotInfo) widget.slotMetadata = slotInfo } } @@ -250,189 +358,6 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager { return nodeRefs.get(id) } - /** - * Validates that a value is a valid WidgetValue type - */ - const validateWidgetValue = (value: unknown): WidgetValue => { - if (value === null || value === undefined || value === void 0) { - return undefined - } - if ( - typeof value === 'string' || - typeof value === 'number' || - typeof value === 'boolean' - ) { - return value - } - if (typeof value === 'object') { - // Check if it's a File array - if ( - Array.isArray(value) && - value.length > 0 && - value.every((item): item is File => item instanceof File) - ) { - return value - } - // Otherwise it's a generic object - return value - } - // If none of the above, return undefined - console.warn(`Invalid widget value type: ${typeof value}`, value) - return undefined - } - - /** - * Updates Vue state when widget values change - */ - const updateVueWidgetState = ( - nodeId: string, - widgetName: string, - value: unknown - ): void => { - try { - const currentData = vueNodeData.get(nodeId) - if (!currentData?.widgets) return - - const updatedWidgets = currentData.widgets.map((w) => - w.name === widgetName ? { ...w, value: validateWidgetValue(value) } : w - ) - // Create a completely new object to ensure Vue reactivity triggers - const updatedData = { - ...currentData, - widgets: updatedWidgets - } - - vueNodeData.set(nodeId, updatedData) - } catch (error) { - // Ignore widget update errors to prevent cascade failures - } - } - - /** - * Updates Vue state when widget options change (e.g., hidden, disabled) - */ - const updateVueWidgetOptions = ( - nodeId: string, - widgetName: string, - options: Record - ): void => { - try { - const currentData = vueNodeData.get(nodeId) - if (!currentData?.widgets) return - - const updatedWidgets = currentData.widgets.map((w) => - w.name === widgetName - ? { ...w, options: { ...w.options, ...options } } - : w - ) - // Create a completely new object to ensure Vue reactivity triggers - const updatedData = { - ...currentData, - widgets: updatedWidgets - } - - vueNodeData.set(nodeId, updatedData) - } catch (error) { - console.error('Error updating widget options:', error) - } - } - - /** - * Refreshes Vue widget state from LiteGraph node widgets. - * Use this after directly modifying node.widgets to sync Vue state. - */ - const refreshVueWidgets = (nodeId: string): void => { - try { - const node = nodeRefs.get(nodeId) - const currentData = vueNodeData.get(nodeId) - if (!node || !currentData) return - - // Re-extract widgets from node - const slotMetadata = new Map() - node.inputs?.forEach((input, index) => { - if (!input?.widget?.name) return - slotMetadata.set(input.widget.name, { - index, - linked: input.link != null - }) - }) - - const freshWidgets = - node.widgets?.map(safeWidgetMapper(node, slotMetadata)) ?? [] - - vueNodeData.set(nodeId, { - ...currentData, - widgets: freshWidgets - }) - } catch (error) { - console.error('Error refreshing Vue widgets:', error) - } - } - - /** - * Creates a wrapped callback for a widget that maintains LiteGraph/Vue sync - */ - const createWrappedWidgetCallback = ( - widget: { value?: unknown; name: string }, // LiteGraph widget with minimal typing - originalCallback: ((value: unknown) => void) | undefined, - nodeId: string - ) => { - let updateInProgress = false - - return (value: unknown) => { - if (updateInProgress) return - updateInProgress = true - - try { - // 1. Update the widget value in LiteGraph (critical for LiteGraph state) - // Validate that the value is of an acceptable type - if ( - value !== null && - value !== undefined && - typeof value !== 'string' && - typeof value !== 'number' && - typeof value !== 'boolean' && - typeof value !== 'object' - ) { - console.warn(`Invalid widget value type: ${typeof value}`) - updateInProgress = false - return - } - - // Always update widget.value to ensure sync - widget.value = value - - // 2. Call the original callback if it exists - if (originalCallback) { - originalCallback.call(widget, value) - } - - // 3. Update Vue state to maintain synchronization - updateVueWidgetState(nodeId, widget.name, value) - } finally { - updateInProgress = false - } - } - } - - /** - * Sets up widget callbacks for a node - */ - const setupNodeWidgetCallbacks = (node: LGraphNode) => { - if (!node.widgets) return - - const nodeId = String(node.id) - - node.widgets.forEach((widget) => { - const originalCallback = widget.callback - widget.callback = createWrappedWidgetCallback( - widget, - originalCallback, - nodeId - ) - }) - } - const syncWithGraph = () => { if (!graph?._nodes) return @@ -453,9 +378,6 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager { // Store non-reactive reference nodeRefs.set(id, node) - // Set up widget callbacks BEFORE extracting data (critical order) - setupNodeWidgetCallbacks(node) - // Extract and store safe data for Vue vueNodeData.set(id, extractVueNodeData(node)) }) @@ -474,13 +396,13 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager { // Store non-reactive reference to original node nodeRefs.set(id, node) - // Set up widget callbacks BEFORE extracting data (critical order) - setupNodeWidgetCallbacks(node) - // Extract initial data for Vue (may be incomplete during graph configure) vueNodeData.set(id, extractVueNodeData(node)) const initializeVueNodeLayout = () => { + // Check if the node was removed mid-sequence + if (!nodeRefs.has(id)) return + // Extract actual positions after configure() has potentially updated them const nodePosition = { x: node.pos[0], y: node.pos[1] } const nodeSize = { width: node.size[0], height: node.size[1] } @@ -510,7 +432,7 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager { } else { // Not during workflow loading - initialize layout immediately // This handles individual node additions during normal operation - initializeVueNodeLayout() + requestAnimationFrame(initializeVueNodeLayout) } // Call original callback if provided @@ -639,6 +561,22 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager { ? propertyEvent.newValue : undefined }) + break + case 'shape': + vueNodeData.set(nodeId, { + ...currentData, + shape: + typeof propertyEvent.newValue === 'number' + ? propertyEvent.newValue + : undefined + }) + break + case 'showAdvanced': + vueNodeData.set(nodeId, { + ...currentData, + showAdvanced: Boolean(propertyEvent.newValue) + }) + break } } }, @@ -695,8 +633,6 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager { return { vueNodeData, getNode, - updateVueWidgetOptions, - refreshVueWidgets, cleanup } } diff --git a/src/composables/graph/useImageMenuOptions.ts b/src/composables/graph/useImageMenuOptions.ts index 3956edefa..0eddf3d00 100644 --- a/src/composables/graph/useImageMenuOptions.ts +++ b/src/composables/graph/useImageMenuOptions.ts @@ -1,6 +1,7 @@ import { useI18n } from 'vue-i18n' import { downloadFile } from '@/base/common/downloadUtil' +import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' import { useCommandStore } from '@/stores/commandStore' import type { MenuOption } from './useMoreOptionsMenu' @@ -16,7 +17,7 @@ export function useImageMenuOptions() { void commandStore.execute('Comfy.MaskEditor.OpenMaskEditor') } - const openImage = (node: any) => { + const openImage = (node: LGraphNode) => { if (!node?.imgs?.length) return const img = node.imgs[node.imageIndex ?? 0] if (!img) return @@ -25,7 +26,7 @@ export function useImageMenuOptions() { window.open(url.toString(), '_blank') } - const copyImage = async (node: any) => { + const copyImage = async (node: LGraphNode) => { if (!node?.imgs?.length) return const img = node.imgs[node.imageIndex ?? 0] if (!img) return @@ -62,7 +63,7 @@ export function useImageMenuOptions() { } } - const saveImage = (node: any) => { + const saveImage = (node: LGraphNode) => { if (!node?.imgs?.length) return const img = node.imgs[node.imageIndex ?? 0] if (!img) return @@ -76,7 +77,7 @@ export function useImageMenuOptions() { } } - const getImageMenuOptions = (node: any): MenuOption[] => { + const getImageMenuOptions = (node: LGraphNode): MenuOption[] => { if (!node?.imgs?.length) return [] return [ diff --git a/src/composables/graph/useMoreOptionsMenu.ts b/src/composables/graph/useMoreOptionsMenu.ts index 45ea6eb1f..96c677cd5 100644 --- a/src/composables/graph/useMoreOptionsMenu.ts +++ b/src/composables/graph/useMoreOptionsMenu.ts @@ -2,8 +2,13 @@ import { computed, ref } from 'vue' import type { Ref } from 'vue' import type { LGraphGroup } from '@/lib/litegraph/src/litegraph' +import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' import { isLGraphGroup } from '@/utils/litegraphUtil' +import { + buildStructuredMenu, + convertContextMenuToOptions +} from './contextMenuConverter' import { useGroupMenuOptions } from './useGroupMenuOptions' import { useImageMenuOptions } from './useImageMenuOptions' import { useNodeMenuOptions } from './useNodeMenuOptions' @@ -15,10 +20,13 @@ export interface MenuOption { icon?: string shortcut?: string hasSubmenu?: boolean - type?: 'divider' + type?: 'divider' | 'category' action?: () => void submenu?: SubMenuOption[] badge?: BadgeVariant + disabled?: boolean + source?: 'litegraph' | 'vue' + isColorPicker?: boolean } export interface SubMenuOption { @@ -26,6 +34,7 @@ export interface SubMenuOption { icon?: string action: () => void color?: string + disabled?: boolean } export enum BadgeVariant { @@ -39,15 +48,21 @@ let nodeOptionsInstance: null | NodeOptionsInstance = null /** * Toggle the node options popover * @param event - The trigger event - * @param element - The target element (button) that triggered the popover */ -export function toggleNodeOptions( - event: Event, - element: HTMLElement, - clickedFromToolbox: boolean = false -) { +export function toggleNodeOptions(event: Event) { if (nodeOptionsInstance?.toggle) { - nodeOptionsInstance.toggle(event, element, clickedFromToolbox) + nodeOptionsInstance.toggle(event) + } +} + +/** + * Show the node options popover (always shows, doesn't toggle) + * Use this for contextmenu events where we always want to show at the new position + * @param event - The trigger event (must be MouseEvent for position) + */ +export function showNodeOptions(event: MouseEvent) { + if (nodeOptionsInstance?.show) { + nodeOptionsInstance.show(event) } } @@ -55,11 +70,8 @@ export function toggleNodeOptions( * Hide the node options popover */ interface NodeOptionsInstance { - toggle: ( - event: Event, - element: HTMLElement, - clickedFromToolbox: boolean - ) => void + toggle: (event: Event) => void + show: (event: MouseEvent) => void hide: () => void isOpen: Ref } @@ -74,6 +86,19 @@ export function registerNodeOptionsInstance( nodeOptionsInstance = instance } +/** + * Mark menu options as coming from Vue hardcoded menu + */ +function markAsVueOptions(options: MenuOption[]): MenuOption[] { + return options.map((opt) => { + // Don't mark dividers or category labels + if (opt.type === 'divider' || opt.type === 'category') { + return opt + } + return { ...opt, source: 'vue' } + }) +} + /** * Composable for managing the More Options menu configuration * Refactored to use smaller, focused composables for better maintainability @@ -91,10 +116,11 @@ export function useMoreOptionsMenu() { computeSelectionFlags } = useSelectionState() + const canvasStore = useCanvasStore() + const { getImageMenuOptions } = useImageMenuOptions() const { getNodeInfoOption, - getAdjustSizeOption, getNodeVisualOptions, getPinOption, getBypassOption, @@ -102,16 +128,13 @@ export function useMoreOptionsMenu() { } = useNodeMenuOptions() const { getFitGroupToNodesOption, - getGroupShapeOptions, getGroupColorOptions, getGroupModeOptions } = useGroupMenuOptions() const { getBasicSelectionOptions, getSubgraphOptions, - getMultipleNodesOptions, - getDeleteOption, - getAlignmentOptions + getMultipleNodesOptions } = useSelectionMenuOptions() const hasSubgraphs = hasSubgraphsComputed @@ -138,80 +161,107 @@ export function useMoreOptionsMenu() { ? selectedGroups[0] : null const hasSubgraphsSelected = hasSubgraphs.value + + // For single node selection, also get LiteGraph menu items to merge + const litegraphOptions: MenuOption[] = [] + if ( + selectedNodes.value.length === 1 && + !groupContext && + canvasStore.canvas + ) { + try { + const node = selectedNodes.value[0] + const rawItems = canvasStore.canvas.getNodeMenuOptions(node) + // Don't apply structuring yet - we'll do it after merging with Vue options + litegraphOptions.push( + ...convertContextMenuToOptions(rawItems, node, false) + ) + } catch (error) { + console.error('Error getting LiteGraph menu items:', error) + } + } + const options: MenuOption[] = [] // Section 1: Basic selection operations (Rename, Copy, Duplicate) - options.push(...getBasicSelectionOptions()) + const basicOps = getBasicSelectionOptions() + options.push(...basicOps) options.push({ type: 'divider' }) - // Section 2: Node Info & Size Adjustment - if (nodeDef.value) { - options.push(getNodeInfoOption(showNodeHelp)) + // Section 2: Node actions (Run Branch, Pin, Bypass, Mute) + if (hasOutputNodesSelected.value) { + const runBranch = getRunBranchOption() + options.push(runBranch) + } + if (!groupContext) { + const pin = getPinOption(states, bump) + const bypass = getBypassOption(states, bump) + options.push(pin) + options.push(bypass) } - if (groupContext) { - options.push(getFitGroupToNodesOption(groupContext)) - } else { - options.push(getAdjustSizeOption()) + const groupModes = getGroupModeOptions(groupContext, bump) + options.push(...groupModes) } + options.push({ type: 'divider' }) - // Section 3: Collapse/Shape/Color - if (groupContext) { - // Group context: Shape, Color, Divider - options.push(getGroupShapeOptions(groupContext, bump)) - options.push(getGroupColorOptions(groupContext, bump)) - options.push({ type: 'divider' }) - } else { - // Node context: Expand/Minimize, Shape, Color, Divider - options.push(...getNodeVisualOptions(states, bump)) - options.push({ type: 'divider' }) - } - - // Section 4: Image operations (if image node) - if (hasImageNode.value && selectedNodes.value.length > 0) { - options.push(...getImageMenuOptions(selectedNodes.value[0])) - } - - // Section 5: Subgraph operations - options.push(...getSubgraphOptions(hasSubgraphsSelected)) - - // Section 6: Multiple nodes operations + // Section 3: Structure operations (Convert to Subgraph, Frame selection, Minimize Node) + options.push( + ...getSubgraphOptions({ + hasSubgraphs: hasSubgraphsSelected, + hasMultipleSelection: hasMultipleNodes.value + }) + ) if (hasMultipleNodes.value) { options.push(...getMultipleNodesOptions()) } - - // Section 7: Divider - options.push({ type: 'divider' }) - - // Section 8: Pin/Unpin (non-group only) - if (!groupContext) { - options.push(getPinOption(states, bump)) - } - - // Section 9: Alignment (if multiple nodes) - if (hasMultipleNodes.value) { - options.push(...getAlignmentOptions()) - } - - // Section 10: Mode operations if (groupContext) { - // Group mode operations - options.push(...getGroupModeOptions(groupContext, bump)) + options.push(getFitGroupToNodesOption(groupContext)) } else { - // Bypass option for nodes - options.push(getBypassOption(states, bump)) + // Node context: Expand/Minimize + const visualOptions = getNodeVisualOptions(states, bump) + if (visualOptions.length > 0) { + options.push(visualOptions[0]) // Expand/Minimize (index 0) + } } - - // Section 11: Run Branch (if output nodes) - if (hasOutputNodesSelected.value) { - options.push(getRunBranchOption()) - } - - // Section 12: Final divider and Delete options.push({ type: 'divider' }) - options.push(getDeleteOption()) - return options + // Section 4: Node properties (Node Info, Shape, Color) + if (nodeDef.value) { + options.push(getNodeInfoOption(showNodeHelp)) + } + if (groupContext) { + options.push(getGroupColorOptions(groupContext, bump)) + } else { + // Add shape and color options + const visualOptions = getNodeVisualOptions(states, bump) + if (visualOptions.length > 1) { + options.push(visualOptions[1]) // Shape (index 1) + } + if (visualOptions.length > 2) { + options.push(visualOptions[2]) // Color (index 2) + } + } + options.push({ type: 'divider' }) + + // Section 5: Image operations (if image node) + if (hasImageNode.value && selectedNodes.value.length > 0) { + options.push(...getImageMenuOptions(selectedNodes.value[0])) + options.push({ type: 'divider' }) + } + // Section 6 & 7: Extensions and Delete are handled by buildStructuredMenu + + // Mark all Vue options with source + const markedVueOptions = markAsVueOptions(options) + + if (litegraphOptions.length > 0) { + // Merge: LiteGraph options first, then Vue options (Vue will win in dedup) + const merged = [...litegraphOptions, ...markedVueOptions] + return buildStructuredMenu(merged) + } + // For other cases, structure the Vue options + const result = buildStructuredMenu(markedVueOptions) + return result }) // Computed property to get only menu items with submenus diff --git a/src/composables/graph/useNodeMenuOptions.ts b/src/composables/graph/useNodeMenuOptions.ts index c1d291a4d..7e8cdfcf4 100644 --- a/src/composables/graph/useNodeMenuOptions.ts +++ b/src/composables/graph/useNodeMenuOptions.ts @@ -73,6 +73,7 @@ export function useNodeMenuOptions() { icon: 'icon-[lucide--palette]', hasSubmenu: true, submenu: colorSubmenu.value, + isColorPicker: true, action: () => {} } ] @@ -96,7 +97,7 @@ export function useNodeMenuOptions() { label: states.bypassed ? t('contextMenu.Remove Bypass') : t('contextMenu.Bypass'), - icon: states.bypassed ? 'icon-[lucide--zap-off]' : 'icon-[lucide--ban]', + icon: 'icon-[lucide--redo-dot]', shortcut: 'Ctrl+B', action: () => { toggleNodeBypass() diff --git a/src/composables/graph/useSelectionMenuOptions.test.ts b/src/composables/graph/useSelectionMenuOptions.test.ts new file mode 100644 index 000000000..8600e85ee --- /dev/null +++ b/src/composables/graph/useSelectionMenuOptions.test.ts @@ -0,0 +1,106 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { useSelectionMenuOptions } from '@/composables/graph/useSelectionMenuOptions' + +const subgraphMocks = vi.hoisted(() => ({ + convertToSubgraph: vi.fn(), + unpackSubgraph: vi.fn(), + addSubgraphToLibrary: vi.fn(), + createI18nMock: vi.fn(() => ({ + global: { + t: vi.fn(), + te: vi.fn(), + d: vi.fn() + } + })) +})) + +vi.mock('vue-i18n', () => ({ + useI18n: () => ({ + t: (key: string) => key + }), + createI18n: subgraphMocks.createI18nMock +})) + +vi.mock('@/composables/graph/useSelectionOperations', () => ({ + useSelectionOperations: () => ({ + copySelection: vi.fn(), + duplicateSelection: vi.fn(), + deleteSelection: vi.fn(), + renameSelection: vi.fn() + }) +})) + +vi.mock('@/composables/graph/useNodeArrangement', () => ({ + useNodeArrangement: () => ({ + alignOptions: [{ localizedName: 'align-left', icon: 'align-left' }], + distributeOptions: [{ localizedName: 'distribute', icon: 'distribute' }], + applyAlign: vi.fn(), + applyDistribute: vi.fn() + }) +})) + +vi.mock('@/composables/graph/useSubgraphOperations', () => ({ + useSubgraphOperations: () => ({ + convertToSubgraph: subgraphMocks.convertToSubgraph, + unpackSubgraph: subgraphMocks.unpackSubgraph, + addSubgraphToLibrary: subgraphMocks.addSubgraphToLibrary + }) +})) + +vi.mock('@/composables/graph/useFrameNodes', () => ({ + useFrameNodes: () => ({ + frameNodes: vi.fn() + }) +})) + +describe('useSelectionMenuOptions - subgraph options', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('returns only convert option when no subgraphs are selected', () => { + const { getSubgraphOptions } = useSelectionMenuOptions() + const options = getSubgraphOptions({ + hasSubgraphs: false, + hasMultipleSelection: true + }) + + expect(options).toHaveLength(1) + expect(options[0]?.label).toBe('contextMenu.Convert to Subgraph') + expect(options[0]?.action).toBe(subgraphMocks.convertToSubgraph) + }) + + it('includes convert, add to library, and unpack when subgraphs are selected', () => { + const { getSubgraphOptions } = useSelectionMenuOptions() + const options = getSubgraphOptions({ + hasSubgraphs: true, + hasMultipleSelection: true + }) + const labels = options.map((option) => option.label) + + expect(labels).toContain('contextMenu.Convert to Subgraph') + expect(labels).toContain('contextMenu.Add Subgraph to Library') + expect(labels).toContain('contextMenu.Unpack Subgraph') + + const convertOption = options.find( + (option) => option.label === 'contextMenu.Convert to Subgraph' + ) + expect(convertOption?.action).toBe(subgraphMocks.convertToSubgraph) + }) + + it('hides convert option when only a single subgraph is selected', () => { + const { getSubgraphOptions } = useSelectionMenuOptions() + const options = getSubgraphOptions({ + hasSubgraphs: true, + hasMultipleSelection: false + }) + + const labels = options.map((option) => option.label) + expect(labels).not.toContain('contextMenu.Convert to Subgraph') + expect(labels).toEqual([ + 'contextMenu.Add Subgraph to Library', + 'contextMenu.Unpack Subgraph' + ]) + }) +}) diff --git a/src/composables/graph/useSelectionMenuOptions.ts b/src/composables/graph/useSelectionMenuOptions.ts index 55e4ec109..e896261a5 100644 --- a/src/composables/graph/useSelectionMenuOptions.ts +++ b/src/composables/graph/useSelectionMenuOptions.ts @@ -63,9 +63,29 @@ export function useSelectionMenuOptions() { } ] - const getSubgraphOptions = (hasSubgraphs: boolean): MenuOption[] => { + const getSubgraphOptions = ({ + hasSubgraphs, + hasMultipleSelection + }: { + hasSubgraphs: boolean + hasMultipleSelection: boolean + }): MenuOption[] => { + const convertOption: MenuOption = { + label: t('contextMenu.Convert to Subgraph'), + icon: 'icon-[lucide--shrink]', + action: convertToSubgraph, + badge: BadgeVariant.NEW + } + + const options: MenuOption[] = [] + const showConvertOption = !hasSubgraphs || hasMultipleSelection + + if (showConvertOption) { + options.push(convertOption) + } + if (hasSubgraphs) { - return [ + options.push( { label: t('contextMenu.Add Subgraph to Library'), icon: 'icon-[lucide--folder-plus]', @@ -76,17 +96,10 @@ export function useSelectionMenuOptions() { icon: 'icon-[lucide--expand]', action: unpackSubgraph } - ] - } else { - return [ - { - label: t('contextMenu.Convert to Subgraph'), - icon: 'icon-[lucide--shrink]', - action: convertToSubgraph, - badge: BadgeVariant.NEW - } - ] + ) } + + return options } const getMultipleNodesOptions = (): MenuOption[] => { diff --git a/src/composables/graph/useSelectionState.test.ts b/src/composables/graph/useSelectionState.test.ts new file mode 100644 index 000000000..cd4af9422 --- /dev/null +++ b/src/composables/graph/useSelectionState.test.ts @@ -0,0 +1,171 @@ +import { createTestingPinia } from '@pinia/testing' +import { setActivePinia } from 'pinia' +import { beforeEach, describe, expect, test, vi } from 'vitest' + +import { useSelectionState } from '@/composables/graph/useSelectionState' +import { useNodeLibrarySidebarTab } from '@/composables/sidebarTabs/useNodeLibrarySidebarTab' +import { LGraphEventMode } from '@/lib/litegraph/src/litegraph' +import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' +import { isImageNode, isLGraphNode } from '@/utils/litegraphUtil' +import { filterOutputNodes } from '@/utils/nodeFilterUtil' +import { + createMockLGraphNode, + createMockPositionable +} from '@/utils/__tests__/litegraphTestUtils' + +// Mock composables +vi.mock('@/composables/sidebarTabs/useNodeLibrarySidebarTab', () => ({ + useNodeLibrarySidebarTab: vi.fn() +})) + +vi.mock('@/utils/litegraphUtil', () => ({ + isLGraphNode: vi.fn(), + isImageNode: vi.fn() +})) + +vi.mock('@/utils/nodeFilterUtil', () => ({ + filterOutputNodes: vi.fn() +})) + +// Mock comment/connection objects with additional properties +const mockComment = { + ...createMockPositionable({ id: 999 }), + type: 'comment', + isNode: false +} +const mockConnection = { + ...createMockPositionable({ id: 1000 }), + type: 'connection', + isNode: false +} + +describe('useSelectionState', () => { + beforeEach(() => { + vi.clearAllMocks() + + // Create testing Pinia instance + setActivePinia( + createTestingPinia({ + createSpy: vi.fn + }) + ) + + // Setup mock composables + vi.mocked(useNodeLibrarySidebarTab).mockReturnValue({ + id: 'node-library-tab', + title: 'Node Library', + type: 'custom', + render: () => null + } as ReturnType) + + // Setup mock utility functions + vi.mocked(isLGraphNode).mockImplementation((item: unknown) => { + const typedItem = item as { isNode?: boolean } + return typedItem?.isNode !== false + }) + vi.mocked(isImageNode).mockImplementation((node: unknown) => { + const typedNode = node as { type?: string } + return typedNode?.type === 'ImageNode' + }) + vi.mocked(filterOutputNodes).mockImplementation((nodes) => + nodes.filter((n) => n.type === 'OutputNode') + ) + }) + + describe('Selection Detection', () => { + test('should return false when nothing selected', () => { + const { hasAnySelection } = useSelectionState() + expect(hasAnySelection.value).toBe(false) + }) + + test('should return true when items selected', () => { + const canvasStore = useCanvasStore() + const node1 = createMockLGraphNode({ id: 1 }) + const node2 = createMockLGraphNode({ id: 2 }) + canvasStore.$state.selectedItems = [node1, node2] + + const { hasAnySelection } = useSelectionState() + expect(hasAnySelection.value).toBe(true) + }) + }) + + describe('Node Type Filtering', () => { + test('should pick only LGraphNodes from mixed selections', () => { + const canvasStore = useCanvasStore() + const graphNode = createMockLGraphNode({ id: 3 }) + canvasStore.$state.selectedItems = [ + graphNode, + mockComment, + mockConnection + ] + + const { selectedNodes } = useSelectionState() + expect(selectedNodes.value).toHaveLength(1) + expect(selectedNodes.value[0]).toEqual(graphNode) + }) + }) + + describe('Node State Computation', () => { + test('should detect bypassed nodes', () => { + const canvasStore = useCanvasStore() + const bypassedNode = createMockLGraphNode({ + id: 4, + mode: LGraphEventMode.BYPASS + }) + canvasStore.$state.selectedItems = [bypassedNode] + + const { selectedNodes } = useSelectionState() + const isBypassed = selectedNodes.value.some( + (n) => n.mode === LGraphEventMode.BYPASS + ) + expect(isBypassed).toBe(true) + }) + + test('should detect pinned/collapsed states', () => { + const canvasStore = useCanvasStore() + const pinnedNode = createMockLGraphNode({ id: 5, pinned: true }) + const collapsedNode = createMockLGraphNode({ + id: 6, + flags: { collapsed: true } + }) + canvasStore.$state.selectedItems = [pinnedNode, collapsedNode] + + const { selectedNodes } = useSelectionState() + const isPinned = selectedNodes.value.some((n) => n.pinned === true) + const isCollapsed = selectedNodes.value.some( + (n) => n.flags?.collapsed === true + ) + const isBypassed = selectedNodes.value.some( + (n) => n.mode === LGraphEventMode.BYPASS + ) + expect(isPinned).toBe(true) + expect(isCollapsed).toBe(true) + expect(isBypassed).toBe(false) + }) + + test('should provide non-reactive state computation', () => { + const canvasStore = useCanvasStore() + const node = createMockLGraphNode({ id: 7, pinned: true }) + canvasStore.$state.selectedItems = [node] + + const { selectedNodes } = useSelectionState() + const isPinned = selectedNodes.value.some((n) => n.pinned === true) + const isCollapsed = selectedNodes.value.some( + (n) => n.flags?.collapsed === true + ) + const isBypassed = selectedNodes.value.some( + (n) => n.mode === LGraphEventMode.BYPASS + ) + + expect(isPinned).toBe(true) + expect(isCollapsed).toBe(false) + expect(isBypassed).toBe(false) + + // Test with empty selection using new composable instance + canvasStore.$state.selectedItems = [] + const { selectedNodes: newSelectedNodes } = useSelectionState() + const newIsPinned = newSelectedNodes.value.some((n) => n.pinned === true) + expect(newIsPinned).toBe(false) + }) + }) +}) diff --git a/src/composables/graph/useSelectionState.ts b/src/composables/graph/useSelectionState.ts index 23268ba2a..5848eafb4 100644 --- a/src/composables/graph/useSelectionState.ts +++ b/src/composables/graph/useSelectionState.ts @@ -105,12 +105,11 @@ export function useSelectionState() { const isSidebarActive = sidebarTabStore.activeSidebarTabId === nodeLibraryTabId - const currentHelpNode: any = nodeHelpStore.currentHelpNode + const currentHelpNode = nodeHelpStore.currentHelpNode const isSameNodeHelpOpen = isSidebarActive && nodeHelpStore.isHelpOpen && - currentHelpNode && - currentHelpNode.nodePath === def.nodePath + currentHelpNode?.nodePath === def.nodePath if (isSameNodeHelpOpen) { nodeHelpStore.closeHelp() diff --git a/src/composables/graph/useSubgraphOperations.ts b/src/composables/graph/useSubgraphOperations.ts index b42cc2ec4..45a665d4b 100644 --- a/src/composables/graph/useSubgraphOperations.ts +++ b/src/composables/graph/useSubgraphOperations.ts @@ -37,6 +37,21 @@ export function useSubgraphOperations() { workflowStore.activeWorkflow?.changeTracker?.checkState() } + const doUnpack = ( + subgraphNodes: SubgraphNode[], + skipMissingNodes: boolean + ) => { + const canvas = canvasStore.getCanvas() + const graph = canvas.subgraph ?? canvas.graph + if (!graph) return + + for (const subgraphNode of subgraphNodes) { + nodeOutputStore.revokeSubgraphPreviews(subgraphNode) + graph.unpackSubgraph(subgraphNode, { skipMissingNodes }) + } + workflowStore.activeWorkflow?.changeTracker?.checkState() + } + const unpackSubgraph = () => { const canvas = canvasStore.getCanvas() const graph = canvas.subgraph ?? canvas.graph @@ -53,17 +68,7 @@ export function useSubgraphOperations() { if (subgraphNodes.length === 0) { return } - - subgraphNodes.forEach((subgraphNode) => { - // Revoke any image previews for the subgraph - nodeOutputStore.revokeSubgraphPreviews(subgraphNode) - - // Unpack the subgraph - graph.unpackSubgraph(subgraphNode) - }) - - // Trigger change tracking - workflowStore.activeWorkflow?.changeTracker?.checkState() + doUnpack(subgraphNodes, true) } const addSubgraphToLibrary = async () => { diff --git a/src/composables/graph/useSubmenuPositioning.ts b/src/composables/graph/useSubmenuPositioning.ts deleted file mode 100644 index 2dda2bd1c..000000000 --- a/src/composables/graph/useSubmenuPositioning.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { nextTick } from 'vue' - -import type { MenuOption } from './useMoreOptionsMenu' - -/** - * Composable for handling submenu positioning logic - */ -export function useSubmenuPositioning() { - /** - * Toggle submenu visibility with proper positioning - * @param option - Menu option with submenu - * @param event - Click event - * @param submenu - PrimeVue Popover reference - * @param currentSubmenu - Currently open submenu name - * @param menuOptionsWithSubmenu - All menu options with submenus - * @param submenuRefs - References to all submenu popovers - */ - const toggleSubmenu = async ( - option: MenuOption, - event: Event, - submenu: any, // Component instance with show/hide methods - currentSubmenu: { value: string | null }, - menuOptionsWithSubmenu: MenuOption[], - submenuRefs: Record // Component instances - ): Promise => { - if (!option.label || !option.hasSubmenu) return - - // Check if this submenu is currently open - const isCurrentlyOpen = currentSubmenu.value === option.label - - // Hide all submenus first - menuOptionsWithSubmenu.forEach((opt) => { - const sm = submenuRefs[`submenu-${opt.label}`] - if (sm) { - sm.hide() - } - }) - currentSubmenu.value = null - - // If it wasn't open before, show it now - if (!isCurrentlyOpen) { - currentSubmenu.value = option.label - await nextTick() - - const menuItem = event.currentTarget as HTMLElement - const menuItemRect = menuItem.getBoundingClientRect() - - // Find the parent popover content element that contains this menu item - const mainPopoverContent = menuItem.closest( - '[data-pc-section="content"]' - ) as HTMLElement - - if (mainPopoverContent) { - const mainPopoverRect = mainPopoverContent.getBoundingClientRect() - - // Create a temporary positioned element as the target - const tempTarget = createPositionedTarget( - mainPopoverRect.right + 8, - menuItemRect.top, - `submenu-target-${option.label}` - ) - - // Create event using the temp target - const tempEvent = createMouseEvent( - mainPopoverRect.right + 8, - menuItemRect.top - ) - - // Show submenu relative to temp target - submenu.show(tempEvent, tempTarget) - - // Clean up temp target after a delay - cleanupTempTarget(tempTarget, 100) - } else { - // Fallback: position to the right of the menu item - const tempTarget = createPositionedTarget( - menuItemRect.right + 8, - menuItemRect.top, - `submenu-fallback-target-${option.label}` - ) - - // Create event using the temp target - const tempEvent = createMouseEvent( - menuItemRect.right + 8, - menuItemRect.top - ) - - // Show submenu relative to temp target - submenu.show(tempEvent, tempTarget) - - // Clean up temp target after a delay - cleanupTempTarget(tempTarget, 100) - } - } - } - - /** - * Create a temporary positioned DOM element for submenu targeting - */ - const createPositionedTarget = ( - left: number, - top: number, - id: string - ): HTMLElement => { - const tempTarget = document.createElement('div') - tempTarget.style.position = 'absolute' - tempTarget.style.left = `${left}px` - tempTarget.style.top = `${top}px` - tempTarget.style.width = '1px' - tempTarget.style.height = '1px' - tempTarget.style.pointerEvents = 'none' - tempTarget.style.visibility = 'hidden' - tempTarget.id = id - - document.body.appendChild(tempTarget) - return tempTarget - } - - /** - * Create a mouse event with specific coordinates - */ - const createMouseEvent = (clientX: number, clientY: number): MouseEvent => { - return new MouseEvent('click', { - bubbles: true, - cancelable: true, - clientX, - clientY - }) - } - - /** - * Clean up temporary target element after delay - */ - const cleanupTempTarget = (target: HTMLElement, delay: number): void => { - setTimeout(() => { - if (target.parentNode) { - target.parentNode.removeChild(target) - } - }, delay) - } - - /** - * Hide all submenus - */ - const hideAllSubmenus = ( - menuOptionsWithSubmenu: MenuOption[], - submenuRefs: Record, // Component instances - currentSubmenu: { value: string | null } - ): void => { - menuOptionsWithSubmenu.forEach((option) => { - const submenu = submenuRefs[`submenu-${option.label}`] - if (submenu) { - submenu.hide() - } - }) - currentSubmenu.value = null - } - - return { - toggleSubmenu, - hideAllSubmenus - } -} diff --git a/src/composables/graph/useViewportCulling.ts b/src/composables/graph/useViewportCulling.ts deleted file mode 100644 index 30cf82e56..000000000 --- a/src/composables/graph/useViewportCulling.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Vue Nodes Viewport Culling - * - * Principles: - * 1. Query DOM directly using data attributes (no cache to maintain) - * 2. Set display none on element to avoid cascade resolution overhead - * 3. Only run when transform changes (event driven) - */ -import { createSharedComposable, useThrottleFn } from '@vueuse/core' -import { computed } from 'vue' - -import { useVueNodeLifecycle } from '@/composables/graph/useVueNodeLifecycle' -import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' -import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' -import { app } from '@/scripts/app' - -type Bounds = [left: number, right: number, top: number, bottom: number] - -function getNodeBounds(node: LGraphNode): Bounds { - const [nodeLeft, nodeTop] = node.pos - const nodeRight = nodeLeft + node.size[0] - const nodeBottom = nodeTop + node.size[1] - return [nodeLeft, nodeRight, nodeTop, nodeBottom] -} - -function viewportEdges( - canvas: ReturnType['canvas'] -): Bounds | undefined { - if (!canvas) { - return - } - const ds = canvas.ds - const viewport_width = canvas.canvas.width - const viewport_height = canvas.canvas.height - const margin = 500 * ds.scale - - const [xOffset, yOffset] = ds.offset - - const leftEdge = -margin / ds.scale - xOffset - const rightEdge = (viewport_width + margin) / ds.scale - xOffset - const topEdge = -margin / ds.scale - yOffset - const bottomEdge = (viewport_height + margin) / ds.scale - yOffset - return [leftEdge, rightEdge, topEdge, bottomEdge] -} - -function boundsIntersect(boxA: Bounds, boxB: Bounds): boolean { - const [aLeft, aRight, aTop, aBottom] = boxA - const [bLeft, bRight, bTop, bBottom] = boxB - - const leftOf = aRight < bLeft - const rightOf = aLeft > bRight - const above = aBottom < bTop - const below = aTop > bBottom - return !(leftOf || rightOf || above || below) -} - -function useViewportCullingIndividual() { - const canvasStore = useCanvasStore() - const { nodeManager } = useVueNodeLifecycle() - - const viewport = computed(() => viewportEdges(canvasStore.canvas)) - - function inViewport(node: LGraphNode | undefined): boolean { - if (!viewport.value || !node) { - return true - } - const nodeBounds = getNodeBounds(node) - return boundsIntersect(nodeBounds, viewport.value) - } - - /** - * Update visibility of all nodes based on viewport - * Queries DOM directly - no cache maintenance needed - */ - function updateVisibility() { - if (!nodeManager.value || !app.canvas) return // load bearing app.canvas check for workflows being loaded. - - const nodeElements = document.querySelectorAll('[data-node-id]') - for (const element of nodeElements) { - const nodeId = element.getAttribute('data-node-id') - if (!nodeId) continue - - const node = nodeManager.value.getNode(nodeId) - if (!node) continue - - const displayValue = inViewport(node) ? '' : 'none' - if ( - element instanceof HTMLElement && - element.style.display !== displayValue - ) { - element.style.display = displayValue - } - } - } - - const handleTransformUpdate = useThrottleFn(() => updateVisibility, 100, true) - - return { handleTransformUpdate } -} - -export const useViewportCulling = createSharedComposable( - useViewportCullingIndividual -) diff --git a/src/composables/graph/useVueNodeLifecycle.ts b/src/composables/graph/useVueNodeLifecycle.ts index 263df3b79..648427a51 100644 --- a/src/composables/graph/useVueNodeLifecycle.ts +++ b/src/composables/graph/useVueNodeLifecycle.ts @@ -4,15 +4,14 @@ import { shallowRef, watch } from 'vue' import { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager' import type { GraphNodeManager } from '@/composables/graph/useGraphNodeManager' import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags' -import { useVueNodesMigrationDismissed } from '@/composables/useVueNodesMigrationDismissed' import type { LGraphNode } from '@/lib/litegraph/src/litegraph' import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations' import { layoutStore } from '@/renderer/core/layout/store/layoutStore' import { useLayoutSync } from '@/renderer/core/layout/sync/useLayoutSync' +import { removeNodeTitleHeight } from '@/renderer/core/layout/utils/nodeSizeUtil' import { ensureCorrectLayoutScale } from '@/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale' import { app as comfyApp } from '@/scripts/app' -import { useToastStore } from '@/platform/updates/common/toastStore' function useVueNodeLifecycleIndividual() { const canvasStore = useCanvasStore() @@ -21,10 +20,6 @@ function useVueNodeLifecycleIndividual() { const nodeManager = shallowRef(null) const { startSync } = useLayoutSync() - const isVueNodeToastDismissed = useVueNodesMigrationDismissed() - - let hasShownMigrationToast = false - const initializeNodeManager = () => { // Use canvas graph if available (handles subgraph contexts), fallback to app graph const activeGraph = comfyApp.canvas?.graph @@ -38,7 +33,10 @@ function useVueNodeLifecycleIndividual() { const nodes = activeGraph._nodes.map((node: LGraphNode) => ({ id: node.id.toString(), pos: [node.pos[0], node.pos[1]] as [number, number], - size: [node.size[0], node.size[1]] as [number, number] + size: [node.size[0], removeNodeTitleHeight(node.size[1])] as [ + number, + number + ] })) layoutStore.initializeFromLiteGraph(nodes) @@ -79,24 +77,12 @@ function useVueNodeLifecycleIndividual() { // Watch for Vue nodes enabled state changes watch( () => shouldRenderVueNodes.value && Boolean(comfyApp.canvas?.graph), - (enabled, wasEnabled) => { + (enabled) => { if (enabled) { initializeNodeManager() ensureCorrectLayoutScale( comfyApp.canvas?.graph?.extra.workflowRendererVersion ) - if ( - wasEnabled === false && - !isVueNodeToastDismissed.value && - !hasShownMigrationToast - ) { - hasShownMigrationToast = true - useToastStore().add({ - group: 'vue-nodes-migration', - severity: 'info', - life: 0 - }) - } } }, { immediate: true } diff --git a/src/composables/maskeditor/gpu/brushShaders.ts b/src/composables/maskeditor/gpu/brushShaders.ts index 659c9ddb4..087394076 100644 --- a/src/composables/maskeditor/gpu/brushShaders.ts +++ b/src/composables/maskeditor/gpu/brushShaders.ts @@ -1,4 +1,4 @@ -import tgpu from 'typegpu' +import { tgpu } from 'typegpu' import * as d from 'typegpu/data' import { BrushUniforms } from './gpuSchema' diff --git a/src/composables/maskeditor/useBrushDrawing.ts b/src/composables/maskeditor/useBrushDrawing.ts index 27b580ee3..f1ea0fb38 100644 --- a/src/composables/maskeditor/useBrushDrawing.ts +++ b/src/composables/maskeditor/useBrushDrawing.ts @@ -1,3 +1,4 @@ +/// import { ref, watch, nextTick, onUnmounted } from 'vue' import QuickLRU from '@alloc/quick-lru' import { debounce } from 'es-toolkit/compat' @@ -12,7 +13,7 @@ import type { Brush, Point } from '@/extensions/core/maskeditor/types' import { useMaskEditorStore } from '@/stores/maskEditorStore' import { useCoordinateTransform } from './useCoordinateTransform' import { resampleSegment } from './splineUtils' -import TGPU from 'typegpu' +import { tgpu } from 'typegpu' import { GPUBrushRenderer } from './gpu/GPUBrushRenderer' import { StrokeProcessor } from './StrokeProcessor' import { getEffectiveBrushSize, getEffectiveHardness } from './brushUtils' @@ -233,6 +234,128 @@ export function useBrushDrawing(initialSettings?: { } ) + const isRecreatingTextures = ref(false) + + watch( + () => store.gpuTexturesNeedRecreation, + async (needsRecreation) => { + if ( + !needsRecreation || + !device || + !store.maskCanvas || + isRecreatingTextures.value + ) + return + + isRecreatingTextures.value = true + + const width = store.gpuTextureWidth + const height = store.gpuTextureHeight + + try { + // Destroy old textures + if (maskTexture) { + maskTexture.destroy() + maskTexture = null + } + if (rgbTexture) { + rgbTexture.destroy() + rgbTexture = null + } + + // Create new textures with updated dimensions + maskTexture = device.createTexture({ + size: [width, height], + format: 'rgba8unorm', + usage: + GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.STORAGE_BINDING | + GPUTextureUsage.RENDER_ATTACHMENT | + GPUTextureUsage.COPY_DST | + GPUTextureUsage.COPY_SRC + }) + + rgbTexture = device.createTexture({ + size: [width, height], + format: 'rgba8unorm', + usage: + GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.STORAGE_BINDING | + GPUTextureUsage.RENDER_ATTACHMENT | + GPUTextureUsage.COPY_DST | + GPUTextureUsage.COPY_SRC + }) + + // Upload pending data if available + if (store.pendingGPUMaskData && store.pendingGPURgbData) { + device.queue.writeTexture( + { texture: maskTexture }, + store.pendingGPUMaskData, + { bytesPerRow: width * 4 }, + { width, height } + ) + + device.queue.writeTexture( + { texture: rgbTexture }, + store.pendingGPURgbData, + { bytesPerRow: width * 4 }, + { width, height } + ) + } else { + // Fallback: read from canvas + await updateGPUFromCanvas() + } + + // Update preview canvas if it exists + if (previewCanvas && renderer) { + previewCanvas.width = width + previewCanvas.height = height + } + + // Recreate readback buffers with new size + const bufferSize = width * height * 4 + if (currentBufferSize !== bufferSize) { + readbackStorageMask?.destroy() + readbackStorageRgb?.destroy() + readbackStagingMask?.destroy() + readbackStagingRgb?.destroy() + + readbackStorageMask = device.createBuffer({ + size: bufferSize, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC + }) + readbackStorageRgb = device.createBuffer({ + size: bufferSize, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC + }) + readbackStagingMask = device.createBuffer({ + size: bufferSize, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ + }) + readbackStagingRgb = device.createBuffer({ + size: bufferSize, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ + }) + + currentBufferSize = bufferSize + } + } catch (error) { + console.error( + '[useBrushDrawing] Failed to recreate GPU textures:', + error + ) + } finally { + // Clear the recreation flag and pending data + store.gpuTexturesNeedRecreation = false + store.gpuTextureWidth = 0 + store.gpuTextureHeight = 0 + store.pendingGPUMaskData = null + store.pendingGPURgbData = null + isRecreatingTextures.value = false + } + } + ) + // Cleanup GPU resources on unmount onUnmounted(() => { if (renderer) { @@ -276,13 +399,14 @@ export function useBrushDrawing(initialSettings?: { } try { - const root = await TGPU.init() + const root = await tgpu.init() store.tgpuRoot = root device = root.device console.warn('✅ TypeGPU initialized! Root:', root) console.warn('Device info:', root.device.limits) - } catch (error: any) { - console.warn('Failed to initialize TypeGPU:', error.message) + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + console.warn('Failed to initialize TypeGPU:', message) } } diff --git a/tests-ui/tests/composables/maskeditor/useCanvasHistory.test.ts b/src/composables/maskeditor/useCanvasHistory.test.ts similarity index 55% rename from tests-ui/tests/composables/maskeditor/useCanvasHistory.test.ts rename to src/composables/maskeditor/useCanvasHistory.test.ts index 0bff43669..2e96eda00 100644 --- a/tests-ui/tests/composables/maskeditor/useCanvasHistory.test.ts +++ b/src/composables/maskeditor/useCanvasHistory.test.ts @@ -2,23 +2,70 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { nextTick } from 'vue' import { useCanvasHistory } from '@/composables/maskeditor/useCanvasHistory' -let mockMaskCanvas: any -let mockRgbCanvas: any -let mockMaskCtx: any -let mockRgbCtx: any +// Define the store shape to avoid 'any' and cast to the expected type +interface MaskEditorStoreState { + maskCanvas: HTMLCanvasElement | null + rgbCanvas: HTMLCanvasElement | null + imgCanvas: HTMLCanvasElement | null + maskCtx: CanvasRenderingContext2D | null + rgbCtx: CanvasRenderingContext2D | null + imgCtx: CanvasRenderingContext2D | null +} -const mockStore = { - maskCanvas: null as any, - rgbCanvas: null as any, - maskCtx: null as any, - rgbCtx: null as any +// Use vi.hoisted to create isolated mock state container +const mockRefs = vi.hoisted(() => ({ + maskCanvas: null as HTMLCanvasElement | null, + rgbCanvas: null as HTMLCanvasElement | null, + imgCanvas: null as HTMLCanvasElement | null, + maskCtx: null as CanvasRenderingContext2D | null, + rgbCtx: null as CanvasRenderingContext2D | null, + imgCtx: null as CanvasRenderingContext2D | null +})) + +const mockStore: MaskEditorStoreState = { + get maskCanvas() { + return mockRefs.maskCanvas + }, + set maskCanvas(val) { + mockRefs.maskCanvas = val + }, + get rgbCanvas() { + return mockRefs.rgbCanvas + }, + set rgbCanvas(val) { + mockRefs.rgbCanvas = val + }, + get imgCanvas() { + return mockRefs.imgCanvas + }, + set imgCanvas(val) { + mockRefs.imgCanvas = val + }, + get maskCtx() { + return mockRefs.maskCtx + }, + set maskCtx(val) { + mockRefs.maskCtx = val + }, + get rgbCtx() { + return mockRefs.rgbCtx + }, + set rgbCtx(val) { + mockRefs.rgbCtx = val + }, + get imgCtx() { + return mockRefs.imgCtx + }, + set imgCtx(val) { + mockRefs.imgCtx = val + } } vi.mock('@/stores/maskEditorStore', () => ({ useMaskEditorStore: vi.fn(() => mockStore) })) -// Mock ImageBitmap for test environment +// Mock ImageBitmap using safe global augmentation pattern if (typeof globalThis.ImageBitmap === 'undefined') { globalThis.ImageBitmap = class ImageBitmap { width: number @@ -28,7 +75,7 @@ if (typeof globalThis.ImageBitmap === 'undefined') { this.height = height } close() {} - } as any + } as typeof ImageBitmap } describe('useCanvasHistory', () => { @@ -43,9 +90,8 @@ describe('useCanvasHistory', () => { return rafCallCount } ) - vi.spyOn(window, 'alert').mockImplementation(() => {}) - const createMockImageData = () => { + const createMockImageData = (): ImageData => { return { data: new Uint8ClampedArray(100 * 100 * 4), width: 100, @@ -53,34 +99,43 @@ describe('useCanvasHistory', () => { } as ImageData } - mockMaskCtx = { + // Mock contexts using explicit partial-cast pattern + mockRefs.maskCtx = { getImageData: vi.fn(() => createMockImageData()), putImageData: vi.fn(), clearRect: vi.fn(), drawImage: vi.fn() - } + } as Partial as CanvasRenderingContext2D - mockRgbCtx = { + mockRefs.rgbCtx = { getImageData: vi.fn(() => createMockImageData()), putImageData: vi.fn(), clearRect: vi.fn(), drawImage: vi.fn() - } + } as Partial as CanvasRenderingContext2D - mockMaskCanvas = { + mockRefs.imgCtx = { + getImageData: vi.fn(() => createMockImageData()), + putImageData: vi.fn(), + clearRect: vi.fn(), + drawImage: vi.fn() + } as Partial as CanvasRenderingContext2D + + // Mock canvases using explicit partial-cast pattern + mockRefs.maskCanvas = { width: 100, height: 100 - } + } as Partial as HTMLCanvasElement - mockRgbCanvas = { + mockRefs.rgbCanvas = { width: 100, height: 100 - } + } as Partial as HTMLCanvasElement - mockStore.maskCanvas = mockMaskCanvas - mockStore.rgbCanvas = mockRgbCanvas - mockStore.maskCtx = mockMaskCtx - mockStore.rgbCtx = mockRgbCtx + mockRefs.imgCanvas = { + width: 100, + height: 100 + } as Partial as HTMLCanvasElement }) describe('initialization', () => { @@ -96,8 +151,14 @@ describe('useCanvasHistory', () => { history.saveInitialState() - expect(mockMaskCtx.getImageData).toHaveBeenCalledWith(0, 0, 100, 100) - expect(mockRgbCtx.getImageData).toHaveBeenCalledWith(0, 0, 100, 100) + expect(mockRefs.maskCtx!.getImageData).toHaveBeenCalledWith( + 0, + 0, + 100, + 100 + ) + expect(mockRefs.rgbCtx!.getImageData).toHaveBeenCalledWith(0, 0, 100, 100) + expect(mockRefs.imgCtx!.getImageData).toHaveBeenCalledWith(0, 0, 100, 100) expect(history.canUndo.value).toBe(false) expect(history.canRedo.value).toBe(false) }) @@ -105,27 +166,48 @@ describe('useCanvasHistory', () => { it('should wait for canvas to be ready', () => { const rafSpy = vi.spyOn(window, 'requestAnimationFrame') - mockStore.maskCanvas = { ...mockMaskCanvas, width: 0, height: 0 } + mockRefs.maskCanvas = { + // oxlint-disable-next-line no-misused-spread + ...mockRefs.maskCanvas, + width: 0, + height: 0 + } as Partial as HTMLCanvasElement const history = useCanvasHistory() history.saveInitialState() expect(rafSpy).toHaveBeenCalled() - mockStore.maskCanvas = mockMaskCanvas + mockRefs.maskCanvas = { + width: 100, + height: 100 + } as Partial as HTMLCanvasElement }) it('should wait for context to be ready', () => { const rafSpy = vi.spyOn(window, 'requestAnimationFrame') - mockStore.maskCtx = null + mockRefs.maskCtx = null const history = useCanvasHistory() history.saveInitialState() expect(rafSpy).toHaveBeenCalled() - mockStore.maskCtx = mockMaskCtx + const createMockImageData = (): ImageData => { + return { + data: new Uint8ClampedArray(100 * 100 * 4), + width: 100, + height: 100 + } as ImageData + } + + mockRefs.maskCtx = { + getImageData: vi.fn(() => createMockImageData()), + putImageData: vi.fn(), + clearRect: vi.fn(), + drawImage: vi.fn() + } as Partial as CanvasRenderingContext2D }) }) @@ -134,13 +216,20 @@ describe('useCanvasHistory', () => { const history = useCanvasHistory() history.saveInitialState() - mockMaskCtx.getImageData.mockClear() - mockRgbCtx.getImageData.mockClear() + vi.mocked(mockRefs.maskCtx!.getImageData).mockClear() + vi.mocked(mockRefs.rgbCtx!.getImageData).mockClear() + vi.mocked(mockRefs.imgCtx!.getImageData).mockClear() history.saveState() - expect(mockMaskCtx.getImageData).toHaveBeenCalledWith(0, 0, 100, 100) - expect(mockRgbCtx.getImageData).toHaveBeenCalledWith(0, 0, 100, 100) + expect(mockRefs.maskCtx!.getImageData).toHaveBeenCalledWith( + 0, + 0, + 100, + 100 + ) + expect(mockRefs.rgbCtx!.getImageData).toHaveBeenCalledWith(0, 0, 100, 100) + expect(mockRefs.imgCtx!.getImageData).toHaveBeenCalledWith(0, 0, 100, 100) expect(history.canUndo.value).toBe(true) }) @@ -184,8 +273,9 @@ describe('useCanvasHistory', () => { history.saveState() - expect(mockMaskCtx.getImageData).toHaveBeenCalled() - expect(mockRgbCtx.getImageData).toHaveBeenCalled() + expect(mockRefs.maskCtx!.getImageData).toHaveBeenCalled() + expect(mockRefs.rgbCtx!.getImageData).toHaveBeenCalled() + expect(mockRefs.imgCtx!.getImageData).toHaveBeenCalled() }) it('should not save state if context is missing', () => { @@ -193,15 +283,17 @@ describe('useCanvasHistory', () => { history.saveInitialState() - mockStore.maskCtx = null - mockMaskCtx.getImageData.mockClear() - mockRgbCtx.getImageData.mockClear() + const savedMaskCtx = mockRefs.maskCtx + mockRefs.maskCtx = null + vi.mocked(savedMaskCtx!.getImageData).mockClear() + vi.mocked(mockRefs.rgbCtx!.getImageData).mockClear() + vi.mocked(mockRefs.imgCtx!.getImageData).mockClear() history.saveState() - expect(mockMaskCtx.getImageData).not.toHaveBeenCalled() + expect(savedMaskCtx!.getImageData).not.toHaveBeenCalled() - mockStore.maskCtx = mockMaskCtx + mockRefs.maskCtx = savedMaskCtx }) }) @@ -214,20 +306,27 @@ describe('useCanvasHistory', () => { history.undo() - expect(mockMaskCtx.putImageData).toHaveBeenCalled() - expect(mockRgbCtx.putImageData).toHaveBeenCalled() + expect(mockRefs.maskCtx!.putImageData).toHaveBeenCalled() + expect(mockRefs.rgbCtx!.putImageData).toHaveBeenCalled() + expect(mockRefs.imgCtx!.putImageData).toHaveBeenCalled() expect(history.canUndo.value).toBe(false) expect(history.canRedo.value).toBe(true) }) - it('should show alert when no undo states available', () => { - const alertSpy = vi.spyOn(window, 'alert') + it('should not undo when no undo states available', () => { const history = useCanvasHistory() history.saveInitialState() + + vi.mocked(mockRefs.maskCtx!.putImageData).mockClear() + vi.mocked(mockRefs.rgbCtx!.putImageData).mockClear() + vi.mocked(mockRefs.imgCtx!.putImageData).mockClear() + history.undo() - expect(alertSpy).toHaveBeenCalledWith('No more undo states available') + expect(mockRefs.maskCtx!.putImageData).not.toHaveBeenCalled() + expect(mockRefs.rgbCtx!.putImageData).not.toHaveBeenCalled() + expect(mockRefs.imgCtx!.putImageData).not.toHaveBeenCalled() }) it('should undo multiple times', () => { @@ -249,16 +348,22 @@ describe('useCanvasHistory', () => { }) it('should not undo beyond first state', () => { - const alertSpy = vi.spyOn(window, 'alert') const history = useCanvasHistory() history.saveInitialState() history.saveState() history.undo() + + vi.mocked(mockRefs.maskCtx!.putImageData).mockClear() + vi.mocked(mockRefs.rgbCtx!.putImageData).mockClear() + vi.mocked(mockRefs.imgCtx!.putImageData).mockClear() + history.undo() - expect(alertSpy).toHaveBeenCalled() + expect(mockRefs.maskCtx!.putImageData).not.toHaveBeenCalled() + expect(mockRefs.rgbCtx!.putImageData).not.toHaveBeenCalled() + expect(mockRefs.imgCtx!.putImageData).not.toHaveBeenCalled() }) }) @@ -270,25 +375,33 @@ describe('useCanvasHistory', () => { history.saveState() history.undo() - mockMaskCtx.putImageData.mockClear() - mockRgbCtx.putImageData.mockClear() + vi.mocked(mockRefs.maskCtx!.putImageData).mockClear() + vi.mocked(mockRefs.rgbCtx!.putImageData).mockClear() + vi.mocked(mockRefs.imgCtx!.putImageData).mockClear() history.redo() - expect(mockMaskCtx.putImageData).toHaveBeenCalled() - expect(mockRgbCtx.putImageData).toHaveBeenCalled() + expect(mockRefs.maskCtx!.putImageData).toHaveBeenCalled() + expect(mockRefs.rgbCtx!.putImageData).toHaveBeenCalled() + expect(mockRefs.imgCtx!.putImageData).toHaveBeenCalled() expect(history.canRedo.value).toBe(false) expect(history.canUndo.value).toBe(true) }) - it('should show alert when no redo states available', () => { - const alertSpy = vi.spyOn(window, 'alert') + it('should not redo when no redo states available', () => { const history = useCanvasHistory() history.saveInitialState() + + vi.mocked(mockRefs.maskCtx!.putImageData).mockClear() + vi.mocked(mockRefs.rgbCtx!.putImageData).mockClear() + vi.mocked(mockRefs.imgCtx!.putImageData).mockClear() + history.redo() - expect(alertSpy).toHaveBeenCalledWith('No more redo states available') + expect(mockRefs.maskCtx!.putImageData).not.toHaveBeenCalled() + expect(mockRefs.rgbCtx!.putImageData).not.toHaveBeenCalled() + expect(mockRefs.imgCtx!.putImageData).not.toHaveBeenCalled() }) it('should redo multiple times', () => { @@ -314,7 +427,6 @@ describe('useCanvasHistory', () => { }) it('should not redo beyond last state', () => { - const alertSpy = vi.spyOn(window, 'alert') const history = useCanvasHistory() history.saveInitialState() @@ -322,9 +434,16 @@ describe('useCanvasHistory', () => { history.undo() history.redo() + + vi.mocked(mockRefs.maskCtx!.putImageData).mockClear() + vi.mocked(mockRefs.rgbCtx!.putImageData).mockClear() + vi.mocked(mockRefs.imgCtx!.putImageData).mockClear() + history.redo() - expect(alertSpy).toHaveBeenCalled() + expect(mockRefs.maskCtx!.putImageData).not.toHaveBeenCalled() + expect(mockRefs.rgbCtx!.putImageData).not.toHaveBeenCalled() + expect(mockRefs.imgCtx!.putImageData).not.toHaveBeenCalled() }) }) @@ -348,13 +467,15 @@ describe('useCanvasHistory', () => { history.saveInitialState() history.clearStates() - mockMaskCtx.getImageData.mockClear() - mockRgbCtx.getImageData.mockClear() + vi.mocked(mockRefs.maskCtx!.getImageData).mockClear() + vi.mocked(mockRefs.rgbCtx!.getImageData).mockClear() + vi.mocked(mockRefs.imgCtx!.getImageData).mockClear() history.saveInitialState() - expect(mockMaskCtx.getImageData).toHaveBeenCalled() - expect(mockRgbCtx.getImageData).toHaveBeenCalled() + expect(mockRefs.maskCtx!.getImageData).toHaveBeenCalled() + expect(mockRefs.rgbCtx!.getImageData).toHaveBeenCalled() + expect(mockRefs.imgCtx!.getImageData).toHaveBeenCalled() }) }) @@ -446,15 +567,17 @@ describe('useCanvasHistory', () => { history.saveInitialState() history.saveState() - mockStore.maskCtx = null - mockMaskCtx.putImageData.mockClear() - mockRgbCtx.putImageData.mockClear() + const savedMaskCtx = mockRefs.maskCtx + mockRefs.maskCtx = null + vi.mocked(savedMaskCtx!.putImageData).mockClear() + vi.mocked(mockRefs.rgbCtx!.putImageData).mockClear() + vi.mocked(mockRefs.imgCtx!.putImageData).mockClear() history.undo() - expect(mockMaskCtx.putImageData).not.toHaveBeenCalled() + expect(savedMaskCtx!.putImageData).not.toHaveBeenCalled() - mockStore.maskCtx = mockMaskCtx + mockRefs.maskCtx = savedMaskCtx }) }) @@ -499,8 +622,12 @@ describe('useCanvasHistory', () => { }) it('should handle zero-sized canvas', () => { - mockMaskCanvas.width = 0 - mockMaskCanvas.height = 0 + if (mockRefs.maskCanvas) { + mockRefs.maskCanvas = { + width: 0, + height: 0 + } as Partial as HTMLCanvasElement + } const history = useCanvasHistory() diff --git a/src/composables/maskeditor/useCanvasHistory.ts b/src/composables/maskeditor/useCanvasHistory.ts index 122e888c7..a7c394530 100644 --- a/src/composables/maskeditor/useCanvasHistory.ts +++ b/src/composables/maskeditor/useCanvasHistory.ts @@ -1,12 +1,17 @@ import { ref, computed } from 'vue' import { useMaskEditorStore } from '@/stores/maskEditorStore' +// Define the state interface for better readability +interface CanvasState { + mask: ImageData | ImageBitmap + rgb: ImageData | ImageBitmap + img: ImageData | ImageBitmap +} + export function useCanvasHistory(maxStates = 20) { const store = useMaskEditorStore() - const states = ref< - { mask: ImageData | ImageBitmap; rgb: ImageData | ImageBitmap }[] - >([]) + const states = ref([]) const currentStateIndex = ref(-1) const initialized = ref(false) @@ -22,22 +27,29 @@ export function useCanvasHistory(maxStates = 20) { }) const saveInitialState = () => { - const maskCtx = store.maskCtx - const rgbCtx = store.rgbCtx - const maskCanvas = store.maskCanvas - const rgbCanvas = store.rgbCanvas + const { maskCtx, rgbCtx, imgCtx, maskCanvas, rgbCanvas, imgCanvas } = store - if (!maskCtx || !rgbCtx || !maskCanvas || !rgbCanvas) { + // Ensure all 3 contexts and canvases are ready + if ( + !maskCtx || + !rgbCtx || + !imgCtx || + !maskCanvas || + !rgbCanvas || + !imgCanvas + ) { requestAnimationFrame(saveInitialState) return } - if (!maskCanvas.width || !rgbCanvas.width) { + if (!maskCanvas.width || !rgbCanvas.width || !imgCanvas.width) { requestAnimationFrame(saveInitialState) return } states.value = [] + + // Capture all three layers const maskState = maskCtx.getImageData( 0, 0, @@ -50,35 +62,51 @@ export function useCanvasHistory(maxStates = 20) { rgbCanvas.width, rgbCanvas.height ) - states.value.push({ mask: maskState, rgb: rgbState }) + const imgState = imgCtx.getImageData( + 0, + 0, + imgCanvas.width, + imgCanvas.height + ) + + states.value.push({ mask: maskState, rgb: rgbState, img: imgState }) currentStateIndex.value = 0 initialized.value = true } const saveState = ( providedMaskData?: ImageData | ImageBitmap, - providedRgbData?: ImageData | ImageBitmap + providedRgbData?: ImageData | ImageBitmap, + providedImgData?: ImageData | ImageBitmap ) => { - const maskCtx = store.maskCtx - const rgbCtx = store.rgbCtx - const maskCanvas = store.maskCanvas - const rgbCanvas = store.rgbCanvas + const { maskCtx, rgbCtx, imgCtx, maskCanvas, rgbCanvas, imgCanvas } = store - if (!maskCtx || !rgbCtx || !maskCanvas || !rgbCanvas) return + if ( + !maskCtx || + !rgbCtx || + !imgCtx || + !maskCanvas || + !rgbCanvas || + !imgCanvas + ) + return if (!initialized.value || currentStateIndex.value === -1) { saveInitialState() return } + // Clear redo history states.value = states.value.slice(0, currentStateIndex.value + 1) let maskState: ImageData | ImageBitmap let rgbState: ImageData | ImageBitmap + let imgState: ImageData | ImageBitmap - if (providedMaskData && providedRgbData) { + if (providedMaskData && providedRgbData && providedImgData) { maskState = providedMaskData rgbState = providedRgbData + imgState = providedImgData } else { maskState = maskCtx.getImageData( 0, @@ -87,71 +115,84 @@ export function useCanvasHistory(maxStates = 20) { maskCanvas.height ) rgbState = rgbCtx.getImageData(0, 0, rgbCanvas.width, rgbCanvas.height) + imgState = imgCtx.getImageData(0, 0, imgCanvas.width, imgCanvas.height) } - states.value.push({ mask: maskState, rgb: rgbState }) + states.value.push({ mask: maskState, rgb: rgbState, img: imgState }) currentStateIndex.value++ + // Maintain max history size and clean up memory if (states.value.length > maxStates) { const removed = states.value.shift() - // Cleanup ImageBitmaps to avoid memory leaks if (removed) { - if (removed.mask instanceof ImageBitmap) removed.mask.close() - if (removed.rgb instanceof ImageBitmap) removed.rgb.close() + cleanupState(removed) } currentStateIndex.value-- } } const undo = () => { - if (!canUndo.value) { - alert('No more undo states available') - return - } - + if (!canUndo.value) return currentStateIndex.value-- restoreState(states.value[currentStateIndex.value]) } const redo = () => { - if (!canRedo.value) { - alert('No more redo states available') - return - } - + if (!canRedo.value) return currentStateIndex.value++ restoreState(states.value[currentStateIndex.value]) } - const restoreState = (state: { - mask: ImageData | ImageBitmap - rgb: ImageData | ImageBitmap - }) => { - const maskCtx = store.maskCtx - const rgbCtx = store.rgbCtx - if (!maskCtx || !rgbCtx) return + const restoreState = (state: CanvasState) => { + const { maskCtx, rgbCtx, imgCtx, maskCanvas, rgbCanvas, imgCanvas } = store + if ( + !maskCtx || + !rgbCtx || + !imgCtx || + !maskCanvas || + !rgbCanvas || + !imgCanvas + ) + return - if (state.mask instanceof ImageBitmap) { - maskCtx.clearRect(0, 0, state.mask.width, state.mask.height) - maskCtx.drawImage(state.mask, 0, 0) - } else { - maskCtx.putImageData(state.mask, 0, 0) + // Update canvas dimensions to match state (handles rotation undo/redo) + const refData = state.mask + const newWidth = refData.width + const newHeight = refData.height + + if (maskCanvas.width !== newWidth || maskCanvas.height !== newHeight) { + maskCanvas.width = newWidth + maskCanvas.height = newHeight + rgbCanvas.width = newWidth + rgbCanvas.height = newHeight + imgCanvas.width = newWidth + imgCanvas.height = newHeight } - if (state.rgb instanceof ImageBitmap) { - rgbCtx.clearRect(0, 0, state.rgb.width, state.rgb.height) - rgbCtx.drawImage(state.rgb, 0, 0) - } else { - rgbCtx.putImageData(state.rgb, 0, 0) - } + const layers = [ + { ctx: maskCtx, data: state.mask }, + { ctx: rgbCtx, data: state.rgb }, + { ctx: imgCtx, data: state.img } + ] + + layers.forEach(({ ctx, data }) => { + if (data instanceof ImageBitmap) { + ctx.clearRect(0, 0, data.width, data.height) + ctx.drawImage(data, 0, 0) + } else { + ctx.putImageData(data, 0, 0) + } + }) + } + + const cleanupState = (state: CanvasState) => { + if (state.mask instanceof ImageBitmap) state.mask.close() + if (state.rgb instanceof ImageBitmap) state.rgb.close() + if (state.img instanceof ImageBitmap) state.img.close() } const clearStates = () => { - // Cleanup bitmaps - states.value.forEach((state) => { - if (state.mask instanceof ImageBitmap) state.mask.close() - if (state.rgb instanceof ImageBitmap) state.rgb.close() - }) + states.value.forEach(cleanupState) states.value = [] currentStateIndex.value = -1 initialized.value = false diff --git a/tests-ui/tests/composables/maskeditor/useCanvasManager.test.ts b/src/composables/maskeditor/useCanvasManager.test.ts similarity index 87% rename from tests-ui/tests/composables/maskeditor/useCanvasManager.test.ts rename to src/composables/maskeditor/useCanvasManager.test.ts index 4fe40df6e..48e1bf7b4 100644 --- a/tests-ui/tests/composables/maskeditor/useCanvasManager.test.ts +++ b/src/composables/maskeditor/useCanvasManager.test.ts @@ -3,13 +3,13 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { MaskBlendMode } from '@/extensions/core/maskeditor/types' import { useCanvasManager } from '@/composables/maskeditor/useCanvasManager' const mockStore = { - imgCanvas: null as any, - maskCanvas: null as any, - rgbCanvas: null as any, - imgCtx: null as any, - maskCtx: null as any, - rgbCtx: null as any, - canvasBackground: null as any, + imgCanvas: null! as HTMLCanvasElement, + maskCanvas: null! as HTMLCanvasElement, + rgbCanvas: null! as HTMLCanvasElement, + imgCtx: null! as CanvasRenderingContext2D, + maskCtx: null! as CanvasRenderingContext2D, + rgbCtx: null! as CanvasRenderingContext2D, + canvasBackground: null! as HTMLElement, maskColor: { r: 0, g: 0, b: 0 }, maskBlendMode: MaskBlendMode.Black, maskOpacity: 0.8 @@ -38,26 +38,30 @@ describe('useCanvasManager', () => { height: 100 } as ImageData - mockStore.imgCtx = { + const partialImgCtx: Partial = { drawImage: vi.fn() } + mockStore.imgCtx = partialImgCtx as CanvasRenderingContext2D - mockStore.maskCtx = { + const partialMaskCtx: Partial = { drawImage: vi.fn(), getImageData: vi.fn(() => mockImageData), putImageData: vi.fn(), globalCompositeOperation: 'source-over', fillStyle: '' } + mockStore.maskCtx = partialMaskCtx as CanvasRenderingContext2D - mockStore.rgbCtx = { + const partialRgbCtx: Partial = { drawImage: vi.fn() } + mockStore.rgbCtx = partialRgbCtx as CanvasRenderingContext2D - mockStore.imgCanvas = { + const partialImgCanvas: Partial = { width: 0, height: 0 } + mockStore.imgCanvas = partialImgCanvas as HTMLCanvasElement mockStore.maskCanvas = { width: 0, @@ -65,19 +69,19 @@ describe('useCanvasManager', () => { style: { mixBlendMode: '', opacity: '' - } - } + } as Pick + } as HTMLCanvasElement mockStore.rgbCanvas = { width: 0, height: 0 - } + } as HTMLCanvasElement mockStore.canvasBackground = { style: { backgroundColor: '' - } - } + } as Pick + } as HTMLElement mockStore.maskColor = { r: 0, g: 0, b: 0 } mockStore.maskBlendMode = MaskBlendMode.Black @@ -163,7 +167,7 @@ describe('useCanvasManager', () => { it('should throw error when canvas missing', async () => { const manager = useCanvasManager() - mockStore.imgCanvas = null + mockStore.imgCanvas = null! as HTMLCanvasElement const origImage = createMockImage(512, 512) const maskImage = createMockImage(512, 512) @@ -176,7 +180,7 @@ describe('useCanvasManager', () => { it('should throw error when context missing', async () => { const manager = useCanvasManager() - mockStore.imgCtx = null + mockStore.imgCtx = null! as CanvasRenderingContext2D const origImage = createMockImage(512, 512) const maskImage = createMockImage(512, 512) @@ -259,7 +263,7 @@ describe('useCanvasManager', () => { it('should return early when canvas missing', async () => { const manager = useCanvasManager() - mockStore.maskCanvas = null + mockStore.maskCanvas = null! as HTMLCanvasElement await manager.updateMaskColor() @@ -269,7 +273,7 @@ describe('useCanvasManager', () => { it('should return early when context missing', async () => { const manager = useCanvasManager() - mockStore.maskCtx = null + mockStore.maskCtx = null! as CanvasRenderingContext2D await manager.updateMaskColor() diff --git a/tests-ui/tests/composables/maskeditor/useCanvasTools.test.ts b/src/composables/maskeditor/useCanvasTools.test.ts similarity index 75% rename from tests-ui/tests/composables/maskeditor/useCanvasTools.test.ts rename to src/composables/maskeditor/useCanvasTools.test.ts index 991d19c00..17a46f8b6 100644 --- a/tests-ui/tests/composables/maskeditor/useCanvasTools.test.ts +++ b/src/composables/maskeditor/useCanvasTools.test.ts @@ -4,17 +4,37 @@ import { ColorComparisonMethod } from '@/extensions/core/maskeditor/types' import { useCanvasTools } from '@/composables/maskeditor/useCanvasTools' +// Mock store interface matching the real store's nullable fields +interface MockMaskEditorStore { + maskCtx: CanvasRenderingContext2D | null + imgCtx: CanvasRenderingContext2D | null + maskCanvas: HTMLCanvasElement | null + imgCanvas: HTMLCanvasElement | null + rgbCtx: CanvasRenderingContext2D | null + rgbCanvas: HTMLCanvasElement | null + maskColor: { r: number; g: number; b: number } + paintBucketTolerance: number + fillOpacity: number + colorSelectTolerance: number + colorComparisonMethod: ColorComparisonMethod + selectionOpacity: number + applyWholeImage: boolean + maskBoundary: boolean + maskTolerance: number + canvasHistory: { saveState: ReturnType } +} + const mockCanvasHistory = { saveState: vi.fn() } -const mockStore = { - maskCtx: null as any, - imgCtx: null as any, - maskCanvas: null as any, - imgCanvas: null as any, - rgbCtx: null as any, - rgbCanvas: null as any, +const mockStore: MockMaskEditorStore = { + maskCtx: null, + imgCtx: null, + maskCanvas: null, + imgCanvas: null, + rgbCtx: null, + rgbCanvas: null, maskColor: { r: 255, g: 255, b: 255 }, paintBucketTolerance: 10, fillOpacity: 100, @@ -57,34 +77,40 @@ describe('useCanvasTools', () => { mockImgImageData.data[i + 3] = 255 } - mockStore.maskCtx = { + const partialMaskCtx: Partial = { getImageData: vi.fn(() => mockMaskImageData), putImageData: vi.fn(), clearRect: vi.fn() } + mockStore.maskCtx = partialMaskCtx as CanvasRenderingContext2D - mockStore.imgCtx = { + const partialImgCtx: Partial = { getImageData: vi.fn(() => mockImgImageData) } + mockStore.imgCtx = partialImgCtx as CanvasRenderingContext2D - mockStore.rgbCtx = { + const partialRgbCtx: Partial = { clearRect: vi.fn() } + mockStore.rgbCtx = partialRgbCtx as CanvasRenderingContext2D - mockStore.maskCanvas = { + const partialMaskCanvas: Partial = { width: 100, height: 100 } + mockStore.maskCanvas = partialMaskCanvas as HTMLCanvasElement - mockStore.imgCanvas = { + const partialImgCanvas: Partial = { width: 100, height: 100 } + mockStore.imgCanvas = partialImgCanvas as HTMLCanvasElement - mockStore.rgbCanvas = { + const partialRgbCanvas: Partial = { width: 100, height: 100 } + mockStore.rgbCanvas = partialRgbCanvas as HTMLCanvasElement mockStore.maskColor = { r: 255, g: 255, b: 255 } mockStore.paintBucketTolerance = 10 @@ -103,13 +129,13 @@ describe('useCanvasTools', () => { tools.paintBucketFill({ x: 50, y: 50 }) - expect(mockStore.maskCtx.getImageData).toHaveBeenCalledWith( + expect(mockStore.maskCtx!.getImageData).toHaveBeenCalledWith( 0, 0, 100, 100 ) - expect(mockStore.maskCtx.putImageData).toHaveBeenCalledWith( + expect(mockStore.maskCtx!.putImageData).toHaveBeenCalledWith( mockMaskImageData, 0, 0 @@ -154,7 +180,7 @@ describe('useCanvasTools', () => { tools.paintBucketFill({ x: -1, y: 50 }) - expect(mockStore.maskCtx.putImageData).not.toHaveBeenCalled() + expect(mockStore.maskCtx!.putImageData).not.toHaveBeenCalled() }) it('should return early when canvas missing', () => { @@ -164,7 +190,7 @@ describe('useCanvasTools', () => { tools.paintBucketFill({ x: 50, y: 50 }) - expect(mockStore.maskCtx.getImageData).not.toHaveBeenCalled() + expect(mockStore.maskCtx?.getImageData).not.toHaveBeenCalled() }) it('should apply fill opacity', () => { @@ -198,14 +224,19 @@ describe('useCanvasTools', () => { await tools.colorSelectFill({ x: 50, y: 50 }) - expect(mockStore.maskCtx.getImageData).toHaveBeenCalledWith( + expect(mockStore.maskCtx!.getImageData).toHaveBeenCalledWith( 0, 0, 100, 100 ) - expect(mockStore.imgCtx.getImageData).toHaveBeenCalledWith(0, 0, 100, 100) - expect(mockStore.maskCtx.putImageData).toHaveBeenCalled() + expect(mockStore.imgCtx!.getImageData).toHaveBeenCalledWith( + 0, + 0, + 100, + 100 + ) + expect(mockStore.maskCtx!.putImageData).toHaveBeenCalled() expect(mockCanvasHistory.saveState).toHaveBeenCalled() }) @@ -216,7 +247,7 @@ describe('useCanvasTools', () => { await tools.colorSelectFill({ x: 50, y: 50 }) - expect(mockStore.maskCtx.putImageData).toHaveBeenCalled() + expect(mockStore.maskCtx!.putImageData).toHaveBeenCalled() }) it('should respect color tolerance', async () => { @@ -239,7 +270,7 @@ describe('useCanvasTools', () => { await tools.colorSelectFill({ x: -1, y: 50 }) - expect(mockStore.maskCtx.putImageData).not.toHaveBeenCalled() + expect(mockStore.maskCtx!.putImageData).not.toHaveBeenCalled() }) it('should return early when canvas missing', async () => { @@ -249,7 +280,7 @@ describe('useCanvasTools', () => { await tools.colorSelectFill({ x: 50, y: 50 }) - expect(mockStore.maskCtx.getImageData).not.toHaveBeenCalled() + expect(mockStore.maskCtx?.getImageData).not.toHaveBeenCalled() }) it('should apply selection opacity', async () => { @@ -270,7 +301,7 @@ describe('useCanvasTools', () => { await tools.colorSelectFill({ x: 50, y: 50 }) - expect(mockStore.maskCtx.putImageData).toHaveBeenCalled() + expect(mockStore.maskCtx!.putImageData).toHaveBeenCalled() }) it('should use LAB color comparison method', async () => { @@ -280,7 +311,7 @@ describe('useCanvasTools', () => { await tools.colorSelectFill({ x: 50, y: 50 }) - expect(mockStore.maskCtx.putImageData).toHaveBeenCalled() + expect(mockStore.maskCtx!.putImageData).toHaveBeenCalled() }) it('should respect mask boundary', async () => { @@ -295,7 +326,7 @@ describe('useCanvasTools', () => { await tools.colorSelectFill({ x: 50, y: 50 }) - expect(mockStore.maskCtx.putImageData).toHaveBeenCalled() + expect(mockStore.maskCtx!.putImageData).toHaveBeenCalled() }) it('should update last color select point', async () => { @@ -303,7 +334,7 @@ describe('useCanvasTools', () => { await tools.colorSelectFill({ x: 30, y: 40 }) - expect(mockStore.maskCtx.putImageData).toHaveBeenCalled() + expect(mockStore.maskCtx!.putImageData).toHaveBeenCalled() }) }) @@ -320,13 +351,13 @@ describe('useCanvasTools', () => { tools.invertMask() - expect(mockStore.maskCtx.getImageData).toHaveBeenCalledWith( + expect(mockStore.maskCtx!.getImageData).toHaveBeenCalledWith( 0, 0, 100, 100 ) - expect(mockStore.maskCtx.putImageData).toHaveBeenCalledWith( + expect(mockStore.maskCtx!.putImageData).toHaveBeenCalledWith( mockMaskImageData, 0, 0 @@ -369,7 +400,7 @@ describe('useCanvasTools', () => { tools.invertMask() - expect(mockStore.maskCtx.getImageData).not.toHaveBeenCalled() + expect(mockStore.maskCtx?.getImageData).not.toHaveBeenCalled() }) it('should return early when context missing', () => { @@ -389,8 +420,8 @@ describe('useCanvasTools', () => { tools.clearMask() - expect(mockStore.maskCtx.clearRect).toHaveBeenCalledWith(0, 0, 100, 100) - expect(mockStore.rgbCtx.clearRect).toHaveBeenCalledWith(0, 0, 100, 100) + expect(mockStore.maskCtx!.clearRect).toHaveBeenCalledWith(0, 0, 100, 100) + expect(mockStore.rgbCtx!.clearRect).toHaveBeenCalledWith(0, 0, 100, 100) expect(mockCanvasHistory.saveState).toHaveBeenCalled() }) @@ -401,7 +432,7 @@ describe('useCanvasTools', () => { tools.clearMask() - expect(mockStore.maskCtx.clearRect).not.toHaveBeenCalled() + expect(mockStore.maskCtx?.clearRect).not.toHaveBeenCalled() expect(mockCanvasHistory.saveState).toHaveBeenCalled() }) @@ -412,8 +443,8 @@ describe('useCanvasTools', () => { tools.clearMask() - expect(mockStore.maskCtx.clearRect).toHaveBeenCalledWith(0, 0, 100, 100) - expect(mockStore.rgbCtx.clearRect).not.toHaveBeenCalled() + expect(mockStore.maskCtx?.clearRect).toHaveBeenCalledWith(0, 0, 100, 100) + expect(mockStore.rgbCtx?.clearRect).not.toHaveBeenCalled() expect(mockCanvasHistory.saveState).toHaveBeenCalled() }) }) @@ -426,26 +457,26 @@ describe('useCanvasTools', () => { tools.clearLastColorSelectPoint() - expect(mockStore.maskCtx.putImageData).toHaveBeenCalled() + expect(mockStore.maskCtx!.putImageData).toHaveBeenCalled() }) }) describe('edge cases', () => { it('should handle small canvas', () => { - mockStore.maskCanvas.width = 1 - mockStore.maskCanvas.height = 1 + mockStore.maskCanvas!.width = 1 + mockStore.maskCanvas!.height = 1 mockMaskImageData = { data: new Uint8ClampedArray(1 * 1 * 4), width: 1, height: 1 } as ImageData - mockStore.maskCtx.getImageData = vi.fn(() => mockMaskImageData) + mockStore.maskCtx!.getImageData = vi.fn(() => mockMaskImageData) const tools = useCanvasTools() tools.paintBucketFill({ x: 0, y: 0 }) - expect(mockStore.maskCtx.putImageData).toHaveBeenCalled() + expect(mockStore.maskCtx!.putImageData).toHaveBeenCalled() }) it('should handle fractional coordinates', () => { @@ -453,7 +484,7 @@ describe('useCanvasTools', () => { tools.paintBucketFill({ x: 50.7, y: 50.3 }) - expect(mockStore.maskCtx.putImageData).toHaveBeenCalled() + expect(mockStore.maskCtx!.putImageData).toHaveBeenCalled() }) it('should handle maximum tolerance', () => { @@ -463,7 +494,7 @@ describe('useCanvasTools', () => { tools.paintBucketFill({ x: 50, y: 50 }) - expect(mockStore.maskCtx.putImageData).toHaveBeenCalled() + expect(mockStore.maskCtx!.putImageData).toHaveBeenCalled() }) it('should handle zero opacity', () => { diff --git a/src/composables/maskeditor/useCanvasTransform.test.ts b/src/composables/maskeditor/useCanvasTransform.test.ts new file mode 100644 index 000000000..07ea0bb43 --- /dev/null +++ b/src/composables/maskeditor/useCanvasTransform.test.ts @@ -0,0 +1,683 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { useCanvasTransform } from '@/composables/maskeditor/useCanvasTransform' + +interface IMockCanvas { + width: number + height: number +} + +interface IMockContext { + getImageData: ReturnType + putImageData: ReturnType + clearRect: ReturnType + drawImage: ReturnType +} + +interface IMockCanvasHistory { + saveState: ReturnType +} + +interface IMockStore { + maskCanvas: IMockCanvas | null + rgbCanvas: IMockCanvas | null + imgCanvas: IMockCanvas | null + maskCtx: IMockContext | null + rgbCtx: IMockContext | null + imgCtx: IMockContext | null + tgpuRoot: unknown + canvasHistory: IMockCanvasHistory + gpuTexturesNeedRecreation: boolean + gpuTextureWidth: number + gpuTextureHeight: number + pendingGPUMaskData: Uint8ClampedArray | null + pendingGPURgbData: Uint8ClampedArray | null +} + +const { mockStore, mockCanvasHistory } = vi.hoisted(() => { + const mockCanvasHistory: IMockCanvasHistory = { + saveState: vi.fn() + } + + const mockStore: IMockStore = { + maskCanvas: null, + rgbCanvas: null, + imgCanvas: null, + maskCtx: null, + rgbCtx: null, + imgCtx: null, + tgpuRoot: null, + canvasHistory: mockCanvasHistory, + gpuTexturesNeedRecreation: false, + gpuTextureWidth: 0, + gpuTextureHeight: 0, + pendingGPUMaskData: null, + pendingGPURgbData: null + } + + return { mockStore, mockCanvasHistory } +}) + +vi.mock('@/stores/maskEditorStore', () => ({ + useMaskEditorStore: vi.fn(() => mockStore) +})) + +// Mock ImageData with improved type safety +if (typeof globalThis.ImageData === 'undefined') { + globalThis.ImageData = class ImageData { + data: Uint8ClampedArray + width: number + height: number + + constructor( + dataOrWidth: Uint8ClampedArray | number, + widthOrHeight?: number, + height?: number + ) { + if (dataOrWidth instanceof Uint8ClampedArray) { + // Constructor overload: new ImageData(data, width, height) + if (widthOrHeight === undefined || height === undefined) { + throw new Error( + 'ImageData constructor requires width and height when data is provided' + ) + } + this.data = dataOrWidth + this.width = widthOrHeight + this.height = height + } else { + // Constructor overload: new ImageData(width, height) + if (widthOrHeight === undefined) { + throw new Error( + 'ImageData constructor requires height when width is provided' + ) + } + this.width = dataOrWidth + this.height = widthOrHeight + this.data = new Uint8ClampedArray(dataOrWidth * widthOrHeight * 4) + } + } + } as typeof ImageData +} + +// Mock ImageBitmap for test environment using safe type casting +if (typeof globalThis.ImageBitmap === 'undefined') { + globalThis.ImageBitmap = class ImageBitmap { + width: number + height: number + constructor(width = 100, height = 100) { + this.width = width + this.height = height + } + close() {} + } as typeof ImageBitmap +} + +describe('useCanvasTransform', () => { + let mockMaskCanvas: IMockCanvas + let mockRgbCanvas: IMockCanvas + let mockImgCanvas: IMockCanvas + let mockMaskCtx: IMockContext + let mockRgbCtx: IMockContext + let mockImgCtx: IMockContext + + beforeEach(() => { + vi.clearAllMocks() + + const createMockImageData = (width: number, height: number) => { + const data = new Uint8ClampedArray(width * height * 4) + for (let i = 0; i < data.length; i += 4) { + data[i] = 255 // R + data[i + 1] = 0 // G + data[i + 2] = 0 // B + data[i + 3] = 255 // A + } + return { + data, + width, + height + } as ImageData + } + + mockMaskCtx = { + getImageData: vi.fn((_x, _y, w, h) => createMockImageData(w, h)), + putImageData: vi.fn(), + clearRect: vi.fn(), + drawImage: vi.fn() + } + + mockRgbCtx = { + getImageData: vi.fn((_x, _y, w, h) => createMockImageData(w, h)), + putImageData: vi.fn(), + clearRect: vi.fn(), + drawImage: vi.fn() + } + + mockImgCtx = { + getImageData: vi.fn((_x, _y, w, h) => createMockImageData(w, h)), + putImageData: vi.fn(), + clearRect: vi.fn(), + drawImage: vi.fn() + } + + mockMaskCanvas = { + width: 100, + height: 50 + } + + mockRgbCanvas = { + width: 100, + height: 50 + } + + mockImgCanvas = { + width: 100, + height: 50 + } + + mockStore.maskCanvas = mockMaskCanvas + mockStore.rgbCanvas = mockRgbCanvas + mockStore.imgCanvas = mockImgCanvas + mockStore.maskCtx = mockMaskCtx + mockStore.rgbCtx = mockRgbCtx + mockStore.imgCtx = mockImgCtx + mockStore.tgpuRoot = null + mockStore.gpuTexturesNeedRecreation = false + mockStore.gpuTextureWidth = 0 + mockStore.gpuTextureHeight = 0 + mockStore.pendingGPUMaskData = null + mockStore.pendingGPURgbData = null + }) + + describe('rotateClockwise', () => { + it('should rotate canvas 90 degrees clockwise', async () => { + const transform = useCanvasTransform() + await transform.rotateClockwise() + + expect(mockMaskCanvas.width).toBe(50) + expect(mockMaskCanvas.height).toBe(100) + expect(mockRgbCanvas.width).toBe(50) + expect(mockRgbCanvas.height).toBe(100) + expect(mockImgCanvas.width).toBe(50) + expect(mockImgCanvas.height).toBe(100) + }) + + it('should call getImageData with original dimensions', async () => { + const transform = useCanvasTransform() + await transform.rotateClockwise() + + expect(mockMaskCtx.getImageData).toHaveBeenCalledWith(0, 0, 100, 50) + expect(mockRgbCtx.getImageData).toHaveBeenCalledWith(0, 0, 100, 50) + expect(mockImgCtx.getImageData).toHaveBeenCalledWith(0, 0, 100, 50) + }) + + it('should call putImageData with rotated data', async () => { + const transform = useCanvasTransform() + await transform.rotateClockwise() + + expect(mockMaskCtx.putImageData).toHaveBeenCalled() + expect(mockRgbCtx.putImageData).toHaveBeenCalled() + expect(mockImgCtx.putImageData).toHaveBeenCalled() + + const maskCall = mockMaskCtx.putImageData.mock.calls[0][0] + expect(maskCall.width).toBe(50) + expect(maskCall.height).toBe(100) + }) + + it('should save transformed state to history', async () => { + const transform = useCanvasTransform() + await transform.rotateClockwise() + + expect(mockCanvasHistory.saveState).toHaveBeenCalled() + + const savedArgs = mockCanvasHistory.saveState.mock.calls[0] + expect(savedArgs).toHaveLength(3) + + expect(savedArgs[0].width).toBe(50) + expect(savedArgs[0].height).toBe(100) + expect(savedArgs[1].width).toBe(50) + expect(savedArgs[1].height).toBe(100) + expect(savedArgs[2].width).toBe(50) + expect(savedArgs[2].height).toBe(100) + }) + + it('should log error when canvas contexts not ready', async () => { + const consoleErrorSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}) + mockStore.maskCanvas = null + + const transform = useCanvasTransform() + await transform.rotateClockwise() + + expect(consoleErrorSpy).toHaveBeenCalledWith( + '[useCanvasTransform] Canvas contexts not ready' + ) + + consoleErrorSpy.mockRestore() + }) + + it('should handle GPU texture recreation when GPU is active', async () => { + mockStore.tgpuRoot = {} + + const transform = useCanvasTransform() + await transform.rotateClockwise() + + expect(mockStore.gpuTexturesNeedRecreation).toBe(true) + expect(mockStore.gpuTextureWidth).toBe(50) + expect(mockStore.gpuTextureHeight).toBe(100) + }) + + it('should not recreate GPU textures when GPU is inactive', async () => { + mockStore.tgpuRoot = null + + const transform = useCanvasTransform() + await transform.rotateClockwise() + + expect(mockStore.gpuTexturesNeedRecreation).toBe(false) + }) + + it('should correctly rotate pixels clockwise at pixel level', async () => { + mockMaskCanvas.width = 2 + mockMaskCanvas.height = 2 + + const createTestPattern = () => { + const data = new Uint8ClampedArray(2 * 2 * 4) + // TL (0,0): Red + data[0] = 255 + data[1] = 0 + data[2] = 0 + data[3] = 255 + // TR (1,0): Green + data[4] = 0 + data[5] = 255 + data[6] = 0 + data[7] = 255 + // BL (0,1): Blue + data[8] = 0 + data[9] = 0 + data[10] = 255 + data[11] = 255 + // BR (1,1): Yellow + data[12] = 255 + data[13] = 255 + data[14] = 0 + data[15] = 255 + return { data, width: 2, height: 2 } as ImageData + } + + mockMaskCtx.getImageData = vi.fn(() => createTestPattern()) + const transform = useCanvasTransform() + await transform.rotateClockwise() + + const result = mockMaskCtx.putImageData.mock.calls[0][0] as ImageData + + // After clockwise rotation: + // New TL should be old BL (Blue) + expect(result.data[0]).toBe(0) // R + expect(result.data[1]).toBe(0) // G + expect(result.data[2]).toBe(255) // B + expect(result.data[3]).toBe(255) // A + + // New TR should be old TL (Red) + expect(result.data[4]).toBe(255) // R + expect(result.data[5]).toBe(0) // G + expect(result.data[6]).toBe(0) // B + expect(result.data[7]).toBe(255) // A + + // New BL should be old BR (Yellow) + expect(result.data[8]).toBe(255) // R + expect(result.data[9]).toBe(255) // G + expect(result.data[10]).toBe(0) // B + expect(result.data[11]).toBe(255) // A + + // New BR should be old TR (Green) + expect(result.data[12]).toBe(0) // R + expect(result.data[13]).toBe(255) // G + expect(result.data[14]).toBe(0) // B + expect(result.data[15]).toBe(255) // A + }) + }) + + describe('rotateCounterclockwise', () => { + it('should rotate canvas 90 degrees counterclockwise', async () => { + const transform = useCanvasTransform() + await transform.rotateCounterclockwise() + + expect(mockMaskCanvas.width).toBe(50) + expect(mockMaskCanvas.height).toBe(100) + }) + + it('should call getImageData with original dimensions', async () => { + const transform = useCanvasTransform() + await transform.rotateCounterclockwise() + + expect(mockMaskCtx.getImageData).toHaveBeenCalledWith(0, 0, 100, 50) + }) + + it('should correctly rotate pixels counterclockwise at pixel level', async () => { + mockMaskCanvas.width = 2 + mockMaskCanvas.height = 2 + + const createTestPattern = () => { + const data = new Uint8ClampedArray(2 * 2 * 4) + // TL (0,0): Red + data[0] = 255 + data[1] = 0 + data[2] = 0 + data[3] = 255 + // TR (1,0): Green + data[4] = 0 + data[5] = 255 + data[6] = 0 + data[7] = 255 + // BL (0,1): Blue + data[8] = 0 + data[9] = 0 + data[10] = 255 + data[11] = 255 + // BR (1,1): Yellow + data[12] = 255 + data[13] = 255 + data[14] = 0 + data[15] = 255 + return { data, width: 2, height: 2 } as ImageData + } + + mockMaskCtx.getImageData = vi.fn(() => createTestPattern()) + const transform = useCanvasTransform() + await transform.rotateCounterclockwise() + + const result = mockMaskCtx.putImageData.mock.calls[0][0] as ImageData + + // After counterclockwise rotation: + // New TL should be old TR (Green) + expect(result.data[0]).toBe(0) // R + expect(result.data[1]).toBe(255) // G + expect(result.data[2]).toBe(0) // B + expect(result.data[3]).toBe(255) // A + + // New TR should be old BR (Yellow) + expect(result.data[4]).toBe(255) // R + expect(result.data[5]).toBe(255) // G + expect(result.data[6]).toBe(0) // B + expect(result.data[7]).toBe(255) // A + + // New BL should be old TL (Red) + expect(result.data[8]).toBe(255) // R + expect(result.data[9]).toBe(0) // G + expect(result.data[10]).toBe(0) // B + expect(result.data[11]).toBe(255) // A + + // New BR should be old BL (Blue) + expect(result.data[12]).toBe(0) // R + expect(result.data[13]).toBe(0) // G + expect(result.data[14]).toBe(255) // B + expect(result.data[15]).toBe(255) // A + }) + + it('should produce different result than clockwise rotation', async () => { + const transform = useCanvasTransform() + + const createAsymmetricImageData = (width: number, height: number) => { + const data = new Uint8ClampedArray(width * height * 4) + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const i = (y * width + x) * 4 + if (x < width / 2 && y < height / 2) { + data[i] = 255 + data[i + 3] = 255 + } else { + data[i + 3] = 255 + } + } + } + return { data, width, height } as ImageData + } + + mockMaskCtx.getImageData = vi.fn(() => createAsymmetricImageData(100, 50)) + await transform.rotateCounterclockwise() + const ccwResult = mockMaskCtx.putImageData.mock.calls[0][0] as ImageData + + mockMaskCanvas.width = 100 + mockMaskCanvas.height = 50 + mockMaskCtx.putImageData.mockClear() + + mockMaskCtx.getImageData = vi.fn(() => createAsymmetricImageData(100, 50)) + await transform.rotateClockwise() + const cwResult = mockMaskCtx.putImageData.mock.calls[0][0] as ImageData + + let pixelDifferences = 0 + for (let i = 0; i < ccwResult.data.length; i++) { + if (ccwResult.data[i] !== cwResult.data[i]) { + pixelDifferences++ + } + } + + expect(pixelDifferences).toBeGreaterThan(0) + }) + }) + + describe('mirrorHorizontal', () => { + it('should mirror canvas horizontally', async () => { + const transform = useCanvasTransform() + await transform.mirrorHorizontal() + + expect(mockMaskCanvas.width).toBe(100) + expect(mockMaskCanvas.height).toBe(50) + }) + + it('should handle GPU texture recreation when GPU is active', async () => { + mockStore.tgpuRoot = {} + + const transform = useCanvasTransform() + await transform.mirrorHorizontal() + + expect(mockStore.gpuTexturesNeedRecreation).toBe(true) + expect(mockStore.gpuTextureWidth).toBe(100) + expect(mockStore.gpuTextureHeight).toBe(50) + }) + + it('should correctly flip pixels horizontally at pixel level', async () => { + mockMaskCanvas.width = 2 + mockMaskCanvas.height = 2 + + const createTestPattern = () => { + const data = new Uint8ClampedArray(2 * 2 * 4) + // TL (0,0): Red + data[0] = 255 + data[1] = 0 + data[2] = 0 + data[3] = 255 + // TR (1,0): Green + data[4] = 0 + data[5] = 255 + data[6] = 0 + data[7] = 255 + // BL (0,1): Blue + data[8] = 0 + data[9] = 0 + data[10] = 255 + data[11] = 255 + // BR (1,1): Yellow + data[12] = 255 + data[13] = 255 + data[14] = 0 + data[15] = 255 + return { data, width: 2, height: 2 } as ImageData + } + + mockMaskCtx.getImageData = vi.fn(() => createTestPattern()) + const transform = useCanvasTransform() + await transform.mirrorHorizontal() + + const result = mockMaskCtx.putImageData.mock.calls[0][0] as ImageData + + // After horizontal flip: + // New TL should be old TR (Green) + expect(result.data[0]).toBe(0) + expect(result.data[1]).toBe(255) + // New TR should be old TL (Red) + expect(result.data[4]).toBe(255) + expect(result.data[5]).toBe(0) + }) + }) + + describe('mirrorVertical', () => { + it('should mirror canvas vertically', async () => { + const transform = useCanvasTransform() + await transform.mirrorVertical() + + expect(mockMaskCanvas.width).toBe(100) + expect(mockMaskCanvas.height).toBe(50) + }) + + it('should handle GPU texture recreation when GPU is active', async () => { + mockStore.tgpuRoot = {} + + const transform = useCanvasTransform() + await transform.mirrorVertical() + + expect(mockStore.gpuTexturesNeedRecreation).toBe(true) + expect(mockStore.gpuTextureWidth).toBe(100) + expect(mockStore.gpuTextureHeight).toBe(50) + }) + + it('should correctly flip pixels vertically at pixel level', async () => { + mockMaskCanvas.width = 2 + mockMaskCanvas.height = 2 + + const createTestPattern = () => { + const data = new Uint8ClampedArray(2 * 2 * 4) + // TL (0,0): Red + data[0] = 255 + data[1] = 0 + data[2] = 0 + data[3] = 255 + // TR (1,0): Green + data[4] = 0 + data[5] = 255 + data[6] = 0 + data[7] = 255 + // BL (0,1): Blue + data[8] = 0 + data[9] = 0 + data[10] = 255 + data[11] = 255 + // BR (1,1): Yellow + data[12] = 255 + data[13] = 255 + data[14] = 0 + data[15] = 255 + return { data, width: 2, height: 2 } as ImageData + } + + mockMaskCtx.getImageData = vi.fn(() => createTestPattern()) + const transform = useCanvasTransform() + await transform.mirrorVertical() + + const result = mockMaskCtx.putImageData.mock.calls[0][0] as ImageData + + // After vertical flip: + // New TL should be old BL (Blue) + expect(result.data[0]).toBe(0) // R + expect(result.data[1]).toBe(0) // G + expect(result.data[2]).toBe(255) // B + expect(result.data[3]).toBe(255) // A + + // New TR should be old BR (Yellow) + expect(result.data[4]).toBe(255) // R + expect(result.data[5]).toBe(255) // G + expect(result.data[6]).toBe(0) // B + expect(result.data[7]).toBe(255) // A + + // New BL should be old TL (Red) + expect(result.data[8]).toBe(255) // R + expect(result.data[9]).toBe(0) // G + expect(result.data[10]).toBe(0) // B + expect(result.data[11]).toBe(255) // A + + // New BR should be old TR (Green) + expect(result.data[12]).toBe(0) // R + expect(result.data[13]).toBe(255) // G + expect(result.data[14]).toBe(0) // B + expect(result.data[15]).toBe(255) // A + }) + + it('should log error when canvas contexts not ready', async () => { + const consoleErrorSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}) + mockStore.maskCanvas = null + + const transform = useCanvasTransform() + await transform.mirrorVertical() + + expect(consoleErrorSpy).toHaveBeenCalledWith( + '[useCanvasTransform] Canvas contexts not ready' + ) + + consoleErrorSpy.mockRestore() + }) + }) + + describe('GPU integration', () => { + it('should set GPU recreation flags for rotation', async () => { + mockStore.tgpuRoot = {} + mockMaskCanvas.width = 100 + mockMaskCanvas.height = 50 + + const transform = useCanvasTransform() + await transform.rotateClockwise() + + expect(mockStore.gpuTexturesNeedRecreation).toBe(true) + expect(mockStore.gpuTextureWidth).toBe(50) + expect(mockStore.gpuTextureHeight).toBe(100) + expect(mockStore.pendingGPUMaskData!.length).toBe(50 * 100 * 4) + expect(mockStore.pendingGPURgbData!.length).toBe(50 * 100 * 4) + }) + + it('should premultiply alpha when preparing GPU data', async () => { + mockStore.tgpuRoot = {} + mockMaskCanvas.width = 1 + mockMaskCanvas.height = 1 + + // Create 1x1 ImageData with semi-transparent pixel + const createSemiTransparentImageData = () => { + const data = new Uint8ClampedArray(1 * 1 * 4) + data[0] = 200 // R + data[1] = 100 // G + data[2] = 50 // B + data[3] = 128 // A (50% opacity) + return { data, width: 1, height: 1 } as ImageData + } + + mockMaskCtx.getImageData = vi.fn(() => createSemiTransparentImageData()) + mockRgbCtx.getImageData = vi.fn(() => createSemiTransparentImageData()) + mockImgCtx.getImageData = vi.fn(() => createSemiTransparentImageData()) + + const transform = useCanvasTransform() + await transform.rotateClockwise() + + // Verify pendingGPUMaskData contains premultiplied values + expect(mockStore.pendingGPUMaskData).not.toBeNull() + const maskData = mockStore.pendingGPUMaskData! + + // Expected premultiplied values: RGB * alpha / 255 + // R: 200 * 128 / 255 ≈ 100 + // G: 100 * 128 / 255 ≈ 50 + // B: 50 * 128 / 255 ≈ 25 + // A: 128 (preserved) + expect(maskData[0]).toBeCloseTo(100, 0) // R premultiplied + expect(maskData[1]).toBeCloseTo(50, 0) // G premultiplied + expect(maskData[2]).toBeCloseTo(25, 0) // B premultiplied + expect(maskData[3]).toBe(128) // A preserved + + // Also verify RGB canvas data + expect(mockStore.pendingGPURgbData).not.toBeNull() + const rgbData = mockStore.pendingGPURgbData! + expect(rgbData[0]).toBeCloseTo(100, 0) + expect(rgbData[1]).toBeCloseTo(50, 0) + expect(rgbData[2]).toBeCloseTo(25, 0) + expect(rgbData[3]).toBe(128) + }) + }) +}) diff --git a/src/composables/maskeditor/useCanvasTransform.ts b/src/composables/maskeditor/useCanvasTransform.ts new file mode 100644 index 000000000..1b08c4117 --- /dev/null +++ b/src/composables/maskeditor/useCanvasTransform.ts @@ -0,0 +1,359 @@ +import { useMaskEditorStore } from '@/stores/maskEditorStore' + +/** + * Composable for canvas transformation operations (rotate, mirror) + */ +export function useCanvasTransform() { + const store = useMaskEditorStore() + + /** + * Rotates a canvas 90 degrees clockwise or counter-clockwise + */ + const rotateCanvas = ( + ctx: CanvasRenderingContext2D, + canvas: HTMLCanvasElement, + clockwise: boolean + ): ImageData => { + const width = canvas.width + const height = canvas.height + + // Get current canvas data + const sourceData = ctx.getImageData(0, 0, width, height) + + // Create new ImageData with swapped dimensions + const rotatedData = new ImageData(height, width) + const src = sourceData.data + const dst = rotatedData.data + + // Rotate pixel by pixel + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const srcIdx = (y * width + x) * 4 + + // Calculate destination coordinates + let dstX: number, dstY: number + if (clockwise) { + // Rotate 90° clockwise: (x,y) -> (height-1-y, x) + dstX = height - 1 - y + dstY = x + } else { + // Rotate 90° counter-clockwise: (x,y) -> (y, width-1-x) + dstX = y + dstY = width - 1 - x + } + + const dstIdx = (dstY * height + dstX) * 4 + + // Copy RGBA values + dst[dstIdx] = src[srcIdx] + dst[dstIdx + 1] = src[srcIdx + 1] + dst[dstIdx + 2] = src[srcIdx + 2] + dst[dstIdx + 3] = src[srcIdx + 3] + } + } + + return rotatedData + } + + /** + * Mirrors a canvas horizontally or vertically + */ + const mirrorCanvas = ( + ctx: CanvasRenderingContext2D, + canvas: HTMLCanvasElement, + horizontal: boolean + ): ImageData => { + const width = canvas.width + const height = canvas.height + + // Get current canvas data + const sourceData = ctx.getImageData(0, 0, width, height) + const mirroredData = new ImageData(width, height) + const src = sourceData.data + const dst = mirroredData.data + + // Mirror pixel by pixel + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const srcIdx = (y * width + x) * 4 + + // Calculate destination coordinates + let dstX: number, dstY: number + if (horizontal) { + // Mirror horizontally: flip X axis + dstX = width - 1 - x + dstY = y + } else { + // Mirror vertically: flip Y axis + dstX = x + dstY = height - 1 - y + } + + const dstIdx = (dstY * width + dstX) * 4 + + // Copy RGBA values + dst[dstIdx] = src[srcIdx] + dst[dstIdx + 1] = src[srcIdx + 1] + dst[dstIdx + 2] = src[srcIdx + 2] + dst[dstIdx + 3] = src[srcIdx + 3] + } + } + + return mirroredData + } + + /** + * Premultiplies alpha for GPU upload + */ + const premultiplyData = (data: Uint8ClampedArray): void => { + for (let i = 0; i < data.length; i += 4) { + const a = data[i + 3] / 255 + data[i] = Math.round(data[i] * a) + data[i + 1] = Math.round(data[i + 1] * a) + data[i + 2] = Math.round(data[i + 2] * a) + } + } + + /** + * Recreates and updates GPU textures after transformation + * This is required because GPU textures have immutable dimensions + */ + const recreateGPUTextures = async ( + width: number, + height: number + ): Promise => { + if ( + !store.tgpuRoot || + !store.maskCanvas || + !store.rgbCanvas || + !store.maskCtx || + !store.rgbCtx + ) { + return + } + + // Get references to GPU resources from useBrushDrawing + // These are stored as module-level variables in useBrushDrawing + // We need to trigger a reinitialization through the store + + // Signal to useBrushDrawing that textures need recreation + store.gpuTexturesNeedRecreation = true + store.gpuTextureWidth = width + store.gpuTextureHeight = height + + // Get current canvas data + const maskImageData = store.maskCtx.getImageData(0, 0, width, height) + const rgbImageData = store.rgbCtx.getImageData(0, 0, width, height) + + // Create new Uint8ClampedArray with ArrayBuffer (not SharedArrayBuffer) + // This ensures compatibility with WebGPU writeTexture + const maskData = new Uint8ClampedArray( + new ArrayBuffer(maskImageData.data.length) + ) + const rgbData = new Uint8ClampedArray( + new ArrayBuffer(rgbImageData.data.length) + ) + + // Copy data + maskData.set(maskImageData.data) + rgbData.set(rgbImageData.data) + + // Runtime check to ensure we have ArrayBuffer backing + if ( + maskData.buffer instanceof SharedArrayBuffer || + rgbData.buffer instanceof SharedArrayBuffer + ) { + console.error( + '[useCanvasTransform] SharedArrayBuffer detected, WebGPU writeTexture will fail' + ) + return + } + + // Premultiply alpha for GPU + premultiplyData(maskData) + premultiplyData(rgbData) + + // Store the premultiplied data for useBrushDrawing to pick up + store.pendingGPUMaskData = maskData + store.pendingGPURgbData = rgbData + } + + /** + * Rotates all canvas layers 90 degrees clockwise and updates GPU + */ + const rotateClockwise = async (): Promise => { + const { maskCanvas, maskCtx, rgbCanvas, rgbCtx, imgCanvas, imgCtx } = store + + if ( + !maskCanvas || + !maskCtx || + !rgbCanvas || + !rgbCtx || + !imgCanvas || + !imgCtx + ) { + console.error('[useCanvasTransform] Canvas contexts not ready') + return + } + + // Store original dimensions + const origWidth = maskCanvas.width + const origHeight = maskCanvas.height + + // Rotate all three layers clockwise + const rotatedMask = rotateCanvas(maskCtx, maskCanvas, true) + const rotatedRgb = rotateCanvas(rgbCtx, rgbCanvas, true) + const rotatedImg = rotateCanvas(imgCtx, imgCanvas, true) + + // Update canvas dimensions (swap width/height) + maskCanvas.width = origHeight + maskCanvas.height = origWidth + rgbCanvas.width = origHeight + rgbCanvas.height = origWidth + imgCanvas.width = origHeight + imgCanvas.height = origWidth + + // Apply rotated data + maskCtx.putImageData(rotatedMask, 0, 0) + rgbCtx.putImageData(rotatedRgb, 0, 0) + imgCtx.putImageData(rotatedImg, 0, 0) + + // Recreate GPU textures with new dimensions if GPU is active + if (store.tgpuRoot) { + await recreateGPUTextures(origHeight, origWidth) + } + + // Save to history + store.canvasHistory.saveState(rotatedMask, rotatedRgb, rotatedImg) + } + + /** + * Rotates all canvas layers 90 degrees counter-clockwise and updates GPU + */ + const rotateCounterclockwise = async (): Promise => { + const { maskCanvas, maskCtx, rgbCanvas, rgbCtx, imgCanvas, imgCtx } = store + + if ( + !maskCanvas || + !maskCtx || + !rgbCanvas || + !rgbCtx || + !imgCanvas || + !imgCtx + ) { + console.error('[useCanvasTransform] Canvas contexts not ready') + return + } + + // Store original dimensions + const origWidth = maskCanvas.width + const origHeight = maskCanvas.height + + // Rotate all three layers counter-clockwise + const rotatedMask = rotateCanvas(maskCtx, maskCanvas, false) + const rotatedRgb = rotateCanvas(rgbCtx, rgbCanvas, false) + const rotatedImg = rotateCanvas(imgCtx, imgCanvas, false) + + // Update canvas dimensions (swap width/height) + maskCanvas.width = origHeight + maskCanvas.height = origWidth + rgbCanvas.width = origHeight + rgbCanvas.height = origWidth + imgCanvas.width = origHeight + imgCanvas.height = origWidth + + // Apply rotated data + maskCtx.putImageData(rotatedMask, 0, 0) + rgbCtx.putImageData(rotatedRgb, 0, 0) + imgCtx.putImageData(rotatedImg, 0, 0) + + // Recreate GPU textures with new dimensions if GPU is active + if (store.tgpuRoot) { + await recreateGPUTextures(origHeight, origWidth) + } + + // Save to history + store.canvasHistory.saveState(rotatedMask, rotatedRgb, rotatedImg) + } + + /** + * Mirrors all canvas layers horizontally and updates GPU + */ + const mirrorHorizontal = async (): Promise => { + const { maskCanvas, maskCtx, rgbCanvas, rgbCtx, imgCanvas, imgCtx } = store + + if ( + !maskCanvas || + !maskCtx || + !rgbCanvas || + !rgbCtx || + !imgCanvas || + !imgCtx + ) { + console.error('[useCanvasTransform] Canvas contexts not ready') + return + } + + // Mirror all three layers horizontally + const mirroredMask = mirrorCanvas(maskCtx, maskCanvas, true) + const mirroredRgb = mirrorCanvas(rgbCtx, rgbCanvas, true) + const mirroredImg = mirrorCanvas(imgCtx, imgCanvas, true) + + // Apply mirrored data (dimensions stay the same) + maskCtx.putImageData(mirroredMask, 0, 0) + rgbCtx.putImageData(mirroredRgb, 0, 0) + imgCtx.putImageData(mirroredImg, 0, 0) + + // Update GPU textures if GPU is active (dimensions unchanged, just data) + if (store.tgpuRoot) { + await recreateGPUTextures(maskCanvas.width, maskCanvas.height) + } + + // Save to history + store.canvasHistory.saveState(mirroredMask, mirroredRgb, mirroredImg) + } + + /** + * Mirrors all canvas layers vertically and updates GPU + */ + const mirrorVertical = async (): Promise => { + const { maskCanvas, maskCtx, rgbCanvas, rgbCtx, imgCanvas, imgCtx } = store + + if ( + !maskCanvas || + !maskCtx || + !rgbCanvas || + !rgbCtx || + !imgCanvas || + !imgCtx + ) { + console.error('[useCanvasTransform] Canvas contexts not ready') + return + } + + // Mirror all three layers vertically + const mirroredMask = mirrorCanvas(maskCtx, maskCanvas, false) + const mirroredRgb = mirrorCanvas(rgbCtx, rgbCanvas, false) + const mirroredImg = mirrorCanvas(imgCtx, imgCanvas, false) + + // Apply mirrored data (dimensions stay the same) + maskCtx.putImageData(mirroredMask, 0, 0) + rgbCtx.putImageData(mirroredRgb, 0, 0) + imgCtx.putImageData(mirroredImg, 0, 0) + + // Update GPU textures if GPU is active (dimensions unchanged, just data) + if (store.tgpuRoot) { + await recreateGPUTextures(maskCanvas.width, maskCanvas.height) + } + + // Save to history + store.canvasHistory.saveState(mirroredMask, mirroredRgb, mirroredImg) + } + + return { + rotateClockwise, + rotateCounterclockwise, + mirrorHorizontal, + mirrorVertical + } +} diff --git a/tests-ui/tests/composables/maskeditor/useImageLoader.test.ts b/src/composables/maskeditor/useImageLoader.test.ts similarity index 71% rename from tests-ui/tests/composables/maskeditor/useImageLoader.test.ts rename to src/composables/maskeditor/useImageLoader.test.ts index ae4938366..b547ac917 100644 --- a/tests-ui/tests/composables/maskeditor/useImageLoader.test.ts +++ b/src/composables/maskeditor/useImageLoader.test.ts @@ -2,22 +2,39 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { useImageLoader } from '@/composables/maskeditor/useImageLoader' +type MockStore = { + imgCanvas: HTMLCanvasElement | null + maskCanvas: HTMLCanvasElement | null + rgbCanvas: HTMLCanvasElement | null + imgCtx: CanvasRenderingContext2D | null + maskCtx: CanvasRenderingContext2D | null + image: HTMLImageElement | null +} + +type MockDataStore = { + inputData: { + baseLayer: { image: HTMLImageElement } + maskLayer: { image: HTMLImageElement } + paintLayer: { image: HTMLImageElement } | null + } | null +} + const mockCanvasManager = { invalidateCanvas: vi.fn().mockResolvedValue(undefined), updateMaskColor: vi.fn().mockResolvedValue(undefined) } -const mockStore = { - imgCanvas: null as any, - maskCanvas: null as any, - rgbCanvas: null as any, - imgCtx: null as any, - maskCtx: null as any, - image: null as any +const mockStore: MockStore = { + imgCanvas: null, + maskCanvas: null, + rgbCanvas: null, + imgCtx: null, + maskCtx: null, + image: null } -const mockDataStore = { - inputData: null as any +const mockDataStore: MockDataStore = { + inputData: null } vi.mock('@/stores/maskEditorStore', () => ({ @@ -33,7 +50,8 @@ vi.mock('@/composables/maskeditor/useCanvasManager', () => ({ })) vi.mock('@vueuse/core', () => ({ - createSharedComposable: (fn: any) => fn + createSharedComposable: unknown>(fn: T) => + fn })) describe('useImageLoader', () => { @@ -61,26 +79,26 @@ describe('useImageLoader', () => { mockStore.imgCtx = { clearRect: vi.fn() - } + } as Partial as CanvasRenderingContext2D mockStore.maskCtx = { clearRect: vi.fn() - } + } as Partial as CanvasRenderingContext2D mockStore.imgCanvas = { width: 0, height: 0 - } + } as Partial as HTMLCanvasElement mockStore.maskCanvas = { width: 0, height: 0 - } + } as Partial as HTMLCanvasElement mockStore.rgbCanvas = { width: 0, height: 0 - } + } as Partial as HTMLCanvasElement mockDataStore.inputData = { baseLayer: { image: mockBaseImage }, @@ -104,10 +122,10 @@ describe('useImageLoader', () => { await loader.loadImages() - expect(mockStore.maskCanvas.width).toBe(512) - expect(mockStore.maskCanvas.height).toBe(512) - expect(mockStore.rgbCanvas.width).toBe(512) - expect(mockStore.rgbCanvas.height).toBe(512) + expect(mockStore.maskCanvas?.width).toBe(512) + expect(mockStore.maskCanvas?.height).toBe(512) + expect(mockStore.rgbCanvas?.width).toBe(512) + expect(mockStore.rgbCanvas?.height).toBe(512) }) it('should clear canvas contexts', async () => { @@ -115,8 +133,8 @@ describe('useImageLoader', () => { await loader.loadImages() - expect(mockStore.imgCtx.clearRect).toHaveBeenCalledWith(0, 0, 0, 0) - expect(mockStore.maskCtx.clearRect).toHaveBeenCalledWith(0, 0, 0, 0) + expect(mockStore.imgCtx?.clearRect).toHaveBeenCalledWith(0, 0, 0, 0) + expect(mockStore.maskCtx?.clearRect).toHaveBeenCalledWith(0, 0, 0, 0) }) it('should call canvasManager methods', async () => { @@ -188,10 +206,10 @@ describe('useImageLoader', () => { await loader.loadImages() - expect(mockStore.maskCanvas.width).toBe(1024) - expect(mockStore.maskCanvas.height).toBe(768) - expect(mockStore.rgbCanvas.width).toBe(1024) - expect(mockStore.rgbCanvas.height).toBe(768) + expect(mockStore.maskCanvas?.width).toBe(1024) + expect(mockStore.maskCanvas?.height).toBe(768) + expect(mockStore.rgbCanvas?.width).toBe(1024) + expect(mockStore.rgbCanvas?.height).toBe(768) }) }) }) diff --git a/src/composables/maskeditor/useMaskEditorLoader.ts b/src/composables/maskeditor/useMaskEditorLoader.ts index 01aaaf0e3..74d39a5a2 100644 --- a/src/composables/maskeditor/useMaskEditorLoader.ts +++ b/src/composables/maskeditor/useMaskEditorLoader.ts @@ -104,7 +104,8 @@ export function useMaskEditorLoader() { // If we have a widget filename, we should prioritize it over the node image // because the node image might be stale (e.g. from a previous save) // while the widget value reflects the current selection. - if (widgetFilename) { + // Skip internal reference formats (e.g. "$35-0" used by some plugins like Impact-Pack) + if (widgetFilename && !widgetFilename.startsWith('$')) { try { // Parse the widget value which might be in format "subfolder/filename [type]" or just "filename" let filename = widgetFilename diff --git a/src/composables/maskeditor/useMaskEditorSaver.ts b/src/composables/maskeditor/useMaskEditorSaver.ts index 4af771399..8aa6bb0da 100644 --- a/src/composables/maskeditor/useMaskEditorSaver.ts +++ b/src/composables/maskeditor/useMaskEditorSaver.ts @@ -51,7 +51,7 @@ export function useMaskEditorSaver() { updateNodeWithServerReferences(sourceNode, outputData) - app.graph.setDirtyCanvas(true) + app.canvas.setDirty(true) } catch (error) { console.error('[MaskEditorSaver] Save failed:', error) throw error @@ -308,7 +308,7 @@ export function useMaskEditorSaver() { const mainImg = await loadImageFromUrl(dataUrl) node.imgs = [mainImg] - app.graph.setDirtyCanvas(true) + app.canvas.setDirty(true) } function updateNodeWithServerReferences( diff --git a/src/composables/maskeditor/usePanAndZoom.ts b/src/composables/maskeditor/usePanAndZoom.ts index 2aa833647..9a7085523 100644 --- a/src/composables/maskeditor/usePanAndZoom.ts +++ b/src/composables/maskeditor/usePanAndZoom.ts @@ -402,6 +402,19 @@ export function usePanAndZoom() { } ) + const addPenPointerId = (pointerId: number): void => { + if (!penPointerIdList.value.includes(pointerId)) { + penPointerIdList.value.push(pointerId) + } + } + + const removePenPointerId = (pointerId: number): void => { + const index = penPointerIdList.value.indexOf(pointerId) + if (index !== -1) { + penPointerIdList.value.splice(index, 1) + } + } + return { initializeCanvasPanZoom, handlePanStart, @@ -411,6 +424,8 @@ export function usePanAndZoom() { handleTouchEnd, updateCursorPosition, zoom, - invalidatePanZoom + invalidatePanZoom, + addPenPointerId, + removePenPointerId } } diff --git a/src/composables/maskeditor/useToolManager.ts b/src/composables/maskeditor/useToolManager.ts index e306f3dfb..9b1847699 100644 --- a/src/composables/maskeditor/useToolManager.ts +++ b/src/composables/maskeditor/useToolManager.ts @@ -22,10 +22,10 @@ export function useToolManager( const coordinateTransform = useCoordinateTransform() const brushDrawing = useBrushDrawing({ - useDominantAxis: app.extensionManager.setting.get( + useDominantAxis: app.extensionManager.setting.get( 'Comfy.MaskEditor.UseDominantAxis' ), - brushAdjustmentSpeed: app.extensionManager.setting.get( + brushAdjustmentSpeed: app.extensionManager.setting.get( 'Comfy.MaskEditor.BrushAdjustmentSpeed' ) }) @@ -114,6 +114,10 @@ export function useToolManager( event.preventDefault() if (event.pointerType === 'touch') return + if (event.pointerType === 'pen') { + panZoom.addPenPointerId(event.pointerId) + } + const isSpacePressed = keyboard.isKeyDown(' ') if (event.buttons === 4 || (event.buttons === 1 && isSpacePressed)) { @@ -207,6 +211,11 @@ export function useToolManager( const handlePointerUp = async (event: PointerEvent): Promise => { store.isPanning = false store.brushVisible = true + + if (event.pointerType === 'pen') { + panZoom.removePenPointerId(event.pointerId) + } + if (event.pointerType === 'touch') return updateCursor() store.isAdjustingBrush = false diff --git a/src/composables/node/useNodeBadge.ts b/src/composables/node/useNodeBadge.ts index 30c4487a3..91fa1115d 100644 --- a/src/composables/node/useNodeBadge.ts +++ b/src/composables/node/useNodeBadge.ts @@ -55,7 +55,7 @@ export const useNodeBadge = () => { showApiPricingBadge ], () => { - app.graph?.setDirtyCanvas(true, true) + app.canvas?.setDirty(true, true) } ) @@ -73,6 +73,14 @@ export const useNodeBadge = () => { onMounted(() => { const nodePricing = useNodePricing() + watch( + () => nodePricing.pricingRevision.value, + () => { + if (!showApiPricingBadge.value) return + app.canvas?.setDirty(true, true) + } + ) + extensionStore.registerExtension({ name: 'Comfy.NodeBadge', nodeCreated(node: LGraphNode) { @@ -111,17 +119,16 @@ export const useNodeBadge = () => { node.badges.push(() => badge.value) if (node.constructor.nodeData?.api_node && showApiPricingBadge.value) { - // Get the pricing function to determine if this node has dynamic pricing + // JSONata rules are dynamic if they depend on any widgets/inputs/input_groups const pricingConfig = nodePricing.getNodePricingConfig(node) const hasDynamicPricing = - typeof pricingConfig?.displayPrice === 'function' - - let creditsBadge - const createBadge = () => { - const price = nodePricing.getNodeDisplayPrice(node) - return priceBadge.getCreditsBadge(price) - } + !!pricingConfig && + ((pricingConfig.depends_on?.widgets?.length ?? 0) > 0 || + (pricingConfig.depends_on?.inputs?.length ?? 0) > 0 || + (pricingConfig.depends_on?.input_groups?.length ?? 0) > 0) + // Keep the existing widget-watch wiring ONLY to trigger redraws on widget change. + // (We no longer rely on it to hold the current badge value.) if (hasDynamicPricing) { // For dynamic pricing nodes, use computed that watches widget changes const relevantWidgetNames = nodePricing.getRelevantWidgetNames( @@ -133,13 +140,63 @@ export const useNodeBadge = () => { triggerCanvasRedraw: true }) - creditsBadge = computedWithWidgetWatch(createBadge) - } else { - // For static pricing nodes, use regular computed - creditsBadge = computed(createBadge) + // Ensure watchers are installed; ignore the returned value. + // (This call is what registers the widget listeners in most implementations.) + computedWithWidgetWatch(() => 0) + + // Hook into connection changes to trigger price recalculation + // This handles both connect and disconnect in VueNodes mode + const relevantInputs = pricingConfig?.depends_on?.inputs ?? [] + const inputGroupPrefixes = + pricingConfig?.depends_on?.input_groups ?? [] + const hasRelevantInputs = + relevantInputs.length > 0 || inputGroupPrefixes.length > 0 + + if (hasRelevantInputs) { + const originalOnConnectionsChange = node.onConnectionsChange + node.onConnectionsChange = function ( + type, + slotIndex, + isConnected, + link, + ioSlot + ) { + originalOnConnectionsChange?.call( + this, + type, + slotIndex, + isConnected, + link, + ioSlot + ) + // Only trigger if this input affects pricing + const inputName = ioSlot?.name + if (!inputName) return + const isRelevantInput = + relevantInputs.includes(inputName) || + inputGroupPrefixes.some((prefix) => + inputName.startsWith(prefix + '.') + ) + if (isRelevantInput) { + nodePricing.triggerPriceRecalculation(node) + } + } + } } - node.badges.push(() => creditsBadge.value) + let lastLabel = nodePricing.getNodeDisplayPrice(node) + let lastBadge = priceBadge.getCreditsBadge(lastLabel) + + const creditsBadgeGetter: () => LGraphBadge = () => { + const label = nodePricing.getNodeDisplayPrice(node) + if (label !== lastLabel) { + lastLabel = label + lastBadge = priceBadge.getCreditsBadge(label) + } + return lastBadge + } + + node.badges.push(creditsBadgeGetter) } }, init() { diff --git a/src/composables/node/useNodePricing.test.ts b/src/composables/node/useNodePricing.test.ts new file mode 100644 index 000000000..2f45258fb --- /dev/null +++ b/src/composables/node/useNodePricing.test.ts @@ -0,0 +1,857 @@ +import { describe, expect, it } from 'vitest' + +import { CREDITS_PER_USD, formatCredits } from '@/base/credits/comfyCredits' +import { useNodePricing } from '@/composables/node/useNodePricing' +import type { LGraphNode } from '@/lib/litegraph/src/litegraph' +import type { PriceBadge } from '@/schemas/nodeDefSchema' +import { createMockLGraphNode } from '@/utils/__tests__/litegraphTestUtils' + +// ----------------------------------------------------------------------------- +// Test Types +// ----------------------------------------------------------------------------- + +interface MockNodeWidget { + name: string + value: unknown + type: string +} + +interface MockNodeInput { + name: string + link: number | null +} + +interface MockNodeData { + name: string + api_node: boolean + price_badge?: PriceBadge +} + +// ----------------------------------------------------------------------------- +// Test Helpers +// ----------------------------------------------------------------------------- + +/** + * Determine if a number should display 1 decimal place. + * Shows decimal only when the first decimal digit is non-zero. + */ +const shouldShowDecimal = (value: number): boolean => { + const rounded = Math.round(value * 10) / 10 + return rounded % 1 !== 0 +} + +const creditValue = (usd: number): string => { + const rawCredits = usd * CREDITS_PER_USD + return formatCredits({ + value: rawCredits, + numberOptions: { + minimumFractionDigits: 0, + maximumFractionDigits: shouldShowDecimal(rawCredits) ? 1 : 0 + } + }) +} + +const creditsLabel = (usd: number, suffix = '/Run'): string => + `${creditValue(usd)} credits${suffix}` + +/** + * Create a mock node with price_badge for testing JSONata-based pricing. + */ +function createMockNodeWithPriceBadge( + nodeTypeName: string, + priceBadge: PriceBadge, + widgets: Array<{ name: string; value: unknown }> = [], + inputs: Array<{ name: string; connected?: boolean }> = [] +): LGraphNode { + const mockWidgets = widgets.map(({ name, value }) => ({ + name, + value, + type: 'combo' + })) + + const mockInputs: MockNodeInput[] = inputs.map(({ name, connected }) => ({ + name, + link: connected ? 1 : null + })) + + const baseNode = createMockLGraphNode() + return Object.assign(baseNode, { + widgets: mockWidgets, + inputs: mockInputs, + constructor: { + nodeData: { + name: nodeTypeName, + api_node: true, + price_badge: priceBadge + } + } + }) +} + +/** Helper to create a price badge with defaults */ +const priceBadge = ( + expr: string, + widgets: Array<{ name: string; type: string }> = [], + inputs: string[] = [], + inputGroups: string[] = [] +): PriceBadge => ({ + engine: 'jsonata', + expr, + depends_on: { widgets, inputs, input_groups: inputGroups } +}) + +/** Helper to create a mock node for edge case testing */ +function createMockNode( + nodeData: MockNodeData, + widgets: MockNodeWidget[] = [], + inputs: MockNodeInput[] = [] +): LGraphNode { + const baseNode = createMockLGraphNode() + return Object.assign(baseNode, { + widgets, + inputs, + constructor: { nodeData } + }) +} + +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + +describe('useNodePricing', () => { + describe('static expressions', () => { + it('should evaluate simple static USD price', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestStaticNode', + priceBadge('{"type":"usd","usd":0.05}') + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe(creditsLabel(0.05)) + }) + + it('should evaluate static text result', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestTextNode', + priceBadge('{"type":"text","text":"Free"}') + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe('Free') + }) + }) + + describe('widget value normalization', () => { + it('should handle INT widget as number', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestIntNode', + priceBadge('{"type":"usd","usd": widgets.count * 0.01}', [ + { name: 'count', type: 'INT' } + ]), + [{ name: 'count', value: 5 }] + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe(creditsLabel(0.05)) + }) + + it('should handle FLOAT widget as number', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestFloatNode', + priceBadge('{"type":"usd","usd": widgets.rate * 10}', [ + { name: 'rate', type: 'FLOAT' } + ]), + [{ name: 'rate', value: 0.05 }] + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe(creditsLabel(0.5)) + }) + + it('should handle COMBO widget with numeric value', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestComboNumericNode', + priceBadge('{"type":"usd","usd": widgets.duration * 0.07}', [ + { name: 'duration', type: 'COMBO' } + ]), + [{ name: 'duration', value: 5 }] + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe(creditsLabel(0.35)) + }) + + it('should handle COMBO widget with string value', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestComboStringNode', + priceBadge( + '(widgets.mode = "pro") ? {"type":"usd","usd":0.10} : {"type":"usd","usd":0.05}', + [{ name: 'mode', type: 'COMBO' }] + ), + [{ name: 'mode', value: 'Pro' }] // Should be lowercased to "pro" + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe(creditsLabel(0.1)) + }) + + it('should handle BOOLEAN widget', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestBooleanNode', + priceBadge('{"type":"usd","usd": widgets.premium ? 0.10 : 0.05}', [ + { name: 'premium', type: 'BOOLEAN' } + ]), + [{ name: 'premium', value: true }] + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe(creditsLabel(0.1)) + }) + + it('should handle STRING widget (lowercased)', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestStringNode', + priceBadge( + '$contains(widgets.model, "pro") ? {"type":"usd","usd":0.10} : {"type":"usd","usd":0.05}', + [{ name: 'model', type: 'STRING' }] + ), + [{ name: 'model', value: 'ProModel' }] // Should be lowercased to "promodel" + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe(creditsLabel(0.1)) + }) + }) + + describe('complex expressions', () => { + it('should handle lookup tables', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestLookupNode', + priceBadge( + `( + $rates := {"720p": 0.05, "1080p": 0.10}; + {"type":"usd","usd": $lookup($rates, widgets.resolution)} + )`, + [{ name: 'resolution', type: 'COMBO' }] + ), + [{ name: 'resolution', value: '1080p' }] + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe(creditsLabel(0.1)) + }) + + it('should handle multiple widgets', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestMultiWidgetNode', + priceBadge( + `( + $rate := (widgets.mode = "pro") ? 0.10 : 0.05; + {"type":"usd","usd": $rate * widgets.duration} + )`, + [ + { name: 'mode', type: 'COMBO' }, + { name: 'duration', type: 'INT' } + ] + ), + [ + { name: 'mode', value: 'pro' }, + { name: 'duration', value: 10 } + ] + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe(creditsLabel(1.0)) + }) + + it('should handle conditional pricing based on widget values', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestConditionalNode', + priceBadge( + `( + $mode := (widgets.resolution = "720p") ? "std" : "pro"; + $rates := {"std": 0.084, "pro": 0.112}; + {"type":"usd","usd": $lookup($rates, $mode) * widgets.duration} + )`, + [ + { name: 'resolution', type: 'COMBO' }, + { name: 'duration', type: 'COMBO' } + ] + ), + [ + { name: 'resolution', value: '1080p' }, + { name: 'duration', value: 5 } + ] + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe(creditsLabel(0.56)) + }) + }) + + describe('range and list results', () => { + it('should format range_usd result', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestRangeNode', + priceBadge('{"type":"range_usd","min_usd":0.05,"max_usd":0.10}') + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toMatch(/\d+\.?\d*-\d+\.?\d* credits\/Run/) + }) + + it('should format list_usd result', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestListNode', + priceBadge('{"type":"list_usd","usd":[0.05, 0.10, 0.15]}') + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toMatch(/\d+\.?\d*\/\d+\.?\d*\/\d+\.?\d* credits\/Run/) + }) + + it('should respect custom suffix in format options', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestSuffixNode', + priceBadge('{"type":"usd","usd":0.07,"format":{"suffix":"/second"}}') + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe(creditsLabel(0.07, '/second')) + }) + + it('should add approximate prefix when specified', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestApproximateNode', + priceBadge('{"type":"usd","usd":0.05,"format":{"approximate":true}}') + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toMatch(/^~\d+\.?\d* credits\/Run$/) + }) + + it('should add note suffix when specified', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestNoteNode', + priceBadge('{"type":"usd","usd":0.05,"format":{"note":"(estimated)"}}') + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toMatch(/credits\/Run \(estimated\)$/) + }) + + it('should combine approximate prefix and note suffix', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestCombinedFormatNode', + priceBadge( + '{"type":"usd","usd":0.05,"format":{"approximate":true,"note":"(beta)","suffix":"/image"}}' + ) + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toMatch(/^~\d+\.?\d* credits\/image \(beta\)$/) + }) + + it('should use custom separator for list_usd', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestListSeparatorNode', + priceBadge( + '{"type":"list_usd","usd":[0.05, 0.10],"format":{"separator":" or "}}' + ) + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toMatch(/\d+\.?\d* or \d+\.?\d* credits\/Run/) + }) + }) + + describe('input connectivity', () => { + it('should handle connected input check', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestInputNode', + priceBadge( + 'inputs.image.connected ? {"type":"usd","usd":0.10} : {"type":"usd","usd":0.05}', + [], + ['image'] + ), + [], + [{ name: 'image', connected: true }] + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe(creditsLabel(0.1)) + }) + + it('should handle disconnected input check', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestInputDisconnectedNode', + priceBadge( + 'inputs.image.connected ? {"type":"usd","usd":0.10} : {"type":"usd","usd":0.05}', + [], + ['image'] + ), + [], + [{ name: 'image', connected: false }] + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe(creditsLabel(0.05)) + }) + }) + + describe('edge cases', () => { + it('should return empty string for non-API nodes', () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNode({ + name: 'RegularNode', + api_node: false + }) + + const price = getNodeDisplayPrice(node) + expect(price).toBe('') + }) + + it('should return empty string for nodes without price_badge', () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNode({ + name: 'ApiNodeNoPricing', + api_node: true + }) + + const price = getNodeDisplayPrice(node) + expect(price).toBe('') + }) + + it('should handle null widget value gracefully', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestNullWidgetNode', + priceBadge( + '{"type":"usd","usd": (widgets.count != null) ? widgets.count * 0.01 : 0.05}', + [{ name: 'count', type: 'INT' }] + ), + [{ name: 'count', value: null }] + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe(creditsLabel(0.05)) + }) + + it('should handle missing widget gracefully', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestMissingWidgetNode', + priceBadge( + '{"type":"usd","usd": (widgets.count != null) ? widgets.count * 0.01 : 0.05}', + [{ name: 'count', type: 'INT' }] + ), + [] + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe(creditsLabel(0.05)) + }) + + it('should handle undefined widget value gracefully', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestUndefinedWidgetNode', + priceBadge( + '{"type":"usd","usd": (widgets.count != null) ? widgets.count * 0.01 : 0.05}', + [{ name: 'count', type: 'INT' }] + ), + [{ name: 'count', value: undefined }] + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe(creditsLabel(0.05)) + }) + }) + + describe('getNodePricingConfig', () => { + it('should return pricing config for nodes with price_badge', () => { + const { getNodePricingConfig } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestConfigNode', + priceBadge('{"type":"usd","usd":0.05}') + ) + + const config = getNodePricingConfig(node) + expect(config).toBeDefined() + expect(config?.engine).toBe('jsonata') + expect(config?.expr).toBe('{"type":"usd","usd":0.05}') + expect(config?.depends_on).toBeDefined() + }) + + it('should return undefined for nodes without price_badge', () => { + const { getNodePricingConfig } = useNodePricing() + const node = createMockNode({ + name: 'NoPricingNode', + api_node: true + }) + + const config = getNodePricingConfig(node) + expect(config).toBeUndefined() + }) + + it('should return undefined for non-API nodes', () => { + const { getNodePricingConfig } = useNodePricing() + const node = createMockNode({ + name: 'RegularNode', + api_node: false + }) + + const config = getNodePricingConfig(node) + expect(config).toBeUndefined() + }) + }) + + describe('getNodeRevisionRef', () => { + it('should return a ref for a node ID', () => { + const { getNodeRevisionRef } = useNodePricing() + const ref = getNodeRevisionRef('node-1') + + expect(ref).toBeDefined() + expect(ref.value).toBe(0) + }) + + it('should return the same ref for the same node ID', () => { + const { getNodeRevisionRef } = useNodePricing() + const ref1 = getNodeRevisionRef('node-same') + const ref2 = getNodeRevisionRef('node-same') + + expect(ref1).toBe(ref2) + }) + + it('should return different refs for different node IDs', () => { + const { getNodeRevisionRef } = useNodePricing() + const ref1 = getNodeRevisionRef('node-a') + const ref2 = getNodeRevisionRef('node-b') + + expect(ref1).not.toBe(ref2) + }) + + it('should handle both string and number node IDs', () => { + const { getNodeRevisionRef } = useNodePricing() + // Number ID gets stringified, so '123' and 123 should return the same ref + const refFromNumber = getNodeRevisionRef(123) + const refFromString = getNodeRevisionRef('123') + + expect(refFromNumber).toBe(refFromString) + }) + }) + + describe('triggerPriceRecalculation', () => { + it('should not throw for API nodes with price_badge', () => { + const { triggerPriceRecalculation } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestTriggerNode', + priceBadge('{"type":"usd","usd":0.05}') + ) + + expect(() => triggerPriceRecalculation(node)).not.toThrow() + }) + + it('should not throw for non-API nodes', () => { + const { triggerPriceRecalculation } = useNodePricing() + const node = createMockNode({ + name: 'RegularNode', + api_node: false + }) + + expect(() => triggerPriceRecalculation(node)).not.toThrow() + }) + }) + + describe('error handling', () => { + it('should return empty string for invalid JSONata expression', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestInvalidExprNode', + // Invalid JSONata syntax (unclosed parenthesis) + priceBadge('{"type":"usd","usd": (widgets.count * 0.01') + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + // Should not crash, just return empty + expect(price).toBe('') + }) + + it('should return empty string for expression that throws at runtime', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestRuntimeErrorNode', + // Expression that will fail at runtime (calling function on undefined) + priceBadge('$lookup(undefined, "key")') + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe('') + }) + + it('should return empty string for invalid PricingResult type', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestInvalidResultTypeNode', + // Returns object with invalid type field + priceBadge('{"type":"invalid_type","value":123}') + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe('') + }) + + it('should return empty string for PricingResult missing type field', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestMissingTypeNode', + // Returns object without type field + priceBadge('{"usd":0.05}') + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe('') + }) + + it('should return empty string for non-object result', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestNonObjectNode', + // Returns a plain number instead of PricingResult object + priceBadge('0.05') + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe('') + }) + + it('should return empty string for null result', async () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNodeWithPriceBadge( + 'TestNullResultNode', + priceBadge('null') + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe('') + }) + }) + + describe('input_groups connectivity', () => { + it('should count connected inputs in a group', async () => { + const { getNodeDisplayPrice } = useNodePricing() + + // Create a node with autogrow-style inputs (group.input1, group.input2, etc.) + const node = createMockNode( + { + name: 'TestInputGroupNode', + api_node: true, + price_badge: { + engine: 'jsonata', + expr: '{"type":"usd","usd": inputGroups.videos * 0.05}', + depends_on: { + widgets: [], + inputs: [], + input_groups: ['videos'] + } + } + }, + [], + [ + { name: 'videos.clip1', link: 1 }, // connected + { name: 'videos.clip2', link: 2 }, // connected + { name: 'videos.clip3', link: null }, // disconnected + { name: 'other_input', link: 3 } // connected but not in group + ] + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + // 2 connected inputs in 'videos' group * 0.05 = 0.10 + expect(price).toBe(creditsLabel(0.1)) + }) + }) + + describe('decimal formatting', () => { + describe('shouldShowDecimal helper', () => { + it('should return true when first decimal digit is non-zero', () => { + expect(shouldShowDecimal(10.5)).toBe(true) + expect(shouldShowDecimal(10.1)).toBe(true) + expect(shouldShowDecimal(10.9)).toBe(true) + expect(shouldShowDecimal(1.5)).toBe(true) + }) + + it('should return false for whole numbers', () => { + expect(shouldShowDecimal(10)).toBe(false) + expect(shouldShowDecimal(10.0)).toBe(false) + expect(shouldShowDecimal(1)).toBe(false) + expect(shouldShowDecimal(100)).toBe(false) + }) + + it('should return false when decimal rounds to zero', () => { + // 10.04 rounds to 10.0, so no decimal shown + expect(shouldShowDecimal(10.04)).toBe(false) + expect(shouldShowDecimal(10.049)).toBe(false) + }) + + it('should return true when decimal rounds to non-zero', () => { + // 10.05 rounds to 10.1, so decimal shown + expect(shouldShowDecimal(10.05)).toBe(true) + expect(shouldShowDecimal(10.06)).toBe(true) + // 10.45 rounds to 10.5 + expect(shouldShowDecimal(10.45)).toBe(true) + }) + }) + + describe('credit value formatting', () => { + it('should show decimal for USD values that result in fractional credits', () => { + // $0.05 * 211 = 10.55 credits → "10.6" + const value1 = creditValue(0.05) + expect(value1).toBe('10.6') + + // $0.10 * 211 = 21.1 credits → "21.1" + const value2 = creditValue(0.1) + expect(value2).toBe('21.1') + }) + + it('should not show decimal for USD values that result in whole credits', () => { + // $1.00 * 211 = 211 credits → "211" + const value = creditValue(1.0) + expect(value).toBe('211') + }) + + it('should not show decimal when credits round to whole number', () => { + // Find a USD value that results in credits close to a whole number + // $0.0473933... * 211 ≈ 10.0 credits + // Let's use a value that gives us ~10.02 credits which rounds to 10.0 + const usd = 10.02 / CREDITS_PER_USD // ~0.0475 USD → ~10.02 credits + const value = creditValue(usd) + expect(value).toBe('10') + }) + }) + + describe('integration with pricing display', () => { + it('should display decimal in badge for fractional credits', async () => { + const { getNodeDisplayPrice } = useNodePricing() + // $0.05 * 211 = 10.55 credits → "10.6 credits/Run" + const node = createMockNodeWithPriceBadge( + 'TestDecimalNode', + priceBadge('{"type":"usd","usd":0.05}') + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe('10.6 credits/Run') + }) + + it('should not display decimal in badge for whole credits', async () => { + const { getNodeDisplayPrice } = useNodePricing() + // $1.00 * 211 = 211 credits → "211 credits/Run" + const node = createMockNodeWithPriceBadge( + 'TestWholeCreditsNode', + priceBadge('{"type":"usd","usd":1.00}') + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe('211 credits/Run') + }) + + it('should handle range with mixed decimal display', async () => { + const { getNodeDisplayPrice } = useNodePricing() + // min: $0.05 * 211 = 10.55 → 10.6 + // max: $1.00 * 211 = 211 → 211 + const node = createMockNodeWithPriceBadge( + 'TestMixedRangeNode', + priceBadge('{"type":"range_usd","min_usd":0.05,"max_usd":1.00}') + ) + + getNodeDisplayPrice(node) + await new Promise((r) => setTimeout(r, 50)) + const price = getNodeDisplayPrice(node) + expect(price).toBe('10.6-211 credits/Run') + }) + }) + }) +}) diff --git a/src/composables/node/useNodePricing.ts b/src/composables/node/useNodePricing.ts index 8e9811e37..5d08e5794 100644 --- a/src/composables/node/useNodePricing.ts +++ b/src/composables/node/useNodePricing.ts @@ -1,1955 +1,654 @@ -import type { INodeInputSlot, LGraphNode } from '@/lib/litegraph/src/litegraph' -import type { IComboWidget } from '@/lib/litegraph/src/types/widgets' +// JSONata-based pricing badge evaluation for API nodes. +// +// Pricing declarations are read from ComfyUI node definitions (price_badge field). +// The Frontend evaluates these declarations locally using a JSONata engine. +// +// JSONata v2.x NOTE: +// - jsonata(expression).evaluate(input) returns a Promise in JSONata 2.x. +// - Therefore, pricing evaluation is async. This file implements: +// - sync getter (returns cached label / last-known label), +// - async evaluation + cache, +// - reactive tick to update UI when async evaluation completes. + +import { readonly, ref } from 'vue' +import type { Ref } from 'vue' +import { CREDITS_PER_USD, formatCredits } from '@/base/credits/comfyCredits' +import { LiteGraph } from '@/lib/litegraph/src/litegraph' +import type { LGraphNode } from '@/lib/litegraph/src/litegraph' +import type { INodeInputSlot } from '@/lib/litegraph/src/interfaces' +import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets' +import type { + ComfyNodeDef, + PriceBadge, + WidgetDependency +} from '@/schemas/nodeDefSchema' +import { useNodeDefStore } from '@/stores/nodeDefStore' +import type { Expression } from 'jsonata' +import jsonata from 'jsonata' /** - * Function that calculates dynamic pricing based on node widget values + * Determine if a number should display 1 decimal place. + * Shows decimal only when the first decimal digit is non-zero. */ -type PricingFunction = (node: LGraphNode) => string +const shouldShowDecimal = (value: number): boolean => { + const rounded = Math.round(value * 10) / 10 + return rounded % 1 !== 0 +} -/** - * Safely executes a pricing function with error handling - * Returns null if the function throws an error, allowing the node to still render - */ -function safePricingExecution( - fn: PricingFunction, - node: LGraphNode, - fallback: string = '' -): string { - try { - return fn(node) - } catch (error) { - // Log error in development but don't throw to avoid breaking node rendering - if (process.env.NODE_ENV === 'development') { - console.warn( - 'Pricing calculation failed for node:', - node.constructor?.nodeData?.name, - error - ) +const getNumberOptions = (credits: number): Intl.NumberFormatOptions => ({ + minimumFractionDigits: 0, + maximumFractionDigits: shouldShowDecimal(credits) ? 1 : 0 +}) + +type CreditFormatOptions = { + suffix?: string + note?: string + approximate?: boolean + separator?: string +} + +const formatCreditsValue = (usd: number): string => { + // Use raw credits value (before rounding) to determine decimal display + const rawCredits = usd * CREDITS_PER_USD + return formatCredits({ + value: rawCredits, + numberOptions: getNumberOptions(rawCredits) + }) +} + +const makePrefix = (approximate?: boolean) => (approximate ? '~' : '') + +const makeSuffix = (suffix?: string) => suffix ?? '/Run' + +const appendNote = (note?: string) => (note ? ` ${note}` : '') + +const formatCreditsLabel = ( + usd: number, + { suffix, note, approximate }: CreditFormatOptions = {} +): string => + `${makePrefix(approximate)}${formatCreditsValue(usd)} credits${makeSuffix(suffix)}${appendNote(note)}` + +const formatCreditsRangeLabel = ( + minUsd: number, + maxUsd: number, + { suffix, note, approximate }: CreditFormatOptions = {} +): string => { + const min = formatCreditsValue(minUsd) + const max = formatCreditsValue(maxUsd) + const rangeValue = min === max ? min : `${min}-${max}` + return `${makePrefix(approximate)}${rangeValue} credits${makeSuffix(suffix)}${appendNote(note)}` +} + +const formatCreditsListLabel = ( + usdValues: number[], + { suffix, note, approximate, separator }: CreditFormatOptions = {} +): string => { + const parts = usdValues.map((value) => formatCreditsValue(value)) + const value = parts.join(separator ?? '/') + return `${makePrefix(approximate)}${value} credits${makeSuffix(suffix)}${appendNote(note)}` +} + +// ----------------------------- +// JSONata pricing types +// ----------------------------- +type PricingResult = + | { type: 'text'; text: string } + | { type: 'usd'; usd: number; format?: CreditFormatOptions } + | { + type: 'range_usd' + min_usd: number + max_usd: number + format?: CreditFormatOptions } - return fallback - } -} + | { type: 'list_usd'; usd: number[]; format?: CreditFormatOptions } -/** - * Helper function to calculate Runway duration-based pricing - * @param node - The LiteGraph node - * @returns Formatted price string - */ -const calculateRunwayDurationPrice = (node: LGraphNode): string => { - const durationWidget = node.widgets?.find( - (w) => w.name === 'duration' - ) as IComboWidget +const PRICING_RESULT_TYPES = ['text', 'usd', 'range_usd', 'list_usd'] as const - if (!durationWidget) return '$0.05/second' - - const duration = Number(durationWidget.value) - // If duration is 0 or NaN, don't fall back to 5 seconds - just use 0 - const validDuration = isNaN(duration) ? 5 : duration - const cost = (0.05 * validDuration).toFixed(2) - return `$${cost}/Run` -} - -const pixversePricingCalculator = (node: LGraphNode): string => { - const durationWidget = node.widgets?.find( - (w) => w.name === 'duration_seconds' - ) as IComboWidget - const qualityWidget = node.widgets?.find( - (w) => w.name === 'quality' - ) as IComboWidget - const motionModeWidget = node.widgets?.find( - (w) => w.name === 'motion_mode' - ) as IComboWidget - - if (!durationWidget || !qualityWidget) { - return '$0.45-1.2/Run (varies with duration, quality & motion mode)' - } - - const duration = String(durationWidget.value) - const quality = String(qualityWidget.value) - const motionMode = String(motionModeWidget?.value) - - // Basic pricing based on duration and quality - if (duration.includes('5')) { - if (quality.includes('1080p')) return '$1.2/Run' - if (quality.includes('720p') && motionMode?.includes('fast')) - return '$1.2/Run' - if (quality.includes('720p') && motionMode?.includes('normal')) - return '$0.6/Run' - if (quality.includes('540p') && motionMode?.includes('fast')) - return '$0.9/Run' - if (quality.includes('540p') && motionMode?.includes('normal')) - return '$0.45/Run' - if (quality.includes('360p') && motionMode?.includes('fast')) - return '$0.9/Run' - if (quality.includes('360p') && motionMode?.includes('normal')) - return '$0.45/Run' - if (quality.includes('720p') && motionMode?.includes('fast')) - return '$1.2/Run' - } else if (duration.includes('8')) { - if (quality.includes('720p') && motionMode?.includes('normal')) - return '$1.2/Run' - if (quality.includes('540p') && motionMode?.includes('normal')) - return '$0.9/Run' - if (quality.includes('540p') && motionMode?.includes('fast')) - return '$1.2/Run' - if (quality.includes('360p') && motionMode?.includes('normal')) - return '$0.9/Run' - if (quality.includes('360p') && motionMode?.includes('fast')) - return '$1.2/Run' - if (quality.includes('1080p') && motionMode?.includes('normal')) - return '$1.2/Run' - if (quality.includes('1080p') && motionMode?.includes('fast')) - return '$1.2/Run' - if (quality.includes('720p') && motionMode?.includes('normal')) - return '$1.2/Run' - if (quality.includes('720p') && motionMode?.includes('fast')) - return '$1.2/Run' - } - - return '$0.9/Run' -} - -const byteDanceVideoPricingCalculator = (node: LGraphNode): string => { - const modelWidget = node.widgets?.find( - (w) => w.name === 'model' - ) as IComboWidget - const durationWidget = node.widgets?.find( - (w) => w.name === 'duration' - ) as IComboWidget - const resolutionWidget = node.widgets?.find( - (w) => w.name === 'resolution' - ) as IComboWidget - - if (!modelWidget || !durationWidget || !resolutionWidget) return 'Token-based' - - const model = String(modelWidget.value).toLowerCase() - const resolution = String(resolutionWidget.value).toLowerCase() - const seconds = parseFloat(String(durationWidget.value)) - const priceByModel: Record> = { - 'seedance-1-0-pro': { - '480p': [0.23, 0.24], - '720p': [0.51, 0.56], - '1080p': [1.18, 1.22] - }, - 'seedance-1-0-lite': { - '480p': [0.17, 0.18], - '720p': [0.37, 0.41], - '1080p': [0.85, 0.88] - } - } - - const modelKey = model.includes('seedance-1-0-pro') - ? 'seedance-1-0-pro' - : model.includes('seedance-1-0-lite') - ? 'seedance-1-0-lite' - : '' - - const resKey = resolution.includes('1080') - ? '1080p' - : resolution.includes('720') - ? '720p' - : resolution.includes('480') - ? '480p' - : '' - - const baseRange = - modelKey && resKey ? priceByModel[modelKey]?.[resKey] : undefined - if (!baseRange) return 'Token-based' - - const [min10s, max10s] = baseRange - const scale = seconds / 10 - const minCost = min10s * scale - const maxCost = max10s * scale - - const minStr = `$${minCost.toFixed(2)}/Run` - const maxStr = `$${maxCost.toFixed(2)}/Run` - - return minStr === maxStr - ? minStr - : `$${minCost.toFixed(2)}-$${maxCost.toFixed(2)}/Run` -} - -const ltxvPricingCalculator = (node: LGraphNode): string => { - const modelWidget = node.widgets?.find( - (w) => w.name === 'model' - ) as IComboWidget - const durationWidget = node.widgets?.find( - (w) => w.name === 'duration' - ) as IComboWidget - const resolutionWidget = node.widgets?.find( - (w) => w.name === 'resolution' - ) as IComboWidget - - const fallback = '$0.04-0.24/second' - if (!modelWidget || !durationWidget || !resolutionWidget) return fallback - - const model = String(modelWidget.value).toLowerCase() - const resolution = String(resolutionWidget.value).toLowerCase() - const seconds = parseFloat(String(durationWidget.value)) - const priceByModel: Record> = { - 'ltx-2 (pro)': { - '1920x1080': 0.06, - '2560x1440': 0.12, - '3840x2160': 0.24 - }, - 'ltx-2 (fast)': { - '1920x1080': 0.04, - '2560x1440': 0.08, - '3840x2160': 0.16 - } - } - - const modelTable = priceByModel[model] - if (!modelTable) return fallback - - const pps = modelTable[resolution] - if (!pps) return fallback - - const cost = (pps * seconds).toFixed(2) - return `$${cost}/Run` -} - -// ---- constants ---- -const SORA_SIZES = { - BASIC: new Set(['720x1280', '1280x720']), - PRO: new Set(['1024x1792', '1792x1024']) -} -const ALL_SIZES = new Set([...SORA_SIZES.BASIC, ...SORA_SIZES.PRO]) - -// ---- sora-2 pricing helpers ---- -function validateSora2Selection( - modelRaw: string, - duration: number, - sizeRaw: string -): string | undefined { - const model = modelRaw?.toLowerCase() ?? '' - const size = sizeRaw?.toLowerCase() ?? '' - - if (!duration || Number.isNaN(duration)) return 'Set duration (4s / 8s / 12s)' - if (!size) return 'Set size (720x1280, 1280x720, 1024x1792, 1792x1024)' - if (!ALL_SIZES.has(size)) - return 'Invalid size. Must be 720x1280, 1280x720, 1024x1792, or 1792x1024.' - - if (model.includes('sora-2-pro')) return undefined - - if (model.includes('sora-2') && !SORA_SIZES.BASIC.has(size)) - return 'sora-2 supports only 720x1280 or 1280x720' - - if (!model.includes('sora-2')) return 'Unsupported model' - - return undefined -} - -function perSecForSora2(modelRaw: string, sizeRaw: string): number { - const model = modelRaw?.toLowerCase() ?? '' - const size = sizeRaw?.toLowerCase() ?? '' - - if (model.includes('sora-2-pro')) { - return SORA_SIZES.PRO.has(size) ? 0.5 : 0.3 - } - if (model.includes('sora-2')) return 0.1 - - return SORA_SIZES.PRO.has(size) ? 0.5 : 0.1 -} - -function formatRunPrice(perSec: number, duration: number) { - return `$${(perSec * duration).toFixed(2)}/Run` -} - -// ---- pricing calculator ---- -const sora2PricingCalculator: PricingFunction = (node: LGraphNode): string => { - const getWidgetValue = (name: string) => - String(node.widgets?.find((w) => w.name === name)?.value ?? '') - - const model = getWidgetValue('model') - const size = getWidgetValue('size') - const duration = Number( - node.widgets?.find((w) => ['duration', 'duration_s'].includes(w.name)) - ?.value +/** Type guard to validate that a value is a PricingResult. */ +const isPricingResult = (value: unknown): value is PricingResult => + typeof value === 'object' && + value !== null && + 'type' in value && + typeof (value as { type: unknown }).type === 'string' && + PRICING_RESULT_TYPES.includes( + (value as { type: string }).type as (typeof PRICING_RESULT_TYPES)[number] ) - if (!model || !size || !duration) return 'Set model, duration & size' +/** + * Widget values are normalized based on their declared type: + * - INT/FLOAT → number (or null if not parseable) + * - BOOLEAN → boolean (or null if not parseable) + * - STRING/COMBO/other → string (lowercased, trimmed) + */ +type NormalizedWidgetValue = string | number | boolean | null - const validationError = validateSora2Selection(model, duration, size) - if (validationError) return validationError +type JsonataPricingRule = { + engine: 'jsonata' + depends_on: { + widgets: WidgetDependency[] + inputs: string[] + input_groups: string[] + } + expr: string + result_defaults?: CreditFormatOptions +} - const perSec = perSecForSora2(model, size) - return formatRunPrice(perSec, duration) +type CompiledJsonataPricingRule = JsonataPricingRule & { + _compiled: Expression | null } /** - * Static pricing data for API nodes, now supporting both strings and functions + * Shape of nodeData attached to LGraphNode constructor for API nodes. + * Uses Pick from schema type to ensure consistency. */ -const apiNodeCosts: Record = - { - FluxProCannyNode: { - displayPrice: '$0.05/Run' - }, - FluxProDepthNode: { - displayPrice: '$0.05/Run' - }, - FluxProExpandNode: { - displayPrice: '$0.05/Run' - }, - FluxProFillNode: { - displayPrice: '$0.05/Run' - }, - FluxProUltraImageNode: { - displayPrice: '$0.06/Run' - }, - FluxProKontextProNode: { - displayPrice: '$0.04/Run' - }, - FluxProKontextMaxNode: { - displayPrice: '$0.08/Run' - }, - Flux2ProImageNode: { - displayPrice: (node: LGraphNode): string => { - const widthW = node.widgets?.find( - (w) => w.name === 'width' - ) as IComboWidget - const heightW = node.widgets?.find( - (w) => w.name === 'height' - ) as IComboWidget - - const w = Number(widthW?.value) - const h = Number(heightW?.value) - if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) { - // global min/max for this node given schema bounds (1MP..4MP output) - return '$0.03–$0.15/Run' - } - - // Is the 'images' input connected? - const imagesInput = node.inputs?.find( - (i) => i.name === 'images' - ) as INodeInputSlot - const hasRefs = - typeof imagesInput?.link !== 'undefined' && imagesInput.link != null - - // Output cost: ceil((w*h)/MP); first MP $0.03, each additional $0.015 - const MP = 1024 * 1024 - const outMP = Math.max(1, Math.floor((w * h + MP - 1) / MP)) - const outputCost = 0.03 + 0.015 * Math.max(outMP - 1, 0) - - if (hasRefs) { - // Unknown ref count/size on the frontend: - // min extra is $0.015, max extra is $0.120 (8 MP cap / 8 refs) - const minTotal = outputCost + 0.015 - const maxTotal = outputCost + 0.12 - return `~$${parseFloat(minTotal.toFixed(3))}–$${parseFloat(maxTotal.toFixed(3))}/Run` - } - - // Precise text-to-image price - return `$${parseFloat(outputCost.toFixed(3))}/Run` - } - }, - OpenAIVideoSora2: { - displayPrice: sora2PricingCalculator - }, - IdeogramV1: { - displayPrice: (node: LGraphNode): string => { - const numImagesWidget = node.widgets?.find( - (w) => w.name === 'num_images' - ) as IComboWidget - const turboWidget = node.widgets?.find( - (w) => w.name === 'turbo' - ) as IComboWidget - - if (!numImagesWidget) return '$0.02-0.06 x num_images/Run' - - const numImages = Number(numImagesWidget.value) || 1 - const turbo = String(turboWidget?.value).toLowerCase() === 'true' - const basePrice = turbo ? 0.02 : 0.06 - const cost = (basePrice * numImages).toFixed(2) - return `$${cost}/Run` - } - }, - IdeogramV2: { - displayPrice: (node: LGraphNode): string => { - const numImagesWidget = node.widgets?.find( - (w) => w.name === 'num_images' - ) as IComboWidget - const turboWidget = node.widgets?.find( - (w) => w.name === 'turbo' - ) as IComboWidget - - if (!numImagesWidget) return '$0.05-0.08 x num_images/Run' - - const numImages = Number(numImagesWidget.value) || 1 - const turbo = String(turboWidget?.value).toLowerCase() === 'true' - const basePrice = turbo ? 0.05 : 0.08 - const cost = (basePrice * numImages).toFixed(2) - return `$${cost}/Run` - } - }, - IdeogramV3: { - displayPrice: (node: LGraphNode): string => { - const renderingSpeedWidget = node.widgets?.find( - (w) => w.name === 'rendering_speed' - ) as IComboWidget - const numImagesWidget = node.widgets?.find( - (w) => w.name === 'num_images' - ) as IComboWidget - const characterInput = node.inputs?.find( - (i) => i.name === 'character_image' - ) as INodeInputSlot - const hasCharacter = - typeof characterInput?.link !== 'undefined' && - characterInput.link != null - - if (!renderingSpeedWidget) - return '$0.03-0.08 x num_images/Run (varies with rendering speed & num_images)' - - const numImages = Number(numImagesWidget?.value) || 1 - let basePrice = 0.06 // default balanced price - - const renderingSpeed = String(renderingSpeedWidget.value) - if (renderingSpeed.toLowerCase().includes('quality')) { - if (hasCharacter) { - basePrice = 0.2 - } else { - basePrice = 0.09 - } - } else if (renderingSpeed.toLowerCase().includes('default')) { - if (hasCharacter) { - basePrice = 0.15 - } else { - basePrice = 0.06 - } - } else if (renderingSpeed.toLowerCase().includes('turbo')) { - if (hasCharacter) { - basePrice = 0.1 - } else { - basePrice = 0.03 - } - } - - const totalCost = (basePrice * numImages).toFixed(2) - return `$${totalCost}/Run` - } - }, - KlingCameraControlI2VNode: { - displayPrice: '$0.49/Run' - }, - KlingCameraControlT2VNode: { - displayPrice: '$0.14/Run' - }, - KlingDualCharacterVideoEffectNode: { - displayPrice: (node: LGraphNode): string => { - const modeWidget = node.widgets?.find( - (w) => w.name === 'mode' - ) as IComboWidget - const modelWidget = node.widgets?.find( - (w) => w.name === 'model_name' - ) as IComboWidget - const durationWidget = node.widgets?.find( - (w) => w.name === 'duration' - ) as IComboWidget - if (!modeWidget || !modelWidget || !durationWidget) - return '$0.14-2.80/Run (varies with model, mode & duration)' - - const modeValue = String(modeWidget.value) - const durationValue = String(durationWidget.value) - const modelValue = String(modelWidget.value) - - // Same pricing matrix as KlingTextToVideoNode - if (modelValue.includes('v1-6') || modelValue.includes('v1-5')) { - if (modeValue.includes('pro')) { - return durationValue.includes('10') ? '$0.98/Run' : '$0.49/Run' - } else { - return durationValue.includes('10') ? '$0.56/Run' : '$0.28/Run' - } - } else if (modelValue.includes('v1')) { - if (modeValue.includes('pro')) { - return durationValue.includes('10') ? '$0.98/Run' : '$0.49/Run' - } else { - return durationValue.includes('10') ? '$0.28/Run' : '$0.14/Run' - } - } - - return '$0.14/Run' - } - }, - KlingImage2VideoNode: { - displayPrice: (node: LGraphNode): string => { - const modeWidget = node.widgets?.find( - (w) => w.name === 'mode' - ) as IComboWidget - const modelWidget = node.widgets?.find( - (w) => w.name === 'model_name' - ) as IComboWidget - const durationWidget = node.widgets?.find( - (w) => w.name === 'duration' - ) as IComboWidget - - if (!modeWidget) { - if (!modelWidget) - return '$0.14-2.80/Run (varies with model, mode & duration)' - - const modelValue = String(modelWidget.value) - if ( - modelValue.includes('v2-1-master') || - modelValue.includes('v2-master') - ) { - return '$1.40/Run' - } else if ( - modelValue.includes('v1-6') || - modelValue.includes('v1-5') - ) { - return '$0.28/Run' - } - return '$0.14/Run' - } - - const modeValue = String(modeWidget.value) - const durationValue = String(durationWidget.value) - const modelValue = String(modelWidget.value) - - // Same pricing matrix as KlingTextToVideoNode - if (modelValue.includes('v2-5-turbo')) { - if (durationValue.includes('10')) { - return '$0.70/Run' - } - return '$0.35/Run' // 5s default - } else if ( - modelValue.includes('v2-1-master') || - modelValue.includes('v2-master') - ) { - if (durationValue.includes('10')) { - return '$2.80/Run' - } - return '$1.40/Run' // 5s default - } else if ( - modelValue.includes('v2-1') || - modelValue.includes('v1-6') || - modelValue.includes('v1-5') - ) { - if (modeValue.includes('pro')) { - return durationValue.includes('10') ? '$0.98/Run' : '$0.49/Run' - } else { - return durationValue.includes('10') ? '$0.56/Run' : '$0.28/Run' - } - } else if (modelValue.includes('v1')) { - if (modeValue.includes('pro')) { - return durationValue.includes('10') ? '$0.98/Run' : '$0.49/Run' - } else { - return durationValue.includes('10') ? '$0.28/Run' : '$0.14/Run' - } - } - - return '$0.14/Run' - } - }, - KlingImageGenerationNode: { - displayPrice: (node: LGraphNode): string => { - const imageInputWidget = node.inputs?.find((i) => i.name === 'image') - // If link is not null => image is connected => modality is image to image - const modality = imageInputWidget?.link - ? 'image to image' - : 'text to image' - const modelWidget = node.widgets?.find( - (w) => w.name === 'model_name' - ) as IComboWidget - const nWidget = node.widgets?.find( - (w) => w.name === 'n' - ) as IComboWidget - - if (!modelWidget) - return '$0.0035-0.028 x n/Run (varies with modality & model)' - - const model = String(modelWidget.value) - const n = Number(nWidget?.value) || 1 - let basePrice = 0.014 // default - - if (modality.includes('text to image')) { - if (model.includes('kling-v1-5') || model.includes('kling-v2')) { - basePrice = 0.014 - } else if (model.includes('kling-v1')) { - basePrice = 0.0035 - } - } else if (modality.includes('image to image')) { - if (model.includes('kling-v1-5')) { - basePrice = 0.028 - } else if (model.includes('kling-v1')) { - basePrice = 0.0035 - } - } - - const totalCost = (basePrice * n).toFixed(4) - return `$${totalCost}/Run` - } - }, - KlingLipSyncAudioToVideoNode: { - displayPrice: '~$0.10/Run' - }, - KlingLipSyncTextToVideoNode: { - displayPrice: '~$0.10/Run' - }, - KlingSingleImageVideoEffectNode: { - displayPrice: (node: LGraphNode): string => { - const effectSceneWidget = node.widgets?.find( - (w) => w.name === 'effect_scene' - ) as IComboWidget - - if (!effectSceneWidget) - return '$0.28-0.49/Run (varies with effect scene)' - - const effectScene = String(effectSceneWidget.value) - if ( - effectScene.includes('fuzzyfuzzy') || - effectScene.includes('squish') - ) { - return '$0.28/Run' - } else if (effectScene.includes('dizzydizzy')) { - return '$0.49/Run' - } else if (effectScene.includes('bloombloom')) { - return '$0.49/Run' - } else if (effectScene.includes('expansion')) { - return '$0.28/Run' - } - - return '$0.28/Run' - } - }, - KlingStartEndFrameNode: { - displayPrice: (node: LGraphNode): string => { - // Same pricing as KlingTextToVideoNode per CSV ("Same as text to video") - const modeWidget = node.widgets?.find( - (w) => w.name === 'mode' - ) as IComboWidget - if (!modeWidget) - return '$0.14-2.80/Run (varies with model, mode & duration)' - - const modeValue = String(modeWidget.value) - - // Same pricing matrix as KlingTextToVideoNode - if (modeValue.includes('v2-1')) { - if (modeValue.includes('10s')) { - return '$0.98/Run' // pro, 10s - } - return '$0.49/Run' // pro, 5s default - } else if (modeValue.includes('v2-master')) { - if (modeValue.includes('10s')) { - return '$2.80/Run' - } - return '$1.40/Run' // 5s default - } else if (modeValue.includes('v1-6')) { - if (modeValue.includes('pro')) { - return modeValue.includes('10s') ? '$0.98/Run' : '$0.49/Run' - } else { - return modeValue.includes('10s') ? '$0.56/Run' : '$0.28/Run' - } - } else if (modeValue.includes('v1')) { - if (modeValue.includes('pro')) { - return modeValue.includes('10s') ? '$0.98/Run' : '$0.49/Run' - } else { - return modeValue.includes('10s') ? '$0.28/Run' : '$0.14/Run' - } - } - - return '$0.14/Run' - } - }, - KlingTextToVideoNode: { - displayPrice: (node: LGraphNode): string => { - const modeWidget = node.widgets?.find( - (w) => w.name === 'mode' - ) as IComboWidget - if (!modeWidget) - return '$0.14-2.80/Run (varies with model, mode & duration)' - - const modeValue = String(modeWidget.value) - - // Pricing matrix from CSV data based on mode string content - if (modeValue.includes('v2-5-turbo')) { - if (modeValue.includes('10')) { - return '$0.70/Run' - } - return '$0.35/Run' // 5s default - } else if (modeValue.includes('v2-1-master')) { - if (modeValue.includes('10s')) { - return '$2.80/Run' // price is the same as for v2-master model - } - return '$1.40/Run' // price is the same as for v2-master model - } else if (modeValue.includes('v2-master')) { - if (modeValue.includes('10s')) { - return '$2.80/Run' - } - return '$1.40/Run' // 5s default - } else if (modeValue.includes('v1-6')) { - if (modeValue.includes('pro')) { - return modeValue.includes('10s') ? '$0.98/Run' : '$0.49/Run' - } else { - return modeValue.includes('10s') ? '$0.56/Run' : '$0.28/Run' - } - } else if (modeValue.includes('v1')) { - if (modeValue.includes('pro')) { - return modeValue.includes('10s') ? '$0.98/Run' : '$0.49/Run' - } else { - return modeValue.includes('10s') ? '$0.28/Run' : '$0.14/Run' - } - } - - return '$0.14/Run' - } - }, - KlingVideoExtendNode: { - displayPrice: '$0.28/Run' - }, - KlingVirtualTryOnNode: { - displayPrice: '$0.07/Run' - }, - LumaImageToVideoNode: { - displayPrice: (node: LGraphNode): string => { - // Same pricing as LumaVideoNode per CSV - const modelWidget = node.widgets?.find( - (w) => w.name === 'model' - ) as IComboWidget - const resolutionWidget = node.widgets?.find( - (w) => w.name === 'resolution' - ) as IComboWidget - const durationWidget = node.widgets?.find( - (w) => w.name === 'duration' - ) as IComboWidget - - if (!modelWidget || !resolutionWidget || !durationWidget) { - return '$0.14-11.47/Run (varies with model, resolution & duration)' - } - - const model = String(modelWidget.value) - const resolution = String(resolutionWidget.value).toLowerCase() - const duration = String(durationWidget.value) - - if (model.includes('ray-flash-2')) { - if (duration.includes('5s')) { - if (resolution.includes('4k')) return '$2.19/Run' - if (resolution.includes('1080p')) return '$0.55/Run' - if (resolution.includes('720p')) return '$0.24/Run' - if (resolution.includes('540p')) return '$0.14/Run' - } else if (duration.includes('9s')) { - if (resolution.includes('4k')) return '$3.95/Run' - if (resolution.includes('1080p')) return '$0.99/Run' - if (resolution.includes('720p')) return '$0.43/Run' - if (resolution.includes('540p')) return '$0.252/Run' - } - } else if (model.includes('ray-2')) { - if (duration.includes('5s')) { - if (resolution.includes('4k')) return '$6.37/Run' - if (resolution.includes('1080p')) return '$1.59/Run' - if (resolution.includes('720p')) return '$0.71/Run' - if (resolution.includes('540p')) return '$0.40/Run' - } else if (duration.includes('9s')) { - if (resolution.includes('4k')) return '$11.47/Run' - if (resolution.includes('1080p')) return '$2.87/Run' - if (resolution.includes('720p')) return '$1.28/Run' - if (resolution.includes('540p')) return '$0.72/Run' - } - } else if (model.includes('ray-1.6')) { - return '$0.35/Run' - } - - return '$0.55/Run' - } - }, - LumaVideoNode: { - displayPrice: (node: LGraphNode): string => { - const modelWidget = node.widgets?.find( - (w) => w.name === 'model' - ) as IComboWidget - const resolutionWidget = node.widgets?.find( - (w) => w.name === 'resolution' - ) as IComboWidget - const durationWidget = node.widgets?.find( - (w) => w.name === 'duration' - ) as IComboWidget - - if (!modelWidget || !resolutionWidget || !durationWidget) { - return '$0.14-11.47/Run (varies with model, resolution & duration)' - } - - const model = String(modelWidget.value) - const resolution = String(resolutionWidget.value).toLowerCase() - const duration = String(durationWidget.value) - - if (model.includes('ray-flash-2')) { - if (duration.includes('5s')) { - if (resolution.includes('4k')) return '$2.19/Run' - if (resolution.includes('1080p')) return '$0.55/Run' - if (resolution.includes('720p')) return '$0.24/Run' - if (resolution.includes('540p')) return '$0.14/Run' - } else if (duration.includes('9s')) { - if (resolution.includes('4k')) return '$3.95/Run' - if (resolution.includes('1080p')) return '$0.99/Run' - if (resolution.includes('720p')) return '$0.43/Run' - if (resolution.includes('540p')) return '$0.252/Run' - } - } else if (model.includes('ray-2')) { - if (duration.includes('5s')) { - if (resolution.includes('4k')) return '$6.37/Run' - if (resolution.includes('1080p')) return '$1.59/Run' - if (resolution.includes('720p')) return '$0.71/Run' - if (resolution.includes('540p')) return '$0.40/Run' - } else if (duration.includes('9s')) { - if (resolution.includes('4k')) return '$11.47/Run' - if (resolution.includes('1080p')) return '$2.87/Run' - if (resolution.includes('720p')) return '$1.28/Run' - if (resolution.includes('540p')) return '$0.72/Run' - } - } else if (model.includes('ray-1-6')) { - return '$0.35/Run' - } - - return '$0.55/Run' - } - }, - MinimaxImageToVideoNode: { - displayPrice: '$0.43/Run' - }, - MinimaxTextToVideoNode: { - displayPrice: '$0.43/Run' - }, - MinimaxHailuoVideoNode: { - displayPrice: (node: LGraphNode): string => { - const resolutionWidget = node.widgets?.find( - (w) => w.name === 'resolution' - ) as IComboWidget - const durationWidget = node.widgets?.find( - (w) => w.name === 'duration' - ) as IComboWidget - - if (!resolutionWidget || !durationWidget) { - return '$0.28-0.56/Run (varies with resolution & duration)' - } - - const resolution = String(resolutionWidget.value) - const duration = String(durationWidget.value) - - if (resolution.includes('768P')) { - if (duration.includes('6')) return '$0.28/Run' - if (duration.includes('10')) return '$0.56/Run' - } else if (resolution.includes('1080P')) { - if (duration.includes('6')) return '$0.49/Run' - } - - return '$0.43/Run' // default median - } - }, - OpenAIDalle2: { - displayPrice: (node: LGraphNode): string => { - const sizeWidget = node.widgets?.find( - (w) => w.name === 'size' - ) as IComboWidget - const nWidget = node.widgets?.find( - (w) => w.name === 'n' - ) as IComboWidget - - if (!sizeWidget) return '$0.016-0.02 x n/Run (varies with size & n)' - - const size = String(sizeWidget.value) - const n = Number(nWidget?.value) || 1 - let basePrice = 0.02 // default - - if (size.includes('1024x1024')) { - basePrice = 0.02 - } else if (size.includes('512x512')) { - basePrice = 0.018 - } else if (size.includes('256x256')) { - basePrice = 0.016 - } - - const totalCost = (basePrice * n).toFixed(3) - return `$${totalCost}/Run` - } - }, - OpenAIDalle3: { - displayPrice: (node: LGraphNode): string => { - // Get size and quality widgets - const sizeWidget = node.widgets?.find( - (w) => w.name === 'size' - ) as IComboWidget - const qualityWidget = node.widgets?.find( - (w) => w.name === 'quality' - ) as IComboWidget - - if (!sizeWidget || !qualityWidget) - return '$0.04-0.12/Run (varies with size & quality)' - - const size = String(sizeWidget.value) - const quality = String(qualityWidget.value) - - // Pricing matrix based on CSV data - if (size.includes('1024x1024')) { - return quality.includes('hd') ? '$0.08/Run' : '$0.04/Run' - } else if (size.includes('1792x1024') || size.includes('1024x1792')) { - return quality.includes('hd') ? '$0.12/Run' : '$0.08/Run' - } - - // Default value - return '$0.04/Run' - } - }, - OpenAIGPTImage1: { - displayPrice: (node: LGraphNode): string => { - const qualityWidget = node.widgets?.find( - (w) => w.name === 'quality' - ) as IComboWidget - const nWidget = node.widgets?.find( - (w) => w.name === 'n' - ) as IComboWidget - - if (!qualityWidget) - return '$0.011-0.30 x n/Run (varies with quality & n)' - - const quality = String(qualityWidget.value) - const n = Number(nWidget?.value) || 1 - let basePriceRange = '$0.046-0.07' // default medium - - if (quality.includes('high')) { - basePriceRange = '$0.167-0.30' - } else if (quality.includes('medium')) { - basePriceRange = '$0.046-0.07' - } else if (quality.includes('low')) { - basePriceRange = '$0.011-0.02' - } - - if (n === 1) { - return `${basePriceRange}/Run` - } else { - return `${basePriceRange} x ${n}/Run` - } - } - }, - PikaImageToVideoNode2_2: { - displayPrice: (node: LGraphNode): string => { - const durationWidget = node.widgets?.find( - (w) => w.name === 'duration' - ) as IComboWidget - const resolutionWidget = node.widgets?.find( - (w) => w.name === 'resolution' - ) as IComboWidget - - if (!durationWidget || !resolutionWidget) { - return '$0.2-1.0/Run (varies with duration & resolution)' - } - - const duration = String(durationWidget.value) - const resolution = String(resolutionWidget.value) - - if (duration.includes('5')) { - if (resolution.includes('1080p')) return '$0.45/Run' - if (resolution.includes('720p')) return '$0.2/Run' - } else if (duration.includes('10')) { - if (resolution.includes('1080p')) return '$1.0/Run' - if (resolution.includes('720p')) return '$0.6/Run' - } - - return '$0.2/Run' - } - }, - PikaScenesV2_2: { - displayPrice: (node: LGraphNode): string => { - const durationWidget = node.widgets?.find( - (w) => w.name === 'duration' - ) as IComboWidget - const resolutionWidget = node.widgets?.find( - (w) => w.name === 'resolution' - ) as IComboWidget - - if (!durationWidget || !resolutionWidget) { - return '$0.2-1.0/Run (varies with duration & resolution)' - } - - const duration = String(durationWidget.value) - const resolution = String(resolutionWidget.value) - - if (duration.includes('5')) { - if (resolution.includes('720p')) return '$0.3/Run' - if (resolution.includes('1080p')) return '$0.5/Run' - } else if (duration.includes('10')) { - if (resolution.includes('720p')) return '$0.4/Run' - if (resolution.includes('1080p')) return '$1.5/Run' - } - - return '$0.3/Run' - } - }, - PikaStartEndFrameNode2_2: { - displayPrice: (node: LGraphNode): string => { - const durationWidget = node.widgets?.find( - (w) => w.name === 'duration' - ) as IComboWidget - const resolutionWidget = node.widgets?.find( - (w) => w.name === 'resolution' - ) as IComboWidget - - if (!durationWidget || !resolutionWidget) { - return '$0.2-1.0/Run (varies with duration & resolution)' - } - - const duration = String(durationWidget.value) - const resolution = String(resolutionWidget.value) - - if (duration.includes('5')) { - if (resolution.includes('720p')) return '$0.2/Run' - if (resolution.includes('1080p')) return '$0.3/Run' - } else if (duration.includes('10')) { - if (resolution.includes('720p')) return '$0.25/Run' - if (resolution.includes('1080p')) return '$1.0/Run' - } - - return '$0.2/Run' - } - }, - PikaTextToVideoNode2_2: { - displayPrice: (node: LGraphNode): string => { - const durationWidget = node.widgets?.find( - (w) => w.name === 'duration' - ) as IComboWidget - const resolutionWidget = node.widgets?.find( - (w) => w.name === 'resolution' - ) as IComboWidget - - if (!durationWidget || !resolutionWidget) { - return '$0.2-1.5/Run (varies with duration & resolution)' - } - - const duration = String(durationWidget.value) - const resolution = String(resolutionWidget.value) - - if (duration.includes('5')) { - if (resolution.includes('1080p')) return '$0.45/Run' - if (resolution.includes('720p')) return '$0.2/Run' - } else if (duration.includes('10')) { - if (resolution.includes('1080p')) return '$1.0/Run' - if (resolution.includes('720p')) return '$0.6/Run' - } - - return '$0.45/Run' - } - }, - Pikadditions: { - displayPrice: '$0.3/Run' - }, - Pikaffects: { - displayPrice: '$0.45/Run' - }, - Pikaswaps: { - displayPrice: '$0.3/Run' - }, - PixverseImageToVideoNode: { - displayPrice: pixversePricingCalculator - }, - PixverseTextToVideoNode: { - displayPrice: pixversePricingCalculator - }, - PixverseTransitionVideoNode: { - displayPrice: pixversePricingCalculator - }, - RecraftCreativeUpscaleNode: { - displayPrice: '$0.25/Run' - }, - RecraftCrispUpscaleNode: { - displayPrice: '$0.004/Run' - }, - RecraftGenerateColorFromImageNode: { - displayPrice: (node: LGraphNode): string => { - const nWidget = node.widgets?.find( - (w) => w.name === 'n' - ) as IComboWidget - if (!nWidget) return '$0.04 x n/Run' - - const n = Number(nWidget.value) || 1 - const cost = (0.04 * n).toFixed(2) - return `$${cost}/Run` - } - }, - RecraftGenerateImageNode: { - displayPrice: (node: LGraphNode): string => { - const nWidget = node.widgets?.find( - (w) => w.name === 'n' - ) as IComboWidget - if (!nWidget) return '$0.04 x n/Run' - - const n = Number(nWidget.value) || 1 - const cost = (0.04 * n).toFixed(2) - return `$${cost}/Run` - } - }, - RecraftGenerateVectorImageNode: { - displayPrice: (node: LGraphNode): string => { - const nWidget = node.widgets?.find( - (w) => w.name === 'n' - ) as IComboWidget - if (!nWidget) return '$0.08 x n/Run' - - const n = Number(nWidget.value) || 1 - const cost = (0.08 * n).toFixed(2) - return `$${cost}/Run` - } - }, - RecraftImageInpaintingNode: { - displayPrice: (node: LGraphNode): string => { - const nWidget = node.widgets?.find( - (w) => w.name === 'n' - ) as IComboWidget - if (!nWidget) return '$0.04 x n/Run' - - const n = Number(nWidget.value) || 1 - const cost = (0.04 * n).toFixed(2) - return `$${cost}/Run` - } - }, - RecraftImageToImageNode: { - displayPrice: (node: LGraphNode): string => { - const nWidget = node.widgets?.find( - (w) => w.name === 'n' - ) as IComboWidget - if (!nWidget) return '$0.04 x n/Run' - - const n = Number(nWidget.value) || 1 - const cost = (0.04 * n).toFixed(2) - return `$${cost}/Run` - } - }, - RecraftRemoveBackgroundNode: { - displayPrice: '$0.01/Run' - }, - RecraftReplaceBackgroundNode: { - displayPrice: '$0.04/Run' - }, - RecraftTextToImageNode: { - displayPrice: (node: LGraphNode): string => { - const nWidget = node.widgets?.find( - (w) => w.name === 'n' - ) as IComboWidget - if (!nWidget) return '$0.04 x n/Run' - - const n = Number(nWidget.value) || 1 - const cost = (0.04 * n).toFixed(2) - return `$${cost}/Run` - } - }, - RecraftTextToVectorNode: { - displayPrice: (node: LGraphNode): string => { - const nWidget = node.widgets?.find( - (w) => w.name === 'n' - ) as IComboWidget - if (!nWidget) return '$0.08 x n/Run' - - const n = Number(nWidget.value) || 1 - const cost = (0.08 * n).toFixed(2) - return `$${cost}/Run` - } - }, - RecraftVectorizeImageNode: { - displayPrice: (node: LGraphNode): string => { - const nWidget = node.widgets?.find( - (w) => w.name === 'n' - ) as IComboWidget - if (!nWidget) return '$0.01 x n/Run' - - const n = Number(nWidget.value) || 1 - const cost = (0.01 * n).toFixed(2) - return `$${cost}/Run` - } - }, - StabilityStableImageSD_3_5Node: { - displayPrice: (node: LGraphNode): string => { - const modelWidget = node.widgets?.find( - (w) => w.name === 'model' - ) as IComboWidget - - if (!modelWidget) return '$0.035-0.065/Run (varies with model)' - - const model = String(modelWidget.value).toLowerCase() - if (model.includes('large')) { - return '$0.065/Run' - } else if (model.includes('medium')) { - return '$0.035/Run' - } - - return '$0.035/Run' - } - }, - StabilityStableImageUltraNode: { - displayPrice: '$0.08/Run' - }, - StabilityUpscaleConservativeNode: { - displayPrice: '$0.25/Run' - }, - StabilityUpscaleCreativeNode: { - displayPrice: '$0.25/Run' - }, - StabilityUpscaleFastNode: { - displayPrice: '$0.01/Run' - }, - StabilityTextToAudio: { - displayPrice: '$0.20/Run' - }, - StabilityAudioToAudio: { - displayPrice: '$0.20/Run' - }, - StabilityAudioInpaint: { - displayPrice: '$0.20/Run' - }, - VeoVideoGenerationNode: { - displayPrice: (node: LGraphNode): string => { - const durationWidget = node.widgets?.find( - (w) => w.name === 'duration_seconds' - ) as IComboWidget - - if (!durationWidget) return '$2.50-5.0/Run (varies with duration)' - - const price = 0.5 * Number(durationWidget.value) - 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 '$0.80-3.20/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') || - model.includes('veo-3.1-fast-generate') - ) { - return generateAudio ? '$1.20/Run' : '$0.80/Run' - } else if ( - model.includes('veo-3.0-generate-001') || - model.includes('veo-3.1-generate') - ) { - return generateAudio ? '$3.20/Run' : '$1.60/Run' - } - - // Default fallback - return '$0.80-3.20/Run' - } - }, - Veo3FirstLastFrameNode: { - 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 - const durationWidget = node.widgets?.find( - (w) => w.name === 'duration' - ) as IComboWidget - - if (!modelWidget || !generateAudioWidget || !durationWidget) { - return '$0.40-3.20/Run (varies with model & audio generation)' - } - - const model = String(modelWidget.value) - const generateAudio = - String(generateAudioWidget.value).toLowerCase() === 'true' - const seconds = parseFloat(String(durationWidget.value)) - - let pricePerSecond: number | null = null - if (model.includes('veo-3.1-fast-generate')) { - pricePerSecond = generateAudio ? 0.15 : 0.1 - } else if (model.includes('veo-3.1-generate')) { - pricePerSecond = generateAudio ? 0.4 : 0.2 - } - if (pricePerSecond === null) { - return '$0.40-3.20/Run' - } - const cost = pricePerSecond * seconds - return `$${cost.toFixed(2)}/Run` - } - }, - LumaImageNode: { - displayPrice: (node: LGraphNode): string => { - const modelWidget = node.widgets?.find( - (w) => w.name === 'model' - ) as IComboWidget - const aspectRatioWidget = node.widgets?.find( - (w) => w.name === 'aspect_ratio' - ) as IComboWidget - - if (!modelWidget || !aspectRatioWidget) { - return '$0.0045-0.0182/Run (varies with model & aspect ratio)' - } - - const model = String(modelWidget.value) - - if (model.includes('photon-flash-1')) { - return '$0.0019/Run' - } else if (model.includes('photon-1')) { - return '$0.0073/Run' - } - - return '$0.0172/Run' - } - }, - LumaImageModifyNode: { - displayPrice: (node: LGraphNode): string => { - const modelWidget = node.widgets?.find( - (w) => w.name === 'model' - ) as IComboWidget - - if (!modelWidget) { - return '$0.0019-0.0073/Run (varies with model)' - } - - const model = String(modelWidget.value) - - if (model.includes('photon-flash-1')) { - return '$0.0019/Run' - } else if (model.includes('photon-1')) { - return '$0.0073/Run' - } - - return '$0.0172/Run' - } - }, - MoonvalleyTxt2VideoNode: { - displayPrice: (node: LGraphNode): string => { - const lengthWidget = node.widgets?.find( - (w) => w.name === 'length' - ) as IComboWidget - - // If no length widget exists, default to 5s pricing - if (!lengthWidget) return '$1.50/Run' - - const length = String(lengthWidget.value) - if (length === '5s') { - return '$1.50/Run' - } else if (length === '10s') { - return '$3.00/Run' - } - - return '$1.50/Run' - } - }, - MoonvalleyImg2VideoNode: { - displayPrice: (node: LGraphNode): string => { - const lengthWidget = node.widgets?.find( - (w) => w.name === 'length' - ) as IComboWidget - - // If no length widget exists, default to 5s pricing - if (!lengthWidget) return '$1.50/Run' - - const length = String(lengthWidget.value) - if (length === '5s') { - return '$1.50/Run' - } else if (length === '10s') { - return '$3.00/Run' - } - - return '$1.50/Run' - } - }, - MoonvalleyVideo2VideoNode: { - displayPrice: (node: LGraphNode): string => { - const lengthWidget = node.widgets?.find( - (w) => w.name === 'length' - ) as IComboWidget - - // If no length widget exists, default to 5s pricing - if (!lengthWidget) return '$2.25/Run' - - const length = String(lengthWidget.value) - if (length === '5s') { - return '$2.25/Run' - } else if (length === '10s') { - return '$4.00/Run' - } - - return '$2.25/Run' - } - }, - // Runway nodes - using actual node names from ComfyUI - RunwayTextToImageNode: { - displayPrice: '$0.08/Run' - }, - RunwayImageToVideoNodeGen3a: { - displayPrice: calculateRunwayDurationPrice - }, - RunwayImageToVideoNodeGen4: { - displayPrice: calculateRunwayDurationPrice - }, - RunwayFirstLastFrameNode: { - displayPrice: calculateRunwayDurationPrice - }, - // Rodin nodes - all have the same pricing structure - Rodin3D_Regular: { - displayPrice: '$0.4/Run' - }, - Rodin3D_Detail: { - displayPrice: '$0.4/Run' - }, - Rodin3D_Smooth: { - displayPrice: '$0.4/Run' - }, - Rodin3D_Sketch: { - displayPrice: '$0.4/Run' - }, - // Tripo nodes - using actual node names from ComfyUI - TripoTextToModelNode: { - displayPrice: (node: LGraphNode): string => { - const quadWidget = node.widgets?.find( - (w) => w.name === 'quad' - ) as IComboWidget - const styleWidget = node.widgets?.find( - (w) => w.name === 'style' - ) as IComboWidget - const textureWidget = node.widgets?.find( - (w) => w.name === 'texture' - ) as IComboWidget - const textureQualityWidget = node.widgets?.find( - (w) => w.name === 'texture_quality' - ) as IComboWidget - - if (!quadWidget || !styleWidget || !textureWidget) - return '$0.1-0.4/Run (varies with quad, style, texture & quality)' - - const quad = String(quadWidget.value).toLowerCase() === 'true' - const style = String(styleWidget.value).toLowerCase() - const texture = String(textureWidget.value).toLowerCase() === 'true' - const textureQuality = String( - textureQualityWidget?.value || 'standard' - ).toLowerCase() - - // Pricing logic based on CSV data - if (style.includes('none')) { - if (!quad) { - if (!texture) return '$0.10/Run' - else return '$0.15/Run' - } else { - if (textureQuality.includes('detailed')) { - if (!texture) return '$0.30/Run' - else return '$0.35/Run' - } else { - if (!texture) return '$0.20/Run' - else return '$0.25/Run' - } - } - } else { - // any style - if (!quad) { - if (!texture) return '$0.15/Run' - else return '$0.20/Run' - } else { - if (textureQuality.includes('detailed')) { - if (!texture) return '$0.35/Run' - else return '$0.40/Run' - } else { - if (!texture) return '$0.25/Run' - else return '$0.30/Run' - } - } - } - } - }, - TripoImageToModelNode: { - displayPrice: (node: LGraphNode): string => { - const quadWidget = node.widgets?.find( - (w) => w.name === 'quad' - ) as IComboWidget - const styleWidget = node.widgets?.find( - (w) => w.name === 'style' - ) as IComboWidget - const textureWidget = node.widgets?.find( - (w) => w.name === 'texture' - ) as IComboWidget - const textureQualityWidget = node.widgets?.find( - (w) => w.name === 'texture_quality' - ) as IComboWidget - - if (!quadWidget || !styleWidget || !textureWidget) - return '$0.2-0.5/Run (varies with quad, style, texture & quality)' - - const quad = String(quadWidget.value).toLowerCase() === 'true' - const style = String(styleWidget.value).toLowerCase() - const texture = String(textureWidget.value).toLowerCase() === 'true' - const textureQuality = String( - textureQualityWidget?.value || 'standard' - ).toLowerCase() - - // Pricing logic based on CSV data for Image to Model - if (style.includes('none')) { - if (!quad) { - if (!texture) return '$0.20/Run' - else return '$0.25/Run' - } else { - if (textureQuality.includes('detailed')) { - if (!texture) return '$0.40/Run' - else return '$0.45/Run' - } else { - if (!texture) return '$0.30/Run' - else return '$0.35/Run' - } - } - } else { - // any style - if (!quad) { - if (!texture) return '$0.25/Run' - else return '$0.30/Run' - } else { - if (textureQuality.includes('detailed')) { - if (!texture) return '$0.45/Run' - else return '$0.50/Run' - } else { - if (!texture) return '$0.35/Run' - else return '$0.40/Run' - } - } - } - } - }, - TripoRefineNode: { - displayPrice: '$0.3/Run' - }, - TripoTextureNode: { - displayPrice: (node: LGraphNode): string => { - const textureQualityWidget = node.widgets?.find( - (w) => w.name === 'texture_quality' - ) as IComboWidget - - if (!textureQualityWidget) return '$0.1-0.2/Run (varies with quality)' - - const textureQuality = String(textureQualityWidget.value) - return textureQuality.includes('detailed') ? '$0.2/Run' : '$0.1/Run' - } - }, - TripoConvertModelNode: { - displayPrice: '$0.10/Run' - }, - TripoRetargetRiggedModelNode: { - displayPrice: '$0.10/Run' - }, - TripoMultiviewToModelNode: { - displayPrice: (node: LGraphNode): string => { - const quadWidget = node.widgets?.find( - (w) => w.name === 'quad' - ) as IComboWidget - const styleWidget = node.widgets?.find( - (w) => w.name === 'style' - ) as IComboWidget - const textureWidget = node.widgets?.find( - (w) => w.name === 'texture' - ) as IComboWidget - const textureQualityWidget = node.widgets?.find( - (w) => w.name === 'texture_quality' - ) as IComboWidget - - if (!quadWidget || !styleWidget || !textureWidget) - return '$0.2-0.5/Run (varies with quad, style, texture & quality)' - - const quad = String(quadWidget.value).toLowerCase() === 'true' - const style = String(styleWidget.value).toLowerCase() - const texture = String(textureWidget.value).toLowerCase() === 'true' - const textureQuality = String( - textureQualityWidget?.value || 'standard' - ).toLowerCase() - - // Pricing logic based on CSV data for Multiview to Model (same as Image to Model) - if (style.includes('none')) { - if (!quad) { - if (!texture) return '$0.20/Run' - else return '$0.25/Run' - } else { - if (textureQuality.includes('detailed')) { - if (!texture) return '$0.40/Run' - else return '$0.45/Run' - } else { - if (!texture) return '$0.30/Run' - else return '$0.35/Run' - } - } - } else { - // any style - if (!quad) { - if (!texture) return '$0.25/Run' - else return '$0.30/Run' - } else { - if (textureQuality.includes('detailed')) { - if (!texture) return '$0.45/Run' - else return '$0.50/Run' - } else { - if (!texture) return '$0.35/Run' - else return '$0.40/Run' - } - } - } - } - }, - // Google/Gemini nodes - GeminiNode: { - displayPrice: (node: LGraphNode): string => { - const modelWidget = node.widgets?.find( - (w) => w.name === 'model' - ) as IComboWidget - - if (!modelWidget) return 'Token-based' - - const model = String(modelWidget.value) - - // Google Veo video generation - if (model.includes('veo-2.0')) { - return '$0.5/second' - } else if (model.includes('gemini-2.5-flash-preview-04-17')) { - return '$0.0003/$0.0025 per 1K tokens' - } else if (model.includes('gemini-2.5-flash')) { - return '$0.0003/$0.0025 per 1K tokens' - } else if (model.includes('gemini-2.5-pro-preview-05-06')) { - return '$0.00125/$0.01 per 1K tokens' - } else if (model.includes('gemini-2.5-pro')) { - return '$0.00125/$0.01 per 1K tokens' - } else if (model.includes('gemini-3-pro-preview')) { - return '$0.002/$0.012 per 1K tokens' - } - // For other Gemini models, show token-based pricing info - return 'Token-based' - } - }, - GeminiImageNode: { - displayPrice: '~$0.039/Image (1K)' - }, - GeminiImage2Node: { - displayPrice: (node: LGraphNode): string => { - const resolutionWidget = node.widgets?.find( - (w) => w.name === 'resolution' - ) as IComboWidget - - if (!resolutionWidget) return 'Token-based' - - const resolution = String(resolutionWidget.value) - if (resolution.includes('1K')) { - return '~$0.134/Image' - } else if (resolution.includes('2K')) { - return '~$0.134/Image' - } else if (resolution.includes('4K')) { - return '~$0.24/Image' - } - return 'Token-based' - } - }, - // OpenAI nodes - OpenAIChatNode: { - displayPrice: (node: LGraphNode): string => { - const modelWidget = node.widgets?.find( - (w) => w.name === 'model' - ) as IComboWidget - - if (!modelWidget) return 'Token-based' - - const model = String(modelWidget.value) - - // Specific pricing for exposed models based on official pricing data (converted to per 1K tokens) - if (model.includes('o4-mini')) { - return '$0.0011/$0.0044 per 1K tokens' - } else if (model.includes('o1-pro')) { - return '$0.15/$0.60 per 1K tokens' - } else if (model.includes('o1')) { - return '$0.015/$0.06 per 1K tokens' - } else if (model.includes('o3-mini')) { - return '$0.0011/$0.0044 per 1K tokens' - } else if (model.includes('o3')) { - return '$0.01/$0.04 per 1K tokens' - } else if (model.includes('gpt-4o')) { - return '$0.0025/$0.01 per 1K tokens' - } else if (model.includes('gpt-4.1-nano')) { - return '$0.0001/$0.0004 per 1K tokens' - } else if (model.includes('gpt-4.1-mini')) { - return '$0.0004/$0.0016 per 1K tokens' - } else if (model.includes('gpt-4.1')) { - return '$0.002/$0.008 per 1K tokens' - } else if (model.includes('gpt-5-nano')) { - return '$0.00005/$0.0004 per 1K tokens' - } else if (model.includes('gpt-5-mini')) { - return '$0.00025/$0.002 per 1K tokens' - } else if (model.includes('gpt-5')) { - return '$0.00125/$0.01 per 1K tokens' - } - return 'Token-based' - } - }, - ViduTextToVideoNode: { - displayPrice: '$0.4/Run' - }, - ViduImageToVideoNode: { - displayPrice: '$0.4/Run' - }, - ViduReferenceVideoNode: { - displayPrice: '$0.4/Run' - }, - ViduStartEndToVideoNode: { - displayPrice: '$0.4/Run' - }, - ByteDanceImageNode: { - displayPrice: (node: LGraphNode): string => { - const modelWidget = node.widgets?.find( - (w) => w.name === 'model' - ) as IComboWidget - - if (!modelWidget) return 'Token-based' - - const model = String(modelWidget.value) - - if (model.includes('seedream-3-0-t2i')) { - return '$0.03/Run' - } - return 'Token-based' - } - }, - ByteDanceImageEditNode: { - displayPrice: (node: LGraphNode): string => { - const modelWidget = node.widgets?.find( - (w) => w.name === 'model' - ) as IComboWidget - - if (!modelWidget) return 'Token-based' - - const model = String(modelWidget.value) - - if (model.includes('seededit-3-0-i2i')) { - return '$0.03/Run' - } - return 'Token-based' - } - }, - ByteDanceSeedreamNode: { - displayPrice: (node: LGraphNode): string => { - const sequentialGenerationWidget = node.widgets?.find( - (w) => w.name === 'sequential_image_generation' - ) as IComboWidget - const maxImagesWidget = node.widgets?.find( - (w) => w.name === 'max_images' - ) as IComboWidget - - if (!sequentialGenerationWidget || !maxImagesWidget) - return '$0.03/Run ($0.03 for one output image)' - - if ( - String(sequentialGenerationWidget.value).toLowerCase() === 'disabled' - ) { - return '$0.03/Run' - } - - const maxImages = Number(maxImagesWidget.value) - if (maxImages === 1) { - return '$0.03/Run' - } - const cost = (0.03 * maxImages).toFixed(2) - return `$${cost}/Run ($0.03 for one output image)` - } - }, - ByteDanceTextToVideoNode: { - displayPrice: byteDanceVideoPricingCalculator - }, - ByteDanceImageToVideoNode: { - displayPrice: byteDanceVideoPricingCalculator - }, - ByteDanceFirstLastFrameNode: { - displayPrice: byteDanceVideoPricingCalculator - }, - ByteDanceImageReferenceNode: { - displayPrice: byteDanceVideoPricingCalculator - }, - WanTextToVideoApi: { - displayPrice: (node: LGraphNode): string => { - const durationWidget = node.widgets?.find( - (w) => w.name === 'duration' - ) as IComboWidget - const resolutionWidget = node.widgets?.find( - (w) => w.name === 'size' - ) as IComboWidget - - if (!durationWidget || !resolutionWidget) return '$0.05-0.15/second' - - const seconds = parseFloat(String(durationWidget.value)) - const resolutionStr = String(resolutionWidget.value).toLowerCase() - - const resKey = resolutionStr.includes('1080') - ? '1080p' - : resolutionStr.includes('720') - ? '720p' - : resolutionStr.includes('480') - ? '480p' - : (resolutionStr.match(/^\s*(\d{3,4}p)/)?.[1] ?? '') - - const pricePerSecond: Record = { - '480p': 0.05, - '720p': 0.1, - '1080p': 0.15 - } - - const pps = pricePerSecond[resKey] - if (isNaN(seconds) || !pps) return '$0.05-0.15/second' - - const cost = (pps * seconds).toFixed(2) - return `$${cost}/Run` - } - }, - WanImageToVideoApi: { - displayPrice: (node: LGraphNode): string => { - const durationWidget = node.widgets?.find( - (w) => w.name === 'duration' - ) as IComboWidget - const resolutionWidget = node.widgets?.find( - (w) => w.name === 'resolution' - ) as IComboWidget - - if (!durationWidget || !resolutionWidget) return '$0.05-0.15/second' - - const seconds = parseFloat(String(durationWidget.value)) - const resolution = String(resolutionWidget.value).trim().toLowerCase() - - const pricePerSecond: Record = { - '480p': 0.05, - '720p': 0.1, - '1080p': 0.15 - } - - const pps = pricePerSecond[resolution] - if (isNaN(seconds) || !pps) return '$0.05-0.15/second' - - const cost = (pps * seconds).toFixed(2) - return `$${cost}/Run` - } - }, - WanTextToImageApi: { - displayPrice: '$0.03/Run' - }, - WanImageToImageApi: { - displayPrice: '$0.03/Run' - }, - LtxvApiTextToVideo: { - displayPrice: ltxvPricingCalculator - }, - LtxvApiImageToVideo: { - displayPrice: ltxvPricingCalculator - } - } +type NodeConstructorData = Partial< + Pick +> /** - * Composable to get node pricing information for API nodes + * Extract nodeData from an LGraphNode's constructor. + * Centralizes the `as any` cast needed to access this runtime property. */ -export const useNodePricing = () => { - /** - * Get the price display for a node - */ - const getNodeDisplayPrice = (node: LGraphNode): string => { - if (!node.constructor?.nodeData?.api_node) return '' +const getNodeConstructorData = ( + node: LGraphNode +): NodeConstructorData | undefined => + (node.constructor as { nodeData?: NodeConstructorData }).nodeData - const nodeName = node.constructor.nodeData.name - const priceConfig = apiNodeCosts[nodeName] +type JsonataEvalContext = { + widgets: Record + inputs: Record + /** Count of connected inputs per autogrow group */ + inputGroups: Record +} - if (!priceConfig) return '' +// ----------------------------- +// Normalization helpers +// ----------------------------- +const asFiniteNumber = (v: unknown): number | null => { + if (v === null || v === undefined) return null - // If it's a function, call it with the node to get dynamic pricing - if (typeof priceConfig.displayPrice === 'function') { - return safePricingExecution(priceConfig.displayPrice, node, '') - } + if (typeof v === 'number') return Number.isFinite(v) ? v : null - // Otherwise return the static price - return priceConfig.displayPrice + if (typeof v === 'string') { + const t = v.trim() + if (t === '') return null + const n = Number(t) + return Number.isFinite(n) ? n : null } - const getNodePricingConfig = (node: LGraphNode) => - apiNodeCosts[node.constructor.nodeData?.name ?? ''] + // Do not coerce booleans/objects into numbers for pricing purposes. + return null +} - const getRelevantWidgetNames = (nodeType: string): string[] => { - const widgetMap: Record = { - KlingTextToVideoNode: ['mode', 'model_name', 'duration'], - KlingImage2VideoNode: ['mode', 'model_name', 'duration'], - KlingImageGenerationNode: ['modality', 'model_name', 'n'], - KlingDualCharacterVideoEffectNode: ['mode', 'model_name', 'duration'], - KlingSingleImageVideoEffectNode: ['effect_scene'], - KlingStartEndFrameNode: ['mode', 'model_name', 'duration'], - MinimaxHailuoVideoNode: ['resolution', 'duration'], - OpenAIDalle3: ['size', 'quality'], - OpenAIDalle2: ['size', 'n'], - OpenAIVideoSora2: ['model', 'size', 'duration'], - OpenAIGPTImage1: ['quality', 'n'], - IdeogramV1: ['num_images', 'turbo'], - IdeogramV2: ['num_images', 'turbo'], - IdeogramV3: ['rendering_speed', 'num_images', 'character_image'], - FluxProKontextProNode: [], - FluxProKontextMaxNode: [], - Flux2ProImageNode: ['width', 'height', 'images'], - VeoVideoGenerationNode: ['duration_seconds'], - Veo3VideoGenerationNode: ['model', 'generate_audio'], - Veo3FirstLastFrameNode: ['model', 'generate_audio', 'duration'], - LumaVideoNode: ['model', 'resolution', 'duration'], - LumaImageToVideoNode: ['model', 'resolution', 'duration'], - LumaImageNode: ['model', 'aspect_ratio'], - LumaImageModifyNode: ['model', 'aspect_ratio'], - PikaTextToVideoNode2_2: ['duration', 'resolution'], - PikaImageToVideoNode2_2: ['duration', 'resolution'], - PikaScenesV2_2: ['duration', 'resolution'], - PikaStartEndFrameNode2_2: ['duration', 'resolution'], - PixverseTextToVideoNode: ['duration_seconds', 'quality', 'motion_mode'], - PixverseTransitionVideoNode: [ - 'duration_seconds', - 'motion_mode', - 'quality' - ], - PixverseImageToVideoNode: ['duration_seconds', 'quality', 'motion_mode'], - StabilityStableImageSD_3_5Node: ['model'], - RecraftTextToImageNode: ['n'], - RecraftImageToImageNode: ['n'], - RecraftImageInpaintingNode: ['n'], - RecraftTextToVectorNode: ['n'], - RecraftVectorizeImageNode: ['n'], - RecraftGenerateColorFromImageNode: ['n'], - RecraftGenerateImageNode: ['n'], - RecraftGenerateVectorImageNode: ['n'], - MoonvalleyTxt2VideoNode: ['length'], - MoonvalleyImg2VideoNode: ['length'], - MoonvalleyVideo2VideoNode: ['length'], - // Runway nodes - RunwayImageToVideoNodeGen3a: ['duration'], - RunwayImageToVideoNodeGen4: ['duration'], - RunwayFirstLastFrameNode: ['duration'], - // Tripo nodes - TripoTextToModelNode: ['quad', 'style', 'texture', 'texture_quality'], - TripoImageToModelNode: ['quad', 'style', 'texture', 'texture_quality'], - TripoTextureNode: ['texture_quality'], - // Google/Gemini nodes - GeminiNode: ['model'], - GeminiImage2Node: ['resolution'], - // OpenAI nodes - OpenAIChatNode: ['model'], - // ByteDance - ByteDanceImageNode: ['model'], - ByteDanceImageEditNode: ['model'], - ByteDanceSeedreamNode: [ - 'model', - 'sequential_image_generation', - 'max_images' - ], - ByteDanceTextToVideoNode: ['model', 'duration', 'resolution'], - ByteDanceImageToVideoNode: ['model', 'duration', 'resolution'], - ByteDanceFirstLastFrameNode: ['model', 'duration', 'resolution'], - ByteDanceImageReferenceNode: ['model', 'duration', 'resolution'], - WanTextToVideoApi: ['duration', 'size'], - WanImageToVideoApi: ['duration', 'resolution'], - LtxvApiTextToVideo: ['model', 'duration', 'resolution'], - LtxvApiImageToVideo: ['model', 'duration', 'resolution'] +/** + * Normalize widget value based on its declared type. + * Returns the value in its natural type for simpler JSONata expressions. + */ +const normalizeWidgetValue = ( + raw: unknown, + declaredType: string +): NormalizedWidgetValue => { + if (raw === undefined || raw === null) { + return null + } + + const upperType = declaredType.toUpperCase() + + // Numeric types + if (upperType === 'INT' || upperType === 'FLOAT') { + return asFiniteNumber(raw) + } + + // Boolean type + if (upperType === 'BOOLEAN') { + if (typeof raw === 'boolean') return raw + if (typeof raw === 'string') { + const ls = raw.trim().toLowerCase() + if (ls === 'true') return true + if (ls === 'false') return false } - return widgetMap[nodeType] || [] + return null + } + + // COMBO type - preserve string/numeric values (for options like [5, "10"]) + if (upperType === 'COMBO') { + if (typeof raw === 'number') return raw + if (typeof raw === 'boolean') return raw + return String(raw).trim().toLowerCase() + } + + // String/other types - return as lowercase trimmed string + return String(raw).trim().toLowerCase() +} + +const buildJsonataContext = ( + node: LGraphNode, + rule: JsonataPricingRule +): JsonataEvalContext => { + const widgets: Record = {} + for (const dep of rule.depends_on.widgets) { + const widget = node.widgets?.find((x: IBaseWidget) => x.name === dep.name) + widgets[dep.name] = normalizeWidgetValue(widget?.value, dep.type) + } + + const inputs: Record = {} + for (const name of rule.depends_on.inputs) { + const slot = node.inputs?.find((x: INodeInputSlot) => x.name === name) + inputs[name] = { connected: slot?.link != null } + } + + // Count connected inputs per autogrow group + const inputGroups: Record = {} + for (const groupName of rule.depends_on.input_groups) { + const prefix = groupName + '.' + inputGroups[groupName] = + node.inputs?.filter( + (inp: INodeInputSlot) => + inp.name?.startsWith(prefix) && inp.link != null + ).length ?? 0 + } + + return { widgets, inputs, inputGroups } +} + +const safeValueForSig = (v: unknown): string => { + if (v === null || v === undefined) return '' + if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') + return String(v) + try { + return JSON.stringify(v) + } catch { + return String(v) + } +} + +// Signature determines whether we need to re-evaluate when widgets/inputs change. +const buildSignature = ( + ctx: JsonataEvalContext, + rule: JsonataPricingRule +): string => { + const parts: string[] = [] + for (const dep of rule.depends_on.widgets) { + parts.push(`w:${dep.name}=${safeValueForSig(ctx.widgets[dep.name])}`) + } + for (const name of rule.depends_on.inputs) { + parts.push(`i:${name}=${ctx.inputs[name]?.connected ? '1' : '0'}`) + } + for (const name of rule.depends_on.input_groups) { + parts.push(`g:${name}=${ctx.inputGroups[name] ?? 0}`) + } + return parts.join('|') +} + +// ----------------------------- +// Result formatting +// ----------------------------- +const formatPricingResult = ( + result: unknown, + defaults: CreditFormatOptions = {} +): string => { + if (!isPricingResult(result)) { + if (result !== undefined && result !== null) { + console.warn('[pricing/jsonata] invalid result format:', result) + } + return '' + } + + if (result.type === 'text') { + return result.text ?? '' + } + + if (result.type === 'usd') { + const usd = asFiniteNumber(result.usd) + if (usd === null) return '' + const fmt = { ...defaults, ...(result.format ?? {}) } + return formatCreditsLabel(usd, fmt) + } + + if (result.type === 'range_usd') { + const minUsd = asFiniteNumber(result.min_usd) + const maxUsd = asFiniteNumber(result.max_usd) + if (minUsd === null || maxUsd === null) return '' + const fmt = { ...defaults, ...(result.format ?? {}) } + return formatCreditsRangeLabel(minUsd, maxUsd, fmt) + } + + if (result.type === 'list_usd') { + const arr = Array.isArray(result.usd) ? result.usd : null + if (!arr) return '' + + const usdValues = arr + .map(asFiniteNumber) + .filter((x): x is number => x != null) + + if (usdValues.length === 0) return '' + + const fmt = { ...defaults, ...(result.format ?? {}) } + return formatCreditsListLabel(usdValues, fmt) + } + + return '' +} + +// ----------------------------- +// Compile rules (non-fatal) +// ----------------------------- +const compileRule = (rule: JsonataPricingRule): CompiledJsonataPricingRule => { + try { + return { ...rule, _compiled: jsonata(rule.expr) } + } catch (e) { + // Do not crash app on bad expressions; just disable rule. + console.error('[pricing/jsonata] failed to compile expr:', rule.expr, e) + return { ...rule, _compiled: null } + } +} + +// ----------------------------- +// Rule cache (per-node-type) +// ----------------------------- +// Cache compiled rules by node type name to avoid recompiling on every evaluation. +const compiledRulesCache = new Map() + +/** + * Convert a PriceBadge from node definition to a JsonataPricingRule. + */ +const priceBadgeToRule = (priceBadge: PriceBadge): JsonataPricingRule => ({ + engine: priceBadge.engine ?? 'jsonata', + depends_on: { + widgets: priceBadge.depends_on?.widgets ?? [], + inputs: priceBadge.depends_on?.inputs ?? [], + input_groups: priceBadge.depends_on?.input_groups ?? [] + }, + expr: priceBadge.expr +}) + +/** + * Get or compile a pricing rule for a node type. + */ +const getCompiledRuleForNodeType = ( + nodeName: string, + priceBadge: PriceBadge | undefined +): CompiledJsonataPricingRule | null => { + if (!priceBadge) return null + + // Check cache first + if (compiledRulesCache.has(nodeName)) { + return compiledRulesCache.get(nodeName) ?? null + } + + // Compile and cache + const rule = priceBadgeToRule(priceBadge) + const compiled = compileRule(rule) + compiledRulesCache.set(nodeName, compiled) + return compiled +} + +// ----------------------------- +// Async evaluation + cache (JSONata 2.x) +// ----------------------------- + +// Reactive tick to force UI updates when async evaluations resolve. +// We purposely read pricingTick.value inside getNodeDisplayPrice to create a dependency. +const pricingTick = ref(0) + +// Per-node revision tracking for VueNodes mode (more efficient than global tick) +// Uses plain Map with individual refs per node for fine-grained reactivity +// Keys are stringified node IDs to handle both string and number ID types +const nodeRevisions = new Map>() + +/** + * Get or create a revision ref for a specific node. + * Each node has its own independent ref, so updates to one won't trigger others. + */ +const getNodeRevisionRef = (nodeId: string | number): Ref => { + const key = String(nodeId) + let rev = nodeRevisions.get(key) + if (!rev) { + rev = ref(0) + nodeRevisions.set(key, rev) + } + return rev +} + +// WeakMaps avoid memory leaks when nodes are removed. +type CacheEntry = { sig: string; label: string } +type InflightEntry = { sig: string; promise: Promise } + +const cache = new WeakMap() +const desiredSig = new WeakMap() +const inflight = new WeakMap() + +const DEBUG_JSONATA_PRICING = false + +const scheduleEvaluation = ( + node: LGraphNode, + rule: CompiledJsonataPricingRule, + ctx: JsonataEvalContext, + sig: string +) => { + desiredSig.set(node, sig) + + const running = inflight.get(node) + if (running && running.sig === sig) return + + if (!rule._compiled) return + + const nodeName = getNodeConstructorData(node)?.name ?? '' + + const promise = Promise.resolve(rule._compiled.evaluate(ctx)) + .then((res) => { + const label = formatPricingResult(res, rule.result_defaults ?? {}) + + // Ignore stale results: if the node changed while we were evaluating, + // desiredSig will no longer match. + if (desiredSig.get(node) !== sig) return + + cache.set(node, { sig, label }) + + if (DEBUG_JSONATA_PRICING) { + console.warn('[pricing/jsonata] resolved', nodeName, { + sig, + res, + label + }) + } + }) + .catch((err) => { + if (process.env.NODE_ENV === 'development') { + console.warn('[pricing/jsonata] evaluation failed', nodeName, err) + } + + // Cache empty to avoid retry-spam for same signature + if (desiredSig.get(node) === sig) { + cache.set(node, { sig, label: '' }) + } + }) + .finally(() => { + const cur = inflight.get(node) + if (cur && cur.sig === sig) inflight.delete(node) + + if (LiteGraph.vueNodesMode) { + // VueNodes mode: bump per-node revision (only this node re-renders) + getNodeRevisionRef(node.id).value++ + } else { + // Nodes 1.0 mode: bump global tick to trigger setDirtyCanvas + pricingTick.value++ + } + }) + + inflight.set(node, { sig, promise }) +} + +/** + * Get the pricing rule for a node from its nodeData.price_badge field. + */ +const getRuleForNode = ( + node: LGraphNode +): CompiledJsonataPricingRule | undefined => { + const nodeData = getNodeConstructorData(node) + if (!nodeData?.api_node) return undefined + + const nodeName = nodeData?.name ?? '' + const priceBadge = nodeData?.price_badge + + if (!priceBadge) return undefined + + const compiled = getCompiledRuleForNodeType(nodeName, priceBadge) + return compiled ?? undefined +} + +// ----------------------------- +// Public composable API +// ----------------------------- +export const useNodePricing = () => { + /** + * Sync getter: + * - returns cached label for the current node signature when available + * - schedules async evaluation when needed + * - remains non-fatal on errors (returns safe fallback '') + */ + const getNodeDisplayPrice = (node: LGraphNode): string => { + // Make this function reactive: when async evaluation completes, we bump pricingTick, + // which causes this getter to recompute in Vue render/computed contexts. + void pricingTick.value + + const nodeData = getNodeConstructorData(node) + if (!nodeData?.api_node) return '' + + const rule = getRuleForNode(node) + if (!rule) return '' + if (rule.engine !== 'jsonata') return '' + if (!rule._compiled) return '' + + const ctx = buildJsonataContext(node, rule) + const sig = buildSignature(ctx, rule) + + const cached = cache.get(node) + if (cached && cached.sig === sig) { + return cached.label + } + + // Cache miss: start async evaluation. + // Return last-known label (if any) to avoid flicker; otherwise return empty. + scheduleEvaluation(node, rule, ctx, sig) + return cached?.label ?? '' + } + + /** + * Expose raw pricing config for tooling/debug UI. + * (Strips compiled expression from returned object.) + */ + const getNodePricingConfig = (node: LGraphNode) => { + const rule = getRuleForNode(node) + if (!rule) return undefined + const { _compiled, ...config } = rule + return config + } + + /** + * Caller compatibility helper: + * returns union of widget dependencies + input dependencies for a node type. + */ + const getRelevantWidgetNames = (nodeType: string): string[] => { + const nodeDefStore = useNodeDefStore() + const nodeDef = nodeDefStore.nodeDefsByName[nodeType] + if (!nodeDef) return [] + + const priceBadge = nodeDef.price_badge + if (!priceBadge) return [] + + const dependsOn = priceBadge.depends_on ?? { + widgets: [], + inputs: [], + input_groups: [] + } + + // Extract widget names + const widgetNames = (dependsOn.widgets ?? []).map((w) => w.name) + + // Keep stable output (dedupe while preserving order) + const out: string[] = [] + for (const n of [ + ...widgetNames, + ...(dependsOn.inputs ?? []), + ...(dependsOn.input_groups ?? []) + ]) { + if (!out.includes(n)) out.push(n) + } + return out + } + + /** + * Check if a node type has dynamic pricing (depends on widgets, inputs, or input_groups). + */ + const hasDynamicPricing = (nodeType: string): boolean => { + const nodeDefStore = useNodeDefStore() + const nodeDef = nodeDefStore.nodeDefsByName[nodeType] + if (!nodeDef) return false + + const priceBadge = nodeDef.price_badge + if (!priceBadge) return false + + const dependsOn = priceBadge.depends_on + if (!dependsOn) return false + + return ( + (dependsOn.widgets?.length ?? 0) > 0 || + (dependsOn.inputs?.length ?? 0) > 0 || + (dependsOn.input_groups?.length ?? 0) > 0 + ) + } + + /** + * Get input_groups prefixes for a node type (for watching connection changes). + */ + const getInputGroupPrefixes = (nodeType: string): string[] => { + const nodeDefStore = useNodeDefStore() + const nodeDef = nodeDefStore.nodeDefsByName[nodeType] + if (!nodeDef) return [] + + const priceBadge = nodeDef.price_badge + if (!priceBadge) return [] + + return priceBadge.depends_on?.input_groups ?? [] + } + + /** + * Get regular input names for a node type (for watching connection changes). + */ + const getInputNames = (nodeType: string): string[] => { + const nodeDefStore = useNodeDefStore() + const nodeDef = nodeDefStore.nodeDefsByName[nodeType] + if (!nodeDef) return [] + + const priceBadge = nodeDef.price_badge + if (!priceBadge) return [] + + return priceBadge.depends_on?.inputs ?? [] + } + + /** + * Trigger price recalculation for a node (call when inputs change). + * Forces re-evaluation by calling getNodeDisplayPrice which will detect + * the signature change and schedule a new evaluation. + */ + const triggerPriceRecalculation = (node: LGraphNode): void => { + const nodeData = getNodeConstructorData(node) + if (!nodeData?.api_node) return + + // Call getNodeDisplayPrice to trigger evaluation if signature changed + getNodeDisplayPrice(node) } return { getNodeDisplayPrice, getNodePricingConfig, - getRelevantWidgetNames + getRelevantWidgetNames, + hasDynamicPricing, + getInputGroupPrefixes, + getInputNames, + getNodeRevisionRef, // Each node has its own independent ref, so updates to one won't trigger others + triggerPriceRecalculation, + pricingRevision: readonly(pricingTick) // reactive invalidation signal } } diff --git a/tests-ui/tests/composables/node/useCreditsBadge.test.ts b/src/composables/node/usePriceBadge.test.ts similarity index 78% rename from tests-ui/tests/composables/node/useCreditsBadge.test.ts rename to src/composables/node/usePriceBadge.test.ts index 72bb92338..d646dd962 100644 --- a/tests-ui/tests/composables/node/useCreditsBadge.test.ts +++ b/src/composables/node/usePriceBadge.test.ts @@ -1,10 +1,8 @@ import { describe, expect, vi } from 'vitest' import { LGraphNode } from '@/lib/litegraph/src/litegraph' -import type { LGraphBadge } from '@/lib/litegraph/src/LGraphBadge' -import type { LGraphIcon } from '@/lib/litegraph/src/LGraphIcon' -import { subgraphTest } from '../../litegraph/subgraph/fixtures/subgraphFixtures' +import { subgraphTest } from '@/lib/litegraph/src/subgraph/__fixtures__/subgraphFixtures' import { usePriceBadge } from '@/composables/node/usePriceBadge' @@ -17,15 +15,10 @@ vi.mock('@/stores/workspace/colorPaletteStore', () => ({ }) })) -const { updateSubgraphCredits } = usePriceBadge() +const { updateSubgraphCredits, getCreditsBadge } = usePriceBadge() const mockNode = new LGraphNode('mock node') -const mockIcon: Partial = { unicode: '\ue96b' } -const badge: Partial = { - icon: mockIcon as LGraphIcon, - text: '$0.05/Run' -} -mockNode.badges = [badge as LGraphBadge] +mockNode.badges = [getCreditsBadge('$0.05/Run')] function getBadgeText(node: LGraphNode): string { const badge = node.badges[0] diff --git a/src/composables/node/usePriceBadge.ts b/src/composables/node/usePriceBadge.ts index 963ca8b4a..93f396a5a 100644 --- a/src/composables/node/usePriceBadge.ts +++ b/src/composables/node/usePriceBadge.ts @@ -4,6 +4,10 @@ import { LGraphBadge } from '@/lib/litegraph/src/litegraph' import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore' import { adjustColor } from '@/utils/colorUtil' +const componentIconSvg = new Image() +componentIconSvg.src = + "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='oklch(83.01%25 0.163 83.16)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M15.536 11.293a1 1 0 0 0 0 1.414l2.376 2.377a1 1 0 0 0 1.414 0l2.377-2.377a1 1 0 0 0 0-1.414l-2.377-2.377a1 1 0 0 0-1.414 0zm-13.239 0a1 1 0 0 0 0 1.414l2.377 2.377a1 1 0 0 0 1.414 0l2.377-2.377a1 1 0 0 0 0-1.414L6.088 8.916a1 1 0 0 0-1.414 0zm6.619 6.619a1 1 0 0 0 0 1.415l2.377 2.376a1 1 0 0 0 1.414 0l2.377-2.376a1 1 0 0 0 0-1.415l-2.377-2.376a1 1 0 0 0-1.414 0zm0-13.238a1 1 0 0 0 0 1.414l2.377 2.376a1 1 0 0 0 1.414 0l2.377-2.376a1 1 0 0 0 0-1.414l-2.377-2.377a1 1 0 0 0-1.414 0z'/%3E%3C/svg%3E" + export const usePriceBadge = () => { function updateSubgraphCredits(node: LGraphNode) { if (!node.isSubgraphNode()) return @@ -33,26 +37,19 @@ export const usePriceBadge = () => { } function isCreditsBadge(badge: LGraphBadge | (() => LGraphBadge)): boolean { - return ( - (typeof badge === 'function' ? badge() : badge).icon?.unicode === '\ue96b' - ) + const badgeInstance = typeof badge === 'function' ? badge() : badge + return badgeInstance.icon?.image === componentIconSvg } const colorPaletteStore = useColorPaletteStore() function getCreditsBadge(price: string): LGraphBadge { const isLightTheme = colorPaletteStore.completedActivePalette.light_theme + return new LGraphBadge({ text: price, iconOptions: { - unicode: '\ue96b', - fontFamily: 'PrimeIcons', - color: isLightTheme - ? adjustColor('#FABC25', { lightness: 0.5 }) - : '#FABC25', - bgColor: isLightTheme - ? adjustColor('#654020', { lightness: 0.5 }) - : '#654020', - fontSize: 8 + image: componentIconSvg, + size: 8 }, fgColor: colorPaletteStore.completedActivePalette.colors.litegraph_base diff --git a/tests-ui/tests/composables/useWatchWidget.test.ts b/src/composables/node/useWatchWidget.test.ts similarity index 87% rename from tests-ui/tests/composables/useWatchWidget.test.ts rename to src/composables/node/useWatchWidget.test.ts index 61363a3f1..346d838a3 100644 --- a/tests-ui/tests/composables/useWatchWidget.test.ts +++ b/src/composables/node/useWatchWidget.test.ts @@ -3,11 +3,12 @@ import { nextTick } from 'vue' import { useComputedWithWidgetWatch } from '@/composables/node/useWatchWidget' import type { LGraphNode } from '@/lib/litegraph/src/litegraph' +import { createMockLGraphNode } from '@/utils/__tests__/litegraphTestUtils' // Mock useChainCallback vi.mock('@/composables/functional/useChainCallback', () => ({ useChainCallback: vi.fn((original, newCallback) => { - return function (this: any, ...args: any[]) { + return function (this: unknown, ...args: unknown[]) { original?.call(this, ...args) newCallback.call(this, ...args) } @@ -18,11 +19,12 @@ describe('useComputedWithWidgetWatch', () => { const createMockNode = ( widgets: Array<{ name: string - value: any - callback?: (...args: any[]) => void + value: unknown + callback?: (...args: unknown[]) => void }> = [] - ) => { - const mockNode = { + ): LGraphNode => { + const baseNode = createMockLGraphNode() + return Object.assign(baseNode, { widgets: widgets.map((widget) => ({ name: widget.name, value: widget.value, @@ -31,9 +33,7 @@ describe('useComputedWithWidgetWatch', () => { graph: { setDirtyCanvas: vi.fn() } - } as unknown as LGraphNode - - return mockNode + }) } it('should create a reactive computed that responds to widget changes', async () => { @@ -59,9 +59,9 @@ describe('useComputedWithWidgetWatch', () => { // Change widget value and trigger callback const widthWidget = mockNode.widgets?.find((w) => w.name === 'width') - if (widthWidget) { + if (widthWidget && widthWidget.callback) { widthWidget.value = 150 - ;(widthWidget.callback as any)?.() + widthWidget.callback(widthWidget.value) } await nextTick() @@ -89,9 +89,9 @@ describe('useComputedWithWidgetWatch', () => { // Change observed widget const widthWidget = mockNode.widgets?.find((w) => w.name === 'width') - if (widthWidget) { + if (widthWidget && widthWidget.callback) { widthWidget.value = 150 - ;(widthWidget.callback as any)?.() + widthWidget.callback(widthWidget.value) } await nextTick() @@ -117,9 +117,9 @@ describe('useComputedWithWidgetWatch', () => { // Change widget value const widget = mockNode.widgets?.[0] - if (widget) { + if (widget && widget.callback) { widget.value = 20 - ;(widget.callback as any)?.() + widget.callback(widget.value) } await nextTick() @@ -139,9 +139,9 @@ describe('useComputedWithWidgetWatch', () => { // Change widget value const widget = mockNode.widgets?.[0] - if (widget) { + if (widget && widget.callback) { widget.value = 20 - ;(widget.callback as any)?.() + widget.callback(widget.value) } await nextTick() @@ -171,8 +171,8 @@ describe('useComputedWithWidgetWatch', () => { // Trigger widget callback const widget = mockNode.widgets?.[0] - if (widget) { - ;(widget.callback as any)?.() + if (widget && widget.callback) { + widget.callback(widget.value) } await nextTick() diff --git a/tests-ui/tests/composables/useCompletionSummary.test.ts b/src/composables/queue/useCompletionSummary.test.ts similarity index 100% rename from tests-ui/tests/composables/useCompletionSummary.test.ts rename to src/composables/queue/useCompletionSummary.test.ts diff --git a/src/composables/queue/useCompletionSummary.ts b/src/composables/queue/useCompletionSummary.ts index ea240c430..4964664a2 100644 --- a/src/composables/queue/useCompletionSummary.ts +++ b/src/composables/queue/useCompletionSummary.ts @@ -57,8 +57,8 @@ export const useCompletionSummary = () => { } if (prev && !active) { const start = lastActiveStartTs.value ?? 0 - const finished = queueStore.historyTasks.filter((t: any) => { - const ts: number | undefined = t.executionEndTimestamp + const finished = queueStore.historyTasks.filter((t) => { + const ts = t.executionEndTimestamp return typeof ts === 'number' && ts >= start }) diff --git a/src/composables/queue/useJobActions.ts b/src/composables/queue/useJobActions.ts new file mode 100644 index 000000000..d350503d9 --- /dev/null +++ b/src/composables/queue/useJobActions.ts @@ -0,0 +1,59 @@ +import { computed, toValue } from 'vue' +import type { MaybeRefOrGetter } from 'vue' +import { useI18n } from 'vue-i18n' + +import { useErrorHandling } from '@/composables/useErrorHandling' +import type { JobListItem } from '@/composables/queue/useJobList' +import { useJobMenu } from '@/composables/queue/useJobMenu' +import type { JobState } from '@/types/queue' + +export type JobAction = { + icon: string + label: string + variant: 'destructive' | 'secondary' | 'textonly' +} + +export function useJobActions( + job: MaybeRefOrGetter +) { + const { t } = useI18n() + const { wrapWithErrorHandlingAsync } = useErrorHandling() + const { cancelJob } = useJobMenu() + + const cancelAction: JobAction = { + icon: 'icon-[lucide--x]', + label: t('sideToolbar.queueProgressOverlay.cancelJobTooltip'), + variant: 'destructive' + } + + const cancellableStates: JobState[] = ['pending', 'initialization', 'running'] + + const jobRef = computed(() => toValue(job) ?? null) + + const canCancelJob = computed(() => { + const currentJob = jobRef.value + if (!currentJob) { + return false + } + + return ( + currentJob.showClear !== false && + cancellableStates.includes(currentJob.state) + ) + }) + + const runCancelJob = wrapWithErrorHandlingAsync(async () => { + const currentJob = jobRef.value + if (!currentJob) { + return + } + + await cancelJob(currentJob) + }) + + return { + cancelAction, + canCancelJob, + runCancelJob + } +} diff --git a/tests-ui/tests/composables/useJobList.test.ts b/src/composables/queue/useJobList.test.ts similarity index 85% rename from tests-ui/tests/composables/useJobList.test.ts rename to src/composables/queue/useJobList.test.ts index 36a14162b..0aaaa892c 100644 --- a/tests-ui/tests/composables/useJobList.test.ts +++ b/src/composables/queue/useJobList.test.ts @@ -5,6 +5,9 @@ import type { Ref } from 'vue' import { useJobList } from '@/composables/queue/useJobList' import type { JobState } from '@/types/queue' +import { buildJobDisplay } from '@/utils/queueDisplay' +import type { BuildJobDisplayCtx } from '@/utils/queueDisplay' +import type { TaskItemImpl } from '@/stores/queueStore' type TestTask = { promptId: string @@ -13,7 +16,7 @@ type TestTask = { executionTime?: number executionEndTimestamp?: number createTime?: number - workflow?: { id?: string } + workflowId?: string } const translations: Record = { @@ -43,19 +46,8 @@ vi.mock('vue-i18n', () => ({ } })) -let stMock: ReturnType -const ensureStMock = () => { - if (!stMock) { - stMock = vi.fn( - (key: string, fallback?: string) => `i18n(${key})-${fallback}` - ) - } - return stMock -} vi.mock('@/i18n', () => ({ - st: (...args: any[]) => { - return ensureStMock()(...args) - } + st: vi.fn((key: string, fallback?: string) => `i18n(${key})-${fallback}`) })) let totalPercent: Ref @@ -75,40 +67,24 @@ vi.mock('@/composables/queue/useQueueProgress', () => ({ } })) -let buildJobDisplayMock: ReturnType -const ensureBuildDisplayMock = () => { - if (!buildJobDisplayMock) { - buildJobDisplayMock = vi.fn((task: any, state: JobState, options: any) => ({ +vi.mock('@/utils/queueDisplay', () => ({ + buildJobDisplay: vi.fn( + (task: TaskItemImpl, state: JobState, options: BuildJobDisplayCtx) => ({ primary: `Job ${task.promptId}`, secondary: `${state} meta`, iconName: `${state}-icon`, iconImageUrl: undefined, showClear: state === 'failed', options - })) - } - return buildJobDisplayMock -} -vi.mock('@/utils/queueDisplay', () => ({ - buildJobDisplay: (...args: any[]) => { - return ensureBuildDisplayMock()(...args) - } + }) + ) })) -let jobStateFromTaskMock: ReturnType -const ensureJobStateMock = () => { - if (!jobStateFromTaskMock) { - jobStateFromTaskMock = vi.fn( - (task: TestTask, isInitializing?: boolean): JobState => - task.mockState ?? (isInitializing ? 'running' : 'completed') - ) - } - return jobStateFromTaskMock -} vi.mock('@/utils/queueUtil', () => ({ - jobStateFromTask: (...args: any[]) => { - return ensureJobStateMock()(...args) - } + jobStateFromTask: vi.fn( + (task: TestTask, isInitializing?: boolean): JobState => + task.mockState ?? (isInitializing ? 'running' : 'completed') + ) })) let queueStoreMock: { @@ -137,7 +113,7 @@ let executionStoreMock: { executingNode: null | { title?: string; type?: string } isPromptInitializing: (promptId?: string | number) => boolean } -let isPromptInitializingMock: ReturnType +let isPromptInitializingMock: (promptId?: string | number) => boolean const ensureExecutionStore = () => { if (!isPromptInitializingMock) { isPromptInitializingMock = vi.fn(() => false) @@ -185,7 +161,7 @@ const createTask = ( executionTime: overrides.executionTime, executionEndTimestamp: overrides.executionEndTimestamp, createTime: overrides.createTime, - workflow: overrides.workflow + workflowId: overrides.workflowId }) const mountUseJobList = () => { @@ -221,13 +197,9 @@ const resetStores = () => { localeRef.value = 'en-US' tMock.mockClear() - if (stMock) stMock.mockClear() - if (buildJobDisplayMock) buildJobDisplayMock.mockClear() - if (jobStateFromTaskMock) jobStateFromTaskMock.mockClear() - if (isPromptInitializingMock) { - isPromptInitializingMock.mockReset() - isPromptInitializingMock.mockReturnValue(false) + vi.mocked(isPromptInitializingMock).mockReset() + vi.mocked(isPromptInitializingMock).mockReturnValue(false) } } @@ -240,6 +212,7 @@ describe('useJobList', () => { let api: ReturnType | null = null beforeEach(() => { + vi.resetAllMocks() resetStores() wrapper?.unmount() wrapper = null @@ -270,18 +243,18 @@ describe('useJobList', () => { await flush() jobItems.value - expect(buildJobDisplayMock).toHaveBeenCalledWith( + expect(buildJobDisplay).toHaveBeenCalledWith( expect.anything(), 'pending', expect.objectContaining({ showAddedHint: true }) ) - buildJobDisplayMock.mockClear() + vi.mocked(buildJobDisplay).mockClear() await vi.advanceTimersByTimeAsync(3000) await flush() jobItems.value - expect(buildJobDisplayMock).toHaveBeenCalledWith( + expect(buildJobDisplay).toHaveBeenCalledWith( expect.anything(), 'pending', expect.objectContaining({ showAddedHint: false }) @@ -303,13 +276,13 @@ describe('useJobList', () => { await flush() expect(vi.getTimerCount()).toBe(0) - buildJobDisplayMock.mockClear() + vi.mocked(buildJobDisplay).mockClear() queueStoreMock.pendingTasks = [ createTask({ promptId: taskId, queueIndex: 2, mockState: 'pending' }) ] await flush() jobItems.value - expect(buildJobDisplayMock).toHaveBeenCalledWith( + expect(buildJobDisplay).toHaveBeenCalledWith( expect.anything(), 'pending', expect.objectContaining({ showAddedHint: true }) @@ -332,24 +305,40 @@ describe('useJobList', () => { expect(vi.getTimerCount()).toBe(0) }) - it('sorts all tasks by queue index descending', async () => { + it('sorts all tasks by create time', async () => { queueStoreMock.pendingTasks = [ - createTask({ promptId: 'p', queueIndex: 1, mockState: 'pending' }) + createTask({ + promptId: 'p', + queueIndex: 1, + mockState: 'pending', + createTime: 3000 + }) ] queueStoreMock.runningTasks = [ - createTask({ promptId: 'r', queueIndex: 5, mockState: 'running' }) + createTask({ + promptId: 'r', + queueIndex: 5, + mockState: 'running', + createTime: 2000 + }) ] queueStoreMock.historyTasks = [ - createTask({ promptId: 'h', queueIndex: 3, mockState: 'completed' }) + createTask({ + promptId: 'h', + queueIndex: 3, + mockState: 'completed', + createTime: 1000, + executionEndTimestamp: 5000 + }) ] const { allTasksSorted } = initComposable() await flush() expect(allTasksSorted.value.map((task) => task.promptId)).toEqual([ + 'p', 'r', - 'h', - 'p' + 'h' ]) }) @@ -387,13 +376,13 @@ describe('useJobList', () => { promptId: 'wf-1', queueIndex: 2, mockState: 'pending', - workflow: { id: 'workflow-1' } + workflowId: 'workflow-1' }), createTask({ promptId: 'wf-2', queueIndex: 1, mockState: 'pending', - workflow: { id: 'workflow-2' } + workflowId: 'workflow-2' }) ] diff --git a/src/composables/queue/useJobList.ts b/src/composables/queue/useJobList.ts index 290cabc68..9d5a73e50 100644 --- a/src/composables/queue/useJobList.ts +++ b/src/composables/queue/useJobList.ts @@ -1,8 +1,10 @@ +import { orderBy } from 'es-toolkit/array' import { computed, onUnmounted, ref, watch } from 'vue' import { useI18n } from 'vue-i18n' import { useQueueProgress } from '@/composables/queue/useQueueProgress' import { st } from '@/i18n' +import { isCloud } from '@/platform/distribution/types' import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore' import { useExecutionStore } from '@/stores/executionStore' import { useQueueStore } from '@/stores/queueStore' @@ -37,7 +39,7 @@ export type JobListItem = { iconName?: string iconImageUrl?: string showClear?: boolean - taskRef?: any + taskRef?: TaskItemImpl progressTotalPercent?: number progressCurrentPercent?: number runningNodeName?: string @@ -196,13 +198,15 @@ export function useJobList() { const selectedWorkflowFilter = ref<'all' | 'current'>('all') const selectedSortMode = ref('mostRecent') + const mostRecentTimestamp = (task: TaskItemImpl) => task.createTime ?? 0 + const allTasksSorted = computed(() => { const all = [ ...queueStore.pendingTasks, ...queueStore.runningTasks, ...queueStore.historyTasks ] - return all.sort((a, b) => b.queueIndex - a.queueIndex) + return orderBy(all, [mostRecentTimestamp], ['desc']) }) const tasksWithJobState = computed(() => @@ -237,7 +241,7 @@ export function useJobList() { const activeId = workflowStore.activeWorkflow?.activeState?.id if (!activeId) return [] entries = entries.filter(({ task }) => { - const wid = task.workflow?.id + const wid = task.workflowId return !!wid && wid === activeId }) } @@ -263,7 +267,8 @@ export function useJobList() { totalPercent: isActive ? totalPercent.value : undefined, currentNodePercent: isActive ? currentNodePercent.value : undefined, currentNodeName: isActive ? currentNodeName.value : undefined, - showAddedHint + showAddedHint, + isCloud }) return { diff --git a/tests-ui/tests/composables/useJobMenu.test.ts b/src/composables/queue/useJobMenu.test.ts similarity index 79% rename from tests-ui/tests/composables/useJobMenu.test.ts rename to src/composables/queue/useJobMenu.test.ts index 0bd876951..e07889022 100644 --- a/tests-ui/tests/composables/useJobMenu.test.ts +++ b/src/composables/queue/useJobMenu.test.ts @@ -5,15 +5,24 @@ import type { Ref } from 'vue' import type { JobListItem } from '@/composables/queue/useJobList' import type { MenuEntry } from '@/composables/queue/useJobMenu' +vi.mock('@/platform/distribution/types', () => ({ + isCloud: false +})) + const downloadFileMock = vi.fn() vi.mock('@/base/common/downloadUtil', () => ({ - downloadFile: (...args: any[]) => downloadFileMock(...args) + downloadFile: (url: string, filename?: string) => { + if (filename === undefined) { + return downloadFileMock(url) + } + return downloadFileMock(url, filename) + } })) const copyToClipboardMock = vi.fn() vi.mock('@/composables/useCopyToClipboard', () => ({ useCopyToClipboard: () => ({ - copyToClipboard: (...args: any[]) => copyToClipboardMock(...args) + copyToClipboard: (text: string) => copyToClipboardMock(text) }) })) @@ -26,8 +35,8 @@ vi.mock('@/i18n', () => ({ const mapTaskOutputToAssetItemMock = vi.fn() vi.mock('@/platform/assets/composables/media/assetMappers', () => ({ - mapTaskOutputToAssetItem: (...args: any[]) => - mapTaskOutputToAssetItemMock(...args) + mapTaskOutputToAssetItem: (taskItem: TaskItemImpl, output: ResultItemImpl) => + mapTaskOutputToAssetItemMock(taskItem, output) })) const mediaAssetActionsMock = { @@ -55,24 +64,28 @@ const workflowStoreMock = { createTemporary: vi.fn() } vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({ - useWorkflowStore: () => workflowStoreMock + useWorkflowStore: () => workflowStoreMock, + ComfyWorkflow: class {} })) const interruptMock = vi.fn() const deleteItemMock = vi.fn() vi.mock('@/scripts/api', () => ({ api: { - interrupt: (...args: any[]) => interruptMock(...args), - deleteItem: (...args: any[]) => deleteItemMock(...args) + interrupt: (runningPromptId: string | null) => + interruptMock(runningPromptId), + deleteItem: (type: string, id: string) => deleteItemMock(type, id) } })) const downloadBlobMock = vi.fn() vi.mock('@/scripts/utils', () => ({ - downloadBlob: (...args: any[]) => downloadBlobMock(...args) + downloadBlob: (filename: string, blob: Blob) => + downloadBlobMock(filename, blob) })) const dialogServiceMock = { + showErrorDialog: vi.fn(), showExecutionErrorDialog: vi.fn(), prompt: vi.fn() } @@ -88,11 +101,14 @@ vi.mock('@/services/litegraphService', () => ({ useLitegraphService: () => litegraphServiceMock })) -const nodeDefStoreMock = { - nodeDefsByName: {} as Record +const nodeDefStoreMock: { + nodeDefsByName: Record> +} = { + nodeDefsByName: {} } vi.mock('@/stores/nodeDefStore', () => ({ - useNodeDefStore: () => nodeDefStoreMock + useNodeDefStore: () => nodeDefStoreMock, + ComfyNodeDefImpl: class {} })) const queueStoreMock = { @@ -103,9 +119,22 @@ vi.mock('@/stores/queueStore', () => ({ useQueueStore: () => queueStoreMock })) +const executionStoreMock = { + clearInitializationByPromptId: vi.fn() +} +vi.mock('@/stores/executionStore', () => ({ + useExecutionStore: () => executionStoreMock +})) + +const getJobWorkflowMock = vi.fn() +vi.mock('@/services/jobOutputCache', () => ({ + getJobWorkflow: (jobId: string) => getJobWorkflowMock(jobId) +})) + const createAnnotatedPathMock = vi.fn() vi.mock('@/utils/createAnnotatedPath', () => ({ - createAnnotatedPath: (...args: any[]) => createAnnotatedPathMock(...args) + createAnnotatedPath: (filename: string, subfolder: string, type: string) => + createAnnotatedPathMock(filename, subfolder, type) })) const appendJsonExtMock = vi.fn((value: string) => @@ -117,13 +146,23 @@ vi.mock('@/utils/formatUtil', () => ({ })) import { useJobMenu } from '@/composables/queue/useJobMenu' +import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore' +import type { ResultItemImpl, TaskItemImpl } from '@/stores/queueStore' -const createJobItem = (overrides: Partial = {}): JobListItem => ({ +type MockTaskRef = Record + +type TestJobListItem = Omit & { + taskRef?: MockTaskRef +} + +const createJobItem = ( + overrides: Partial = {} +): JobListItem => ({ id: overrides.id ?? 'job-1', title: overrides.title ?? 'Test job', meta: overrides.meta ?? 'meta', state: overrides.state ?? 'completed', - taskRef: overrides.taskRef, + taskRef: overrides.taskRef as TaskItemImpl | undefined, iconName: overrides.iconName, iconImageUrl: overrides.iconImageUrl, showClear: overrides.showClear, @@ -166,10 +205,12 @@ describe('useJobMenu', () => { })) createAnnotatedPathMock.mockReturnValue('annotated-path') nodeDefStoreMock.nodeDefsByName = { - LoadImage: { id: 'LoadImage' }, - LoadVideo: { id: 'LoadVideo' }, - LoadAudio: { id: 'LoadAudio' } + LoadImage: { name: 'LoadImage' }, + LoadVideo: { name: 'LoadVideo' }, + LoadAudio: { name: 'LoadAudio' } } + // Default: no workflow available via lazy loading + getJobWorkflowMock.mockResolvedValue(undefined) }) const setCurrentItem = (item: JobListItem | null) => { @@ -179,10 +220,13 @@ describe('useJobMenu', () => { it('opens workflow when workflow data exists', async () => { const { openJobWorkflow } = mountJobMenu() const workflow = { nodes: [] } - setCurrentItem(createJobItem({ id: '55', taskRef: { workflow } })) + // Mock lazy loading via fetchJobDetail + extractWorkflow + getJobWorkflowMock.mockResolvedValue(workflow) + setCurrentItem(createJobItem({ id: '55' })) await openJobWorkflow() + expect(getJobWorkflowMock).toHaveBeenCalledWith('55') expect(workflowStoreMock.createTemporary).toHaveBeenCalledWith( 'Job 55.json', workflow @@ -225,7 +269,7 @@ describe('useJobMenu', () => { ['initialization', interruptMock, deleteItemMock] ])('cancels %s job via interrupt', async (state) => { const { cancelJob } = mountJobMenu() - setCurrentItem(createJobItem({ state: state as any })) + setCurrentItem(createJobItem({ state: state as JobListItem['state'] })) await cancelJob() @@ -257,11 +301,12 @@ describe('useJobMenu', () => { it('copies error message from failed job entry', async () => { const { jobMenuEntries } = mountJobMenu() - const error = { exception_message: 'boom' } setCurrentItem( createJobItem({ state: 'failed', - taskRef: { status: { messages: [['execution_error', error]] } } as any + taskRef: { + errorMessage: 'Something went wrong' + } as Partial }) ) @@ -269,31 +314,77 @@ describe('useJobMenu', () => { const entry = findActionEntry(jobMenuEntries.value, 'copy-error') await entry?.onClick?.() - expect(copyToClipboardMock).toHaveBeenCalledWith('boom') + expect(copyToClipboardMock).toHaveBeenCalledWith('Something went wrong') }) - it('reports error via dialog when entry triggered', async () => { + it('reports error via rich dialog when execution_error available', async () => { + const executionError = { + prompt_id: 'job-1', + timestamp: 12345, + node_id: '5', + node_type: 'KSampler', + executed: ['1', '2'], + exception_message: 'CUDA out of memory', + exception_type: 'RuntimeError', + traceback: ['line 1', 'line 2'], + current_inputs: {}, + current_outputs: {} + } const { jobMenuEntries } = mountJobMenu() - const error = { exception_message: 'bad', extra: 1 } setCurrentItem( createJobItem({ state: 'failed', - taskRef: { status: { messages: [['execution_error', error]] } } as any + taskRef: { + errorMessage: 'CUDA out of memory', + executionError, + createTime: 12345 + } as Partial }) ) await nextTick() const entry = findActionEntry(jobMenuEntries.value, 'report-error') - entry?.onClick?.() + await entry?.onClick?.() + expect(dialogServiceMock.showExecutionErrorDialog).toHaveBeenCalledTimes(1) expect(dialogServiceMock.showExecutionErrorDialog).toHaveBeenCalledWith( - error + executionError ) + expect(dialogServiceMock.showErrorDialog).not.toHaveBeenCalled() + }) + + it('falls back to simple error dialog when no execution_error', async () => { + const { jobMenuEntries } = mountJobMenu() + setCurrentItem( + createJobItem({ + state: 'failed', + taskRef: { + errorMessage: 'Job failed with error' + } as Partial + }) + ) + + await nextTick() + const entry = findActionEntry(jobMenuEntries.value, 'report-error') + await entry?.onClick?.() + + expect(dialogServiceMock.showExecutionErrorDialog).not.toHaveBeenCalled() + expect(dialogServiceMock.showErrorDialog).toHaveBeenCalledTimes(1) + const [errorArg, optionsArg] = + dialogServiceMock.showErrorDialog.mock.calls[0] + expect(errorArg).toBeInstanceOf(Error) + expect(errorArg.message).toBe('Job failed with error') + expect(optionsArg).toEqual({ reportType: 'queueJobError' }) }) it('ignores error actions when message missing', async () => { const { jobMenuEntries } = mountJobMenu() - setCurrentItem(createJobItem({ state: 'failed', taskRef: { status: {} } })) + setCurrentItem( + createJobItem({ + state: 'failed', + taskRef: { errorMessage: undefined } as Partial + }) + ) await nextTick() const copyEntry = findActionEntry(jobMenuEntries.value, 'copy-error') @@ -302,6 +393,7 @@ describe('useJobMenu', () => { await reportEntry?.onClick?.() expect(copyToClipboardMock).not.toHaveBeenCalled() + expect(dialogServiceMock.showErrorDialog).not.toHaveBeenCalled() expect(dialogServiceMock.showExecutionErrorDialog).not.toHaveBeenCalled() }) @@ -438,7 +530,12 @@ describe('useJobMenu', () => { it('ignores add-to-current entry when preview missing entirely', async () => { const { jobMenuEntries } = mountJobMenu() - setCurrentItem(createJobItem({ state: 'completed', taskRef: {} as any })) + setCurrentItem( + createJobItem({ + state: 'completed', + taskRef: {} as Partial + }) + ) await nextTick() const entry = findActionEntry(jobMenuEntries.value, 'add-to-current') @@ -460,29 +557,35 @@ describe('useJobMenu', () => { await nextTick() const entry = findActionEntry(jobMenuEntries.value, 'download') - entry?.onClick?.() + void entry?.onClick?.() expect(downloadFileMock).toHaveBeenCalledWith('https://asset') }) it('ignores download request when preview missing', async () => { const { jobMenuEntries } = mountJobMenu() - setCurrentItem(createJobItem({ state: 'completed', taskRef: {} as any })) + setCurrentItem( + createJobItem({ + state: 'completed', + taskRef: {} as Partial + }) + ) await nextTick() const entry = findActionEntry(jobMenuEntries.value, 'download') - entry?.onClick?.() + void entry?.onClick?.() expect(downloadFileMock).not.toHaveBeenCalled() }) it('exports workflow with default filename when prompting disabled', async () => { + const workflow = { foo: 'bar' } + getJobWorkflowMock.mockResolvedValue(workflow) const { jobMenuEntries } = mountJobMenu() setCurrentItem( createJobItem({ id: '7', - state: 'completed', - taskRef: { workflow: { foo: 'bar' } } + state: 'completed' }) ) @@ -502,11 +605,11 @@ describe('useJobMenu', () => { it('prompts for filename when setting enabled', async () => { settingStoreMock.get.mockReturnValue(true) dialogServiceMock.prompt.mockResolvedValue('custom-name') + getJobWorkflowMock.mockResolvedValue({}) const { jobMenuEntries } = mountJobMenu() setCurrentItem( createJobItem({ - state: 'completed', - taskRef: { workflow: {} } + state: 'completed' }) ) @@ -526,12 +629,12 @@ describe('useJobMenu', () => { it('keeps existing json extension when exporting workflow', async () => { settingStoreMock.get.mockReturnValue(true) dialogServiceMock.prompt.mockResolvedValue('existing.json') + getJobWorkflowMock.mockResolvedValue({ foo: 'bar' }) const { jobMenuEntries } = mountJobMenu() setCurrentItem( createJobItem({ id: '42', - state: 'completed', - taskRef: { workflow: { foo: 'bar' } } + state: 'completed' }) ) @@ -547,11 +650,11 @@ describe('useJobMenu', () => { it('abandons export when prompt cancelled', async () => { settingStoreMock.get.mockReturnValue(true) dialogServiceMock.prompt.mockResolvedValue('') + getJobWorkflowMock.mockResolvedValue({}) const { jobMenuEntries } = mountJobMenu() setCurrentItem( createJobItem({ - state: 'completed', - taskRef: { workflow: {} } + state: 'completed' }) ) @@ -671,7 +774,12 @@ describe('useJobMenu', () => { it('returns failed menu entries with error actions', async () => { const { jobMenuEntries } = mountJobMenu() - setCurrentItem(createJobItem({ state: 'failed', taskRef: { status: {} } })) + setCurrentItem( + createJobItem({ + state: 'failed', + taskRef: { errorMessage: 'Some error' } as Partial + }) + ) await nextTick() expect(jobMenuEntries.value.map((entry) => entry.key)).toEqual([ diff --git a/src/composables/queue/useJobMenu.ts b/src/composables/queue/useJobMenu.ts index db8a1cbc3..b50a24846 100644 --- a/src/composables/queue/useJobMenu.ts +++ b/src/composables/queue/useJobMenu.ts @@ -6,18 +6,17 @@ import { useCopyToClipboard } from '@/composables/useCopyToClipboard' import { st, t } from '@/i18n' import { mapTaskOutputToAssetItem } from '@/platform/assets/composables/media/assetMappers' import { useMediaAssetActions } from '@/platform/assets/composables/useMediaAssetActions' +import { isCloud } from '@/platform/distribution/types' import { useSettingStore } from '@/platform/settings/settingStore' import { useWorkflowService } from '@/platform/workflow/core/services/workflowService' import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore' -import type { - ExecutionErrorWsMessage, - ResultItem, - ResultItemType -} from '@/schemas/apiSchema' +import type { ResultItem, ResultItemType } from '@/schemas/apiSchema' import { api } from '@/scripts/api' import { downloadBlob } from '@/scripts/utils' import { useDialogService } from '@/services/dialogService' +import { getJobWorkflow } from '@/services/jobOutputCache' import { useLitegraphService } from '@/services/litegraphService' +import { useExecutionStore } from '@/stores/executionStore' import { useNodeDefStore } from '@/stores/nodeDefStore' import { useQueueStore } from '@/stores/queueStore' import type { ResultItemImpl, TaskItemImpl } from '@/stores/queueStore' @@ -41,63 +40,78 @@ export type MenuEntry = * @param onInspectAsset Callback to trigger when inspecting a completed job's asset */ export function useJobMenu( - currentMenuItem: () => JobListItem | null, + currentMenuItem: () => JobListItem | null = () => null, onInspectAsset?: (item: JobListItem) => void ) { const workflowStore = useWorkflowStore() const workflowService = useWorkflowService() const queueStore = useQueueStore() + const executionStore = useExecutionStore() const { copyToClipboard } = useCopyToClipboard() const litegraphService = useLitegraphService() const nodeDefStore = useNodeDefStore() const mediaAssetActions = useMediaAssetActions() - const openJobWorkflow = async () => { - const item = currentMenuItem() - if (!item) return - const data = item.taskRef?.workflow + const resolveItem = (item?: JobListItem | null): JobListItem | null => + item ?? currentMenuItem() + + const openJobWorkflow = async (item?: JobListItem | null) => { + const target = resolveItem(item) + if (!target) return + const data = await getJobWorkflow(target.id) if (!data) return - const filename = `Job ${item.id}.json` + const filename = `Job ${target.id}.json` const temp = workflowStore.createTemporary(filename, data) await workflowService.openWorkflow(temp) } - const copyJobId = async () => { - const item = currentMenuItem() - if (!item) return - await copyToClipboard(item.id) + const copyJobId = async (item?: JobListItem | null) => { + const target = resolveItem(item) + if (!target) return + await copyToClipboard(target.id) } - const cancelJob = async () => { - const item = currentMenuItem() - if (!item) return - if (item.state === 'running' || item.state === 'initialization') { - await api.interrupt(item.id) - } else if (item.state === 'pending') { - await api.deleteItem('queue', item.id) + const cancelJob = async (item?: JobListItem | null) => { + const target = resolveItem(item) + if (!target) return + if (target.state === 'running' || target.state === 'initialization') { + if (isCloud) { + await api.deleteItem('queue', target.id) + } else { + await api.interrupt(target.id) + } + } else if (target.state === 'pending') { + await api.deleteItem('queue', target.id) } + executionStore.clearInitializationByPromptId(target.id) await queueStore.update() } - const copyErrorMessage = async () => { - const item = currentMenuItem() - if (!item) return - const msgs = item.taskRef?.status?.messages as any[] | undefined - const err = msgs?.find((m: any) => m?.[0] === 'execution_error')?.[1] as - | ExecutionErrorWsMessage - | undefined - const message = err?.exception_message - if (message) await copyToClipboard(String(message)) + const copyErrorMessage = async (item?: JobListItem | null) => { + const target = resolveItem(item) + const message = target?.taskRef?.errorMessage + if (message) await copyToClipboard(message) } - const reportError = () => { - const item = currentMenuItem() - if (!item) return - const msgs = item.taskRef?.status?.messages as any[] | undefined - const err = msgs?.find((m: any) => m?.[0] === 'execution_error')?.[1] as - | ExecutionErrorWsMessage - | undefined - if (err) useDialogService().showExecutionErrorDialog(err) + const reportError = (item?: JobListItem | null) => { + const target = resolveItem(item) + if (!target) return + + // Use execution_error from list response if available + const executionError = target.taskRef?.executionError + + if (executionError) { + useDialogService().showExecutionErrorDialog(executionError) + return + } + + // Fall back to simple error dialog + const message = target.taskRef?.errorMessage + if (message) { + useDialogService().showErrorDialog(new Error(message), { + reportType: 'queueJobError' + }) + } } // This is very magical only because it matches the respective backend implementation @@ -167,7 +181,7 @@ export function useJobMenu( const exportJobWorkflow = async () => { const item = currentMenuItem() if (!item) return - const data = item.taskRef?.workflow + const data = await getJobWorkflow(item.id) if (!data) return const settingStore = useSettingStore() diff --git a/tests-ui/tests/composables/useQueueProgress.test.ts b/src/composables/queue/useQueueProgress.test.ts similarity index 100% rename from tests-ui/tests/composables/useQueueProgress.test.ts rename to src/composables/queue/useQueueProgress.test.ts diff --git a/src/composables/queue/useResultGallery.test.ts b/src/composables/queue/useResultGallery.test.ts new file mode 100644 index 000000000..8b8522a79 --- /dev/null +++ b/src/composables/queue/useResultGallery.test.ts @@ -0,0 +1,171 @@ +import { createPinia, setActivePinia } from 'pinia' +import { beforeEach, describe, expect, it } from 'vitest' + +import { useResultGallery } from '@/composables/queue/useResultGallery' +import type { JobListItem as JobListViewItem } from '@/composables/queue/useJobList' +import type { JobListItem } from '@/platform/remote/comfyui/jobs/jobTypes' +import { ResultItemImpl, TaskItemImpl } from '@/stores/queueStore' + +const createResultItem = ( + url: string, + supportsPreview = true +): ResultItemImpl => { + const item = new ResultItemImpl({ + filename: url, + subfolder: '', + type: 'output', + nodeId: 'node-1', + mediaType: supportsPreview ? 'images' : 'unknown' + }) + // Override url getter for test matching + Object.defineProperty(item, 'url', { get: () => url }) + Object.defineProperty(item, 'supportsPreview', { get: () => supportsPreview }) + return item +} + +const createMockJob = (id: string, outputsCount = 1): JobListItem => ({ + id, + status: 'completed', + create_time: Date.now(), + preview_output: null, + outputs_count: outputsCount, + priority: 0 +}) + +const createTask = ( + preview?: ResultItemImpl, + allOutputs?: ResultItemImpl[], + outputsCount = 1 +): TaskItemImpl => { + const job = createMockJob( + `task-${Math.random().toString(36).slice(2)}`, + outputsCount + ) + const flatOutputs = allOutputs ?? (preview ? [preview] : []) + return new TaskItemImpl(job, {}, flatOutputs) +} + +const createJobViewItem = ( + id: string, + taskRef?: TaskItemImpl +): JobListViewItem => + ({ + id, + title: `Job ${id}`, + meta: '', + state: 'completed', + showClear: false, + taskRef + }) as JobListViewItem + +describe('useResultGallery', () => { + beforeEach(() => { + setActivePinia(createPinia()) + }) + + it('collects only previewable outputs and preserves their order', async () => { + const previewable = [createResultItem('p-1'), createResultItem('p-2')] + const nonPreviewable = createResultItem('skip-me', false) + const tasks = [ + createTask(previewable[0]), + createTask(nonPreviewable), + createTask(previewable[1]), + createTask() + ] + + const { galleryItems, galleryActiveIndex, onViewItem } = useResultGallery( + () => tasks + ) + + await onViewItem(createJobViewItem('job-1', tasks[0])) + + expect(galleryItems.value).toEqual([previewable[0]]) + expect(galleryActiveIndex.value).toBe(0) + }) + + it('does not change state when there are no previewable tasks', async () => { + const { galleryItems, galleryActiveIndex, onViewItem } = useResultGallery( + () => [] + ) + + await onViewItem(createJobViewItem('job-missing')) + + expect(galleryItems.value).toEqual([]) + expect(galleryActiveIndex.value).toBe(-1) + }) + + it('activates the index that matches the viewed preview URL', async () => { + const previewable = [ + createResultItem('p-1'), + createResultItem('p-2'), + createResultItem('p-3') + ] + const tasks = previewable.map((preview) => createTask(preview)) + + const { galleryItems, galleryActiveIndex, onViewItem } = useResultGallery( + () => tasks + ) + + await onViewItem(createJobViewItem('job-2', tasks[1])) + + expect(galleryItems.value).toEqual([previewable[1]]) + expect(galleryActiveIndex.value).toBe(0) + }) + + it('defaults to the first entry when the clicked job lacks a preview', async () => { + const previewable = [createResultItem('p-1'), createResultItem('p-2')] + const tasks = previewable.map((preview) => createTask(preview)) + + const { galleryItems, galleryActiveIndex, onViewItem } = useResultGallery( + () => tasks + ) + + await onViewItem(createJobViewItem('job-no-preview')) + + expect(galleryItems.value).toEqual(previewable) + expect(galleryActiveIndex.value).toBe(0) + }) + + it('defaults to the first entry when no gallery item matches the preview URL', async () => { + const previewable = [createResultItem('p-1'), createResultItem('p-2')] + const tasks = previewable.map((preview) => createTask(preview)) + + const { galleryItems, galleryActiveIndex, onViewItem } = useResultGallery( + () => tasks + ) + + const taskWithMismatchedPreview = createTask(createResultItem('missing')) + await onViewItem( + createJobViewItem('job-mismatch', taskWithMismatchedPreview) + ) + + expect(galleryItems.value).toEqual([createResultItem('missing')]) + expect(galleryActiveIndex.value).toBe(0) + }) + + it('loads full outputs when task has only preview outputs', async () => { + const previewOutput = createResultItem('preview-1') + const fullOutputs = [ + createResultItem('full-1'), + createResultItem('full-2'), + createResultItem('full-3') + ] + + // Create a task with outputsCount > 1 to trigger lazy loading + const job = createMockJob('task-1', 3) + const task = new TaskItemImpl(job, {}, [previewOutput]) + + // Mock loadFullOutputs to return full outputs + const loadedTask = new TaskItemImpl(job, {}, fullOutputs) + task.loadFullOutputs = async () => loadedTask + + const { galleryItems, galleryActiveIndex, onViewItem } = useResultGallery( + () => [task] + ) + + await onViewItem(createJobViewItem('job-1', task)) + + expect(galleryItems.value).toEqual(fullOutputs) + expect(galleryActiveIndex.value).toBe(0) + }) +}) diff --git a/src/composables/queue/useResultGallery.ts b/src/composables/queue/useResultGallery.ts index 0e9934165..44c6a607d 100644 --- a/src/composables/queue/useResultGallery.ts +++ b/src/composables/queue/useResultGallery.ts @@ -1,27 +1,42 @@ import { ref, shallowRef } from 'vue' import type { JobListItem } from '@/composables/queue/useJobList' -import type { ResultItemImpl } from '@/stores/queueStore' +import { findActiveIndex, getOutputsForTask } from '@/services/jobOutputCache' +import type { ResultItemImpl, TaskItemImpl } from '@/stores/queueStore' /** * Manages result gallery state and activation for queue items. */ -export function useResultGallery(getFilteredTasks: () => any[]) { +export function useResultGallery(getFilteredTasks: () => TaskItemImpl[]) { const galleryActiveIndex = ref(-1) const galleryItems = shallowRef([]) - const onViewItem = (item: JobListItem) => { - const items: ResultItemImpl[] = getFilteredTasks().flatMap((t: any) => { - const preview = t.previewOutput - return preview && preview.supportsPreview ? [preview] : [] - }) + async function onViewItem(item: JobListItem) { + const tasks = getFilteredTasks() + if (!tasks.length) return + + const targetTask = item.taskRef + const targetOutputs = targetTask + ? await getOutputsForTask(targetTask) + : null + + // Request was superseded by a newer one + if (targetOutputs === null && targetTask) return + + // Use target's outputs if available, otherwise fall back to all previews + const items = targetOutputs?.length + ? targetOutputs + : tasks + .map((t) => t.previewOutput) + .filter((o): o is ResultItemImpl => !!o) if (!items.length) return galleryItems.value = items - const activeUrl: string | undefined = item.taskRef?.previewOutput?.url - const idx = activeUrl ? items.findIndex((o) => o.url === activeUrl) : 0 - galleryActiveIndex.value = idx >= 0 ? idx : 0 + galleryActiveIndex.value = findActiveIndex( + items, + item.taskRef?.previewOutput?.url + ) } return { diff --git a/src/composables/sidebarTabs/useAssetsSidebarTab.ts b/src/composables/sidebarTabs/useAssetsSidebarTab.ts index ce3753173..cd8e8d0bb 100644 --- a/src/composables/sidebarTabs/useAssetsSidebarTab.ts +++ b/src/composables/sidebarTabs/useAssetsSidebarTab.ts @@ -1,6 +1,7 @@ import { markRaw } from 'vue' import AssetsSidebarTab from '@/components/sidebar/tabs/AssetsSidebarTab.vue' +import { useQueueStore } from '@/stores/queueStore' import type { SidebarTabExtension } from '@/types/extensionTypes' export const useAssetsSidebarTab = (): SidebarTabExtension => { @@ -11,6 +12,12 @@ export const useAssetsSidebarTab = (): SidebarTabExtension => { tooltip: 'sideToolbar.assets', label: 'sideToolbar.labels.assets', component: markRaw(AssetsSidebarTab), - type: 'vue' + type: 'vue', + iconBadge: () => { + const queueStore = useQueueStore() + return queueStore.pendingTasks.length > 0 + ? queueStore.pendingTasks.length.toString() + : null + } } } diff --git a/tests-ui/tests/composables/BrowserTabTitle.test.ts b/src/composables/useBrowserTabTitle.test.ts similarity index 83% rename from tests-ui/tests/composables/BrowserTabTitle.test.ts rename to src/composables/useBrowserTabTitle.test.ts index 3c0cc623f..d25ec4fd2 100644 --- a/tests-ui/tests/composables/BrowserTabTitle.test.ts +++ b/src/composables/useBrowserTabTitle.test.ts @@ -11,13 +11,28 @@ vi.mock('@/i18n', () => ({ })) // Mock the execution store -const executionStore = reactive({ +const executionStore = reactive<{ + isIdle: boolean + executionProgress: number + executingNode: unknown + executingNodeProgress: number + nodeProgressStates: Record + activePrompt: { + workflow: { + changeTracker: { + activeState: { + nodes: { id: number; type: string }[] + } + } + } + } | null +}>({ isIdle: true, executionProgress: 0, - executingNode: null as any, + executingNode: null, executingNodeProgress: 0, - nodeProgressStates: {} as any, - activePrompt: null as any + nodeProgressStates: {}, + activePrompt: null }) vi.mock('@/stores/executionStore', () => ({ useExecutionStore: () => executionStore @@ -25,15 +40,21 @@ vi.mock('@/stores/executionStore', () => ({ // Mock the setting store const settingStore = reactive({ - get: vi.fn(() => 'Enabled') + get: vi.fn((_key: string) => 'Enabled') }) vi.mock('@/platform/settings/settingStore', () => ({ useSettingStore: () => settingStore })) // Mock the workflow store -const workflowStore = reactive({ - activeWorkflow: null as any +const workflowStore = reactive<{ + activeWorkflow: { + filename: string + isModified: boolean + isPersisted: boolean + } | null +}>({ + activeWorkflow: null }) vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({ useWorkflowStore: () => workflowStore @@ -52,13 +73,13 @@ describe('useBrowserTabTitle', () => { // reset execution store executionStore.isIdle = true executionStore.executionProgress = 0 - executionStore.executingNode = null as any + executionStore.executingNode = null executionStore.executingNodeProgress = 0 executionStore.nodeProgressStates = {} executionStore.activePrompt = null // reset setting and workflow stores - ;(settingStore.get as any).mockReturnValue('Enabled') + vi.mocked(settingStore.get).mockReturnValue('Enabled') workflowStore.activeWorkflow = null workspaceStore.shiftDown = false @@ -74,7 +95,7 @@ describe('useBrowserTabTitle', () => { }) it('sets workflow name as title when workflow exists and menu enabled', async () => { - ;(settingStore.get as any).mockReturnValue('Enabled') + vi.mocked(settingStore.get).mockReturnValue('Enabled') workflowStore.activeWorkflow = { filename: 'myFlow', isModified: false, @@ -88,7 +109,7 @@ describe('useBrowserTabTitle', () => { }) it('adds asterisk for unsaved workflow', async () => { - ;(settingStore.get as any).mockReturnValue('Enabled') + vi.mocked(settingStore.get).mockReturnValue('Enabled') workflowStore.activeWorkflow = { filename: 'myFlow', isModified: true, @@ -102,7 +123,7 @@ describe('useBrowserTabTitle', () => { }) it('hides asterisk when autosave is enabled', async () => { - ;(settingStore.get as any).mockImplementation((key: string) => { + vi.mocked(settingStore.get).mockImplementation((key: string) => { if (key === 'Comfy.Workflow.AutoSave') return 'after delay' if (key === 'Comfy.UseNewMenu') return 'Enabled' return 'Enabled' @@ -118,7 +139,7 @@ describe('useBrowserTabTitle', () => { }) it('hides asterisk while Shift key is held', async () => { - ;(settingStore.get as any).mockImplementation((key: string) => { + vi.mocked(settingStore.get).mockImplementation((key: string) => { if (key === 'Comfy.Workflow.AutoSave') return 'off' if (key === 'Comfy.UseNewMenu') return 'Enabled' return 'Enabled' @@ -137,7 +158,7 @@ describe('useBrowserTabTitle', () => { // Fails when run together with other tests. Suspect to be caused by leaked // state from previous tests. it.skip('disables workflow title when menu disabled', async () => { - ;(settingStore.get as any).mockReturnValue('Disabled') + vi.mocked(settingStore.get).mockReturnValue('Disabled') workflowStore.activeWorkflow = { filename: 'myFlow', isModified: false, diff --git a/tests-ui/tests/composables/useCachedRequest.test.ts b/src/composables/useCachedRequest.test.ts similarity index 94% rename from tests-ui/tests/composables/useCachedRequest.test.ts rename to src/composables/useCachedRequest.test.ts index 8d2a92a23..06d344ae1 100644 --- a/tests-ui/tests/composables/useCachedRequest.test.ts +++ b/src/composables/useCachedRequest.test.ts @@ -3,8 +3,11 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { useCachedRequest } from '@/composables/useCachedRequest' describe('useCachedRequest', () => { - let mockRequestFn: ReturnType - let abortSpy: ReturnType + let mockRequestFn: ( + params: unknown, + signal?: AbortSignal + ) => Promise + let abortSpy: () => void beforeEach(() => { vi.clearAllMocks() @@ -22,7 +25,7 @@ describe('useCachedRequest', () => { ) // Create a mock request function that returns different results based on params - mockRequestFn = vi.fn(async (params: any) => { + mockRequestFn = vi.fn(async (params: unknown) => { // Simulate a request that takes some time await new Promise((resolve) => setTimeout(resolve, 8)) @@ -135,12 +138,18 @@ describe('useCachedRequest', () => { it('should use custom cache key function if provided', async () => { // Create a cache key function that sorts object keys - const cacheKeyFn = (params: any) => { + const cacheKeyFn = (params: unknown) => { if (typeof params !== 'object' || params === null) return String(params) return JSON.stringify( - Object.keys(params) + Object.keys(params as Record) .sort() - .reduce((acc, key) => ({ ...acc, [key]: params[key] }), {}) + .reduce( + (acc, key) => ({ + ...acc, + [key]: (params as Record)[key] + }), + {} + ) ) } diff --git a/src/composables/useCanvasDrop.ts b/src/composables/useCanvasDrop.ts index d3870b04b..51379e98b 100644 --- a/src/composables/useCanvasDrop.ts +++ b/src/composables/useCanvasDrop.ts @@ -41,7 +41,7 @@ export const useCanvasDrop = (canvasRef: Ref) => { } else if (node.data instanceof ComfyModelDef) { const model = node.data const pos = basePos - const nodeAtPos = comfyApp.graph.getNodeOnPos(pos[0], pos[1]) + const nodeAtPos = comfyApp.canvas.graph?.getNodeOnPos(pos[0], pos[1]) let targetProvider: ModelNodeProvider | null = null let targetGraphNode: LGraphNode | null = null if (nodeAtPos) { diff --git a/src/composables/useCivitaiModel.ts b/src/composables/useCivitaiModel.ts index c47b27483..be15f828c 100644 --- a/src/composables/useCivitaiModel.ts +++ b/src/composables/useCivitaiModel.ts @@ -36,7 +36,7 @@ interface CivitaiModelVersionResponse { model: CivitaiModel modelId: number files: CivitaiModelFile[] - [key: string]: any + [key: string]: unknown } /** diff --git a/src/composables/useContextMenuTranslation.ts b/src/composables/useContextMenuTranslation.ts index cfcf5c809..0c69343f5 100644 --- a/src/composables/useContextMenuTranslation.ts +++ b/src/composables/useContextMenuTranslation.ts @@ -22,7 +22,10 @@ export const useContextMenuTranslation = () => { this: LGraphCanvas, ...args: Parameters ) { - const res: IContextMenuValue[] = getCanvasMenuOptions.apply(this, args) + const res: (IContextMenuValue | null)[] = getCanvasMenuOptions.apply( + this, + args + ) // Add items from new extension API const newApiItems = app.collectCanvasMenuItems(this) @@ -58,13 +61,16 @@ export const useContextMenuTranslation = () => { LGraphCanvas.prototype ) + // Install compatibility layer for getNodeMenuOptions + legacyMenuCompat.install(LGraphCanvas.prototype, 'getNodeMenuOptions') + // Wrap getNodeMenuOptions to add new API items const nodeMenuFn = LGraphCanvas.prototype.getNodeMenuOptions const getNodeMenuOptionsWithExtensions = function ( this: LGraphCanvas, ...args: Parameters ) { - const res = nodeMenuFn.apply(this, args) + const res = nodeMenuFn.apply(this, args) as (IContextMenuValue | null)[] // Add items from new extension API const node = args[0] @@ -73,11 +79,28 @@ export const useContextMenuTranslation = () => { res.push(item) } + // Add legacy monkey-patched items + const legacyItems = legacyMenuCompat.extractLegacyItems( + 'getNodeMenuOptions', + this, + ...args + ) + for (const item of legacyItems) { + res.push(item) + } + return res } LGraphCanvas.prototype.getNodeMenuOptions = getNodeMenuOptionsWithExtensions + legacyMenuCompat.registerWrapper( + 'getNodeMenuOptions', + getNodeMenuOptionsWithExtensions, + nodeMenuFn, + LGraphCanvas.prototype + ) + function translateMenus( values: readonly (IContextMenuValue | string | null)[] | undefined, options: IContextMenuOptions @@ -100,7 +123,9 @@ export const useContextMenuTranslation = () => { } // for capture translation text of input and widget - const extraInfo: any = options.extra || options.parentMenu?.options?.extra + const extraInfo = (options.extra || options.parentMenu?.options?.extra) as + | { inputs?: INodeInputSlot[]; widgets?: IWidget[] } + | undefined // widgets and inputs const matchInput = value.content?.match(reInput) if (matchInput) { diff --git a/src/composables/useCopy.test.ts b/src/composables/useCopy.test.ts new file mode 100644 index 000000000..bed017f33 --- /dev/null +++ b/src/composables/useCopy.test.ts @@ -0,0 +1,105 @@ +import { describe, expect, it } from 'vitest' + +/** + * Encodes a UTF-8 string to base64 (same logic as useCopy.ts) + */ +function encodeClipboardData(data: string): string { + return btoa( + String.fromCharCode(...Array.from(new TextEncoder().encode(data))) + ) +} + +/** + * Decodes base64 to UTF-8 string (same logic as usePaste.ts) + */ +function decodeClipboardData(base64: string): string { + const binaryString = atob(base64) + const bytes = Uint8Array.from(binaryString, (c) => c.charCodeAt(0)) + return new TextDecoder().decode(bytes) +} + +describe('Clipboard UTF-8 base64 encoding/decoding', () => { + it('should handle ASCII-only strings', () => { + const original = '{"nodes":[{"id":1,"type":"LoadImage"}]}' + const encoded = encodeClipboardData(original) + const decoded = decodeClipboardData(encoded) + expect(decoded).toBe(original) + }) + + it('should handle Chinese characters in localized_name', () => { + const original = + '{"nodes":[{"id":1,"type":"LoadImage","localized_name":"图像"}]}' + const encoded = encodeClipboardData(original) + const decoded = decodeClipboardData(encoded) + expect(decoded).toBe(original) + }) + + it('should handle Japanese characters', () => { + const original = '{"localized_name":"画像を読み込む"}' + const encoded = encodeClipboardData(original) + const decoded = decodeClipboardData(encoded) + expect(decoded).toBe(original) + }) + + it('should handle Korean characters', () => { + const original = '{"localized_name":"이미지 불러오기"}' + const encoded = encodeClipboardData(original) + const decoded = decodeClipboardData(encoded) + expect(decoded).toBe(original) + }) + + it('should handle mixed ASCII and Unicode characters', () => { + const original = + '{"nodes":[{"id":1,"type":"LoadImage","localized_name":"加载图像","label":"Load Image 图片"}]}' + const encoded = encodeClipboardData(original) + const decoded = decodeClipboardData(encoded) + expect(decoded).toBe(original) + }) + + it('should handle emoji characters', () => { + const original = '{"title":"Test Node 🎨🖼️"}' + const encoded = encodeClipboardData(original) + const decoded = decodeClipboardData(encoded) + expect(decoded).toBe(original) + }) + + it('should handle empty string', () => { + const original = '' + const encoded = encodeClipboardData(original) + const decoded = decodeClipboardData(encoded) + expect(decoded).toBe(original) + }) + + it('should handle complex node data with multiple Unicode fields', () => { + const original = JSON.stringify({ + nodes: [ + { + id: 1, + type: 'LoadImage', + localized_name: '图像', + inputs: [{ localized_name: '图片', name: 'image' }], + outputs: [{ localized_name: '输出', name: 'output' }] + } + ], + groups: [{ title: '预处理组 🔧' }], + links: [] + }) + const encoded = encodeClipboardData(original) + const decoded = decodeClipboardData(encoded) + expect(decoded).toBe(original) + expect(JSON.parse(decoded)).toEqual(JSON.parse(original)) + }) + + it('should produce valid base64 output', () => { + const original = '{"localized_name":"中文测试"}' + const encoded = encodeClipboardData(original) + // Base64 should only contain valid characters + expect(encoded).toMatch(/^[A-Za-z0-9+/=]+$/) + }) + + it('should fail with plain btoa for non-Latin1 characters', () => { + const original = '{"localized_name":"图像"}' + // This demonstrates why we need TextEncoder - plain btoa fails + expect(() => btoa(original)).toThrow() + }) +}) diff --git a/src/composables/useCopy.ts b/src/composables/useCopy.ts index b2455fef8..abf60d71d 100644 --- a/src/composables/useCopy.ts +++ b/src/composables/useCopy.ts @@ -23,10 +23,16 @@ export const useCopy = () => { const canvas = canvasStore.canvas if (canvas?.selectedItems) { const serializedData = canvas.copyToClipboard() + // Use TextEncoder to handle Unicode characters properly + const base64Data = btoa( + String.fromCharCode( + ...Array.from(new TextEncoder().encode(serializedData)) + ) + ) // clearData doesn't remove images from clipboard e.clipboardData?.setData( 'text/html', - clipboardHTMLWrapper.join(btoa(serializedData)) + clipboardHTMLWrapper.join(base64Data) ) e.preventDefault() e.stopImmediatePropagation() diff --git a/tests-ui/tests/composables/useCoreCommands.test.ts b/src/composables/useCoreCommands.test.ts similarity index 59% rename from tests-ui/tests/composables/useCoreCommands.test.ts rename to src/composables/useCoreCommands.test.ts index 6ab5d58db..b4c230bb2 100644 --- a/tests-ui/tests/composables/useCoreCommands.test.ts +++ b/src/composables/useCoreCommands.test.ts @@ -3,14 +3,16 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { ref } from 'vue' import { useCoreCommands } from '@/composables/useCoreCommands' +import type { LGraphNode } from '@/lib/litegraph/src/litegraph' import { useSettingStore } from '@/platform/settings/settingStore' import { api } from '@/scripts/api' import { app } from '@/scripts/app' +import { createMockLGraphNode } from '@/utils/__tests__/litegraphTestUtils' // Mock vue-i18n for useExternalLink const mockLocale = ref('en') -vi.mock('vue-i18n', async (importOriginal) => { - const actual = await importOriginal() +vi.mock('vue-i18n', async () => { + const actual = await vi.importActual('vue-i18n') return { ...actual, useI18n: vi.fn(() => ({ @@ -32,7 +34,7 @@ vi.mock('@/scripts/app', () => { } }), canvas: mockCanvas, - graph: { + rootGraph: { clear: mockGraphClear } } @@ -106,30 +108,84 @@ vi.mock('@/platform/cloud/subscription/composables/useSubscription', () => ({ })) describe('useCoreCommands', () => { - const mockSubgraph = { - nodes: [ - // Mock input node - { - constructor: { comfyClass: 'SubgraphInputNode' }, - id: 'input1' - }, - // Mock output node - { - constructor: { comfyClass: 'SubgraphOutputNode' }, - id: 'output1' - }, - // Mock user node - { - constructor: { comfyClass: 'SomeUserNode' }, - id: 'user1' - }, - // Another mock user node - { - constructor: { comfyClass: 'AnotherUserNode' }, - id: 'user2' + const createMockNode = (id: number, comfyClass: string): LGraphNode => { + const baseNode = createMockLGraphNode({ id }) + return Object.assign(baseNode, { + constructor: { + ...baseNode.constructor, + comfyClass } - ], - remove: vi.fn() + }) + } + + const createMockSubgraph = () => { + const mockNodes = [ + // Mock input node + createMockNode(1, 'SubgraphInputNode'), + // Mock output node + createMockNode(2, 'SubgraphOutputNode'), + // Mock user node + createMockNode(3, 'SomeUserNode'), + // Another mock user node + createMockNode(4, 'AnotherUserNode') + ] + + return { + nodes: mockNodes, + remove: vi.fn(), + events: { + dispatch: vi.fn(), + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn() + }, + name: 'test-subgraph', + inputNode: undefined, + outputNode: undefined, + add: vi.fn(), + clear: vi.fn(), + serialize: vi.fn(), + configure: vi.fn(), + start: vi.fn(), + stop: vi.fn(), + runStep: vi.fn(), + findNodeByTitle: vi.fn(), + findNodesByTitle: vi.fn(), + findNodesByType: vi.fn(), + findNodeById: vi.fn(), + getNodeById: vi.fn(), + setDirtyCanvas: vi.fn(), + sendActionToCanvas: vi.fn() + } as Partial as typeof app.canvas.subgraph + } + + const mockSubgraph = createMockSubgraph() + + function createMockSettingStore( + getReturnValue: boolean + ): ReturnType { + return { + get: vi.fn().mockReturnValue(getReturnValue), + addSetting: vi.fn(), + loadSettingValues: vi.fn(), + set: vi.fn(), + exists: vi.fn(), + getDefaultValue: vi.fn(), + settingValues: {}, + settingsById: {}, + $id: 'setting', + $state: { + settingValues: {}, + settingsById: {} + }, + $patch: vi.fn(), + $reset: vi.fn(), + $subscribe: vi.fn(), + $onAction: vi.fn(), + $dispose: vi.fn(), + _customProperties: new Set(), + _p: {} + } as ReturnType } beforeEach(() => { @@ -142,9 +198,7 @@ describe('useCoreCommands', () => { app.canvas.subgraph = undefined // Mock settings store - vi.mocked(useSettingStore).mockReturnValue({ - get: vi.fn().mockReturnValue(false) // Skip confirmation dialog - } as any) + vi.mocked(useSettingStore).mockReturnValue(createMockSettingStore(false)) // Mock global confirm global.confirm = vi.fn().mockReturnValue(true) @@ -161,13 +215,13 @@ describe('useCoreCommands', () => { await clearCommand.function() expect(app.clean).toHaveBeenCalled() - expect(app.graph.clear).toHaveBeenCalled() + expect(app.rootGraph.clear).toHaveBeenCalled() expect(api.dispatchCustomEvent).toHaveBeenCalledWith('graphCleared') }) it('should preserve input/output nodes when clearing subgraph', async () => { // Set up subgraph context - app.canvas.subgraph = mockSubgraph as any + app.canvas.subgraph = mockSubgraph const commands = useCoreCommands() const clearCommand = commands.find( @@ -178,27 +232,22 @@ describe('useCoreCommands', () => { await clearCommand.function() expect(app.clean).toHaveBeenCalled() - expect(app.graph.clear).not.toHaveBeenCalled() + expect(app.rootGraph.clear).not.toHaveBeenCalled() // Should only remove user nodes, not input/output nodes - expect(mockSubgraph.remove).toHaveBeenCalledTimes(2) - expect(mockSubgraph.remove).toHaveBeenCalledWith(mockSubgraph.nodes[2]) // user1 - expect(mockSubgraph.remove).toHaveBeenCalledWith(mockSubgraph.nodes[3]) // user2 - expect(mockSubgraph.remove).not.toHaveBeenCalledWith( - mockSubgraph.nodes[0] - ) // input1 - expect(mockSubgraph.remove).not.toHaveBeenCalledWith( - mockSubgraph.nodes[1] - ) // output1 + const subgraph = app.canvas.subgraph! + expect(subgraph.remove).toHaveBeenCalledTimes(2) + expect(subgraph.remove).toHaveBeenCalledWith(subgraph.nodes[2]) // user1 + expect(subgraph.remove).toHaveBeenCalledWith(subgraph.nodes[3]) // user2 + expect(subgraph.remove).not.toHaveBeenCalledWith(subgraph.nodes[0]) // input1 + expect(subgraph.remove).not.toHaveBeenCalledWith(subgraph.nodes[1]) // output1 expect(api.dispatchCustomEvent).toHaveBeenCalledWith('graphCleared') }) it('should respect confirmation setting', async () => { // Mock confirmation required - vi.mocked(useSettingStore).mockReturnValue({ - get: vi.fn().mockReturnValue(true) // Require confirmation - } as any) + vi.mocked(useSettingStore).mockReturnValue(createMockSettingStore(true)) global.confirm = vi.fn().mockReturnValue(false) // User cancels @@ -212,7 +261,7 @@ describe('useCoreCommands', () => { // Should not clear anything when user cancels expect(app.clean).not.toHaveBeenCalled() - expect(app.graph.clear).not.toHaveBeenCalled() + expect(app.rootGraph.clear).not.toHaveBeenCalled() expect(api.dispatchCustomEvent).not.toHaveBeenCalled() }) }) diff --git a/src/composables/useCoreCommands.ts b/src/composables/useCoreCommands.ts index 305debbc7..8f3f72304 100644 --- a/src/composables/useCoreCommands.ts +++ b/src/composables/useCoreCommands.ts @@ -1,6 +1,7 @@ import { useCurrentUser } from '@/composables/auth/useCurrentUser' import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions' import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems' +import { useSubgraphOperations } from '@/composables/graph/useSubgraphOperations' import { useExternalLink } from '@/composables/useExternalLink' import { useModelSelectorDialog } from '@/composables/useModelSelectorDialog' import { @@ -8,14 +9,12 @@ import { DEFAULT_LIGHT_COLOR_PALETTE } from '@/constants/coreColorPalettes' import { tryToggleWidgetPromotion } from '@/core/graph/subgraph/proxyWidgetUtils' -import { showSubgraphNodeDialog } from '@/core/graph/subgraph/useSubgraphNodeDialog' import { t } from '@/i18n' import { LGraphEventMode, LGraphGroup, LGraphNode, - LiteGraph, - SubgraphNode + LiteGraph } from '@/lib/litegraph/src/litegraph' import type { Point } from '@/lib/litegraph/src/litegraph' import { useAssetBrowserDialog } from '@/platform/assets/composables/useAssetBrowserDialog' @@ -33,8 +32,6 @@ import { useCanvasStore, useTitleEditorStore } from '@/renderer/core/canvas/canvasStore' -import { layoutStore } from '@/renderer/core/layout/store/layoutStore' -import { selectionBounds } from '@/renderer/core/layout/utils/layoutMath' import { api } from '@/scripts/api' import { app } from '@/scripts/app' import { useDialogService } from '@/services/dialogService' @@ -42,12 +39,16 @@ import { useLitegraphService } from '@/services/litegraphService' import type { ComfyCommand } from '@/stores/commandStore' import { useExecutionStore } from '@/stores/executionStore' import { useHelpCenterStore } from '@/stores/helpCenterStore' -import { useNodeOutputStore } from '@/stores/imagePreviewStore' -import { useQueueSettingsStore, useQueueStore } from '@/stores/queueStore' +import { + useQueueSettingsStore, + useQueueStore, + useQueueUIStore +} from '@/stores/queueStore' import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore' import { useSubgraphStore } from '@/stores/subgraphStore' import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore' import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore' +import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore' import { useSearchBoxStore } from '@/stores/workspace/searchBoxStore' import { useWorkspaceStore } from '@/stores/workspaceStore' import { @@ -63,10 +64,12 @@ import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTyp import { useWorkflowTemplateSelectorDialog } from './useWorkflowTemplateSelectorDialog' +import { useMaskEditorStore } from '@/stores/maskEditorStore' +import { useDialogStore } from '@/stores/dialogStore' + const { isActiveSubscription, showSubscriptionDialog } = useSubscription() const moveSelectedNodesVersionAdded = '1.22.2' - export function useCoreCommands(): ComfyCommand[] { const workflowService = useWorkflowService() const workflowStore = useWorkflowStore() @@ -78,13 +81,25 @@ export function useCoreCommands(): ComfyCommand[] { const executionStore = useExecutionStore() const telemetry = useTelemetry() const { staticUrls, buildDocsUrl } = useExternalLink() + const settingStore = useSettingStore() const bottomPanelStore = useBottomPanelStore() + const dialogStore = useDialogStore() + const maskEditorStore = useMaskEditorStore() + const { getSelectedNodes, toggleSelectedNodesMode } = useSelectedLiteGraphItems() const getTracker = () => workflowStore.activeWorkflow?.changeTracker + function isQueuePanelV2Enabled() { + return settingStore.get('Comfy.Queue.QPOV2') + } + + async function toggleQueuePanelV2() { + await settingStore.set('Comfy.Queue.QPOV2', !isQueuePanelV2Enabled()) + } + const moveSelectedNodes = ( positionUpdater: (pos: Point, gridSize: number) => Point ) => { @@ -107,7 +122,7 @@ export function useCoreCommands(): ComfyCommand[] { menubarLabel: 'New', category: 'essentials' as const, function: async () => { - const previousWorkflowHadNodes = app.graph._nodes.length > 0 + const previousWorkflowHadNodes = app.rootGraph._nodes.length > 0 await workflowService.loadBlankWorkflow() telemetry?.trackWorkflowCreated({ workflow_type: 'blank', @@ -130,7 +145,7 @@ export function useCoreCommands(): ComfyCommand[] { icon: 'pi pi-code', label: 'Load Default Workflow', function: async () => { - const previousWorkflowHadNodes = app.graph._nodes.length > 0 + const previousWorkflowHadNodes = app.rootGraph._nodes.length > 0 await workflowService.loadDefaultWorkflow() telemetry?.trackWorkflowCreated({ workflow_type: 'default', @@ -173,6 +188,26 @@ export function useCoreCommands(): ComfyCommand[] { await workflowService.saveWorkflowAs(workflow) } }, + { + id: 'Comfy.RenameWorkflow', + icon: 'pi pi-pencil', + label: 'Rename Workflow', + menubarLabel: 'Rename', + function: async () => { + const workflow = workflowStore.activeWorkflow + if (!workflow || !workflow.isPersisted) return + + const newName = await dialogService.prompt({ + title: t('g.rename'), + message: t('workflowService.enterFilename') + ':', + defaultValue: workflow.filename + }) + if (!newName || newName === workflow.filename) return + + const newPath = workflow.directory + '/' + newName + '.json' + await workflowService.renameWorkflow(workflow, newPath) + } + }, { id: 'Comfy.ExportWorkflow', icon: 'pi pi-download', @@ -198,7 +233,12 @@ export function useCoreCommands(): ComfyCommand[] { label: 'Undo', category: 'essentials' as const, function: async () => { - await getTracker()?.undo?.() + // If Mask Editor is open, use its history instead of the graph + if (dialogStore.isDialogOpen('global-mask-editor')) { + maskEditorStore.canvasHistory.undo() + } else { + await getTracker()?.undo?.() + } } }, { @@ -207,7 +247,11 @@ export function useCoreCommands(): ComfyCommand[] { label: 'Redo', category: 'essentials' as const, function: async () => { - await getTracker()?.redo?.() + if (dialogStore.isDialogOpen('global-mask-editor')) { + maskEditorStore.canvasHistory.redo() + } else { + await getTracker()?.redo?.() + } } }, { @@ -344,53 +388,15 @@ export function useCoreCommands(): ComfyCommand[] { menubarLabel: 'Zoom to fit', category: 'view-controls' as const, function: () => { - const vueNodesEnabled = useSettingStore().get('Comfy.VueNodes.Enabled') - - if (vueNodesEnabled) { - // Get nodes from Vue stores - const canvasStore = useCanvasStore() - const selectedNodeIds = canvasStore.selectedNodeIds - const allNodes = layoutStore.getAllNodes().value - - // Get nodes to fit - selected if any, otherwise all - const nodesToFit = - selectedNodeIds.size > 0 - ? Array.from(selectedNodeIds) - .map((id) => allNodes.get(id)) - .filter((node) => node != null) - : Array.from(allNodes.values()) - - // Use Vue nodes bounds calculation - const bounds = selectionBounds(nodesToFit) - if (!bounds) { - toastStore.add({ - severity: 'error', - summary: t('toastMessages.emptyCanvas'), - life: 3000 - }) - return - } - - // Convert to LiteGraph format and animate - const lgBounds = [ - bounds.x, - bounds.y, - bounds.width, - bounds.height - ] as const - const setDirty = () => app.canvas.setDirty(true, true) - app.canvas.ds.animateToBounds(lgBounds, setDirty) - } else { - if (app.canvas.empty) { - toastStore.add({ - severity: 'error', - summary: t('toastMessages.emptyCanvas'), - life: 3000 - }) - return - } - app.canvas.fitViewToSelectionAnimated() + if (app.canvas.empty) { + toastStore.add({ + severity: 'error', + summary: t('toastMessages.emptyCanvas'), + life: 3000 + }) + return } + app.canvas.fitViewToSelectionAnimated() } }, { @@ -464,6 +470,18 @@ export function useCoreCommands(): ComfyCommand[] { }, active: () => useSettingStore().get('Comfy.Minimap.Visible') }, + { + id: 'Comfy.Queue.ToggleOverlay', + icon: 'pi pi-history', + label: () => t('queue.toggleJobHistory'), + menubarLabel: () => t('queue.jobHistory'), + versionAdded: '1.37.0', + category: 'view-controls' as const, + function: () => { + useQueueUIStore().toggleOverlay() + }, + active: () => useQueueUIStore().isOverlayExpanded + }, { id: 'Comfy.QueuePrompt', icon: 'pi pi-play', @@ -746,7 +764,7 @@ export function useCoreCommands(): ComfyCommand[] { 'Comfy.GroupSelectedNodes.Padding' ) group.resizeTo(group.children, padding) - app.graph.change() + app.canvas.setDirty(false, true) } } } @@ -922,15 +940,6 @@ export function useCoreCommands(): ComfyCommand[] { }) } }, - { - id: 'Comfy.Manager.ToggleManagerProgressDialog', - icon: 'pi pi-spinner', - label: 'Toggle the Custom Nodes Manager Progress Bar', - versionAdded: '1.13.9', - function: () => { - dialogService.toggleManagerProgressDialog() - } - }, { id: 'Comfy.User.OpenSignInDialog', icon: 'pi pi-user', @@ -1010,14 +1019,8 @@ export function useCoreCommands(): ComfyCommand[] { label: 'Unpack the selected Subgraph', versionAdded: '1.26.3', function: () => { - const canvas = canvasStore.getCanvas() - const graph = canvas.subgraph ?? canvas.graph - if (!graph) throw new TypeError('Canvas has no graph or subgraph set.') - - const subgraphNode = app.canvas.selectedItems.values().next().value - if (!(subgraphNode instanceof SubgraphNode)) return - useNodeOutputStore().revokeSubgraphPreviews(subgraphNode) - graph.unpackSubgraph(subgraphNode) + const { unpackSubgraph } = useSubgraphOperations() + unpackSubgraph() } }, { @@ -1025,7 +1028,9 @@ export function useCoreCommands(): ComfyCommand[] { label: 'Edit Subgraph Widgets', icon: 'icon-[lucide--settings-2]', versionAdded: '1.28.5', - function: showSubgraphNodeDialog + function: () => { + useRightSidePanelStore().openPanel('subgraph') + } }, { id: 'Comfy.Graph.ToggleWidgetPromotion', @@ -1220,11 +1225,25 @@ export function useCoreCommands(): ComfyCommand[] { await useWorkflowService().reloadCurrentWorkflow() // ensure changes take effect immediately } }, + { + id: 'Comfy.ToggleQPOV2', + icon: 'pi pi-list', + label: 'Toggle Queue Panel V2', + function: toggleQueuePanelV2 + }, { id: 'Comfy.ToggleLinear', icon: 'pi pi-database', - label: 'toggle linear mode', - function: () => (canvasStore.linearMode = !canvasStore.linearMode) + label: 'Toggle Simple Mode', + function: (metadata?: Record) => { + const source = + typeof metadata?.source === 'string' ? metadata.source : 'keybind' + const newMode = !canvasStore.linearMode + if (newMode) useTelemetry()?.trackEnterLinear({ source }) + app.rootGraph.extra.linearMode = newMode + workflowStore.activeWorkflow?.changeTracker?.checkState() + canvasStore.linearMode = newMode + } } ] diff --git a/tests-ui/tests/composables/useErrorHandling.test.ts b/src/composables/useErrorHandling.test.ts similarity index 100% rename from tests-ui/tests/composables/useErrorHandling.test.ts rename to src/composables/useErrorHandling.test.ts diff --git a/tests-ui/tests/composables/useExternalLink.test.ts b/src/composables/useExternalLink.test.ts similarity index 89% rename from tests-ui/tests/composables/useExternalLink.test.ts rename to src/composables/useExternalLink.test.ts index 986322cba..7f0ef90aa 100644 --- a/tests-ui/tests/composables/useExternalLink.test.ts +++ b/src/composables/useExternalLink.test.ts @@ -1,7 +1,4 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -import { ref } from 'vue' - -import { useExternalLink } from '@/composables/useExternalLink' // Mock the environment utilities vi.mock('@/utils/envUtil', () => ({ @@ -9,22 +6,27 @@ vi.mock('@/utils/envUtil', () => ({ electronAPI: vi.fn() })) -// Mock vue-i18n -const mockLocale = ref('en') -vi.mock('vue-i18n', () => ({ - useI18n: vi.fn(() => ({ - locale: mockLocale - })) +// Provide a minimal i18n instance for the composable +const i18n = vi.hoisted(() => ({ + global: { + locale: { + value: 'en' + } + } +})) +vi.mock('@/i18n', () => ({ + i18n })) // Import after mocking to get the mocked versions +import { useExternalLink } from '@/composables/useExternalLink' import { electronAPI, isElectron } from '@/utils/envUtil' describe('useExternalLink', () => { beforeEach(() => { vi.clearAllMocks() // Reset to default state - mockLocale.value = 'en' + i18n.global.locale.value = 'en' vi.mocked(isElectron).mockReturnValue(false) }) @@ -53,7 +55,7 @@ describe('useExternalLink', () => { describe('buildDocsUrl', () => { it('should build basic docs URL without locale', () => { - mockLocale.value = 'en' + i18n.global.locale.value = 'en' const { buildDocsUrl } = useExternalLink() const url = buildDocsUrl('/changelog') @@ -61,7 +63,7 @@ describe('useExternalLink', () => { }) it('should build docs URL with Chinese (zh) locale when requested', () => { - mockLocale.value = 'zh' + i18n.global.locale.value = 'zh' const { buildDocsUrl } = useExternalLink() const url = buildDocsUrl('/changelog', { includeLocale: true }) @@ -69,7 +71,7 @@ describe('useExternalLink', () => { }) it('should build docs URL with Chinese (zh-TW) locale when requested', () => { - mockLocale.value = 'zh-TW' + i18n.global.locale.value = 'zh-TW' const { buildDocsUrl } = useExternalLink() const url = buildDocsUrl('/changelog', { includeLocale: true }) @@ -77,7 +79,7 @@ describe('useExternalLink', () => { }) it('should not include locale for English when requested', () => { - mockLocale.value = 'en' + i18n.global.locale.value = 'en' const { buildDocsUrl } = useExternalLink() const url = buildDocsUrl('/changelog', { includeLocale: true }) @@ -92,7 +94,7 @@ describe('useExternalLink', () => { }) it('should add platform suffix when requested', () => { - mockLocale.value = 'en' + i18n.global.locale.value = 'en' vi.mocked(isElectron).mockReturnValue(true) vi.mocked(electronAPI).mockReturnValue({ getPlatform: () => 'darwin' @@ -104,7 +106,7 @@ describe('useExternalLink', () => { }) it('should add platform suffix with trailing slash', () => { - mockLocale.value = 'en' + i18n.global.locale.value = 'en' vi.mocked(isElectron).mockReturnValue(true) vi.mocked(electronAPI).mockReturnValue({ getPlatform: () => 'win32' @@ -116,7 +118,7 @@ describe('useExternalLink', () => { }) it('should combine locale and platform', () => { - mockLocale.value = 'zh' + i18n.global.locale.value = 'zh' vi.mocked(isElectron).mockReturnValue(true) vi.mocked(electronAPI).mockReturnValue({ getPlatform: () => 'darwin' @@ -133,7 +135,7 @@ describe('useExternalLink', () => { }) it('should not add platform when not desktop', () => { - mockLocale.value = 'en' + i18n.global.locale.value = 'en' vi.mocked(isElectron).mockReturnValue(false) const { buildDocsUrl } = useExternalLink() diff --git a/src/composables/useExternalLink.ts b/src/composables/useExternalLink.ts index 59cb9f689..75cb07454 100644 --- a/src/composables/useExternalLink.ts +++ b/src/composables/useExternalLink.ts @@ -1,7 +1,7 @@ import { computed } from 'vue' import { electronAPI, isElectron } from '@/utils/envUtil' -import { useI18n } from 'vue-i18n' +import { i18n } from '@/i18n' /** * Composable for building docs.comfy.org URLs with automatic locale and platform detection @@ -23,7 +23,7 @@ import { useI18n } from 'vue-i18n' * ``` */ export function useExternalLink() { - const { locale } = useI18n() + const locale = computed(() => String(i18n.global.locale.value)) const isChinese = computed(() => { return locale.value === 'zh' || locale.value === 'zh-TW' @@ -92,8 +92,14 @@ export function useExternalLink() { comfyOrg: 'https://www.comfy.org/' } + /** Common doc paths for use with buildDocsUrl */ + const docsPaths = { + partnerNodesPricing: '/tutorials/partner-nodes/pricing' + } + return { buildDocsUrl, - staticUrls + staticUrls, + docsPaths } } diff --git a/tests-ui/tests/composables/useFeatureFlags.test.ts b/src/composables/useFeatureFlags.test.ts similarity index 90% rename from tests-ui/tests/composables/useFeatureFlags.test.ts rename to src/composables/useFeatureFlags.test.ts index eddb57b65..c2b3634f7 100644 --- a/tests-ui/tests/composables/useFeatureFlags.test.ts +++ b/src/composables/useFeatureFlags.test.ts @@ -30,8 +30,7 @@ describe('useFeatureFlags', () => { it('should access supportsPreviewMetadata', () => { vi.mocked(api.getServerFeature).mockImplementation( (path, defaultValue) => { - if (path === ServerFeatureFlag.SUPPORTS_PREVIEW_METADATA) - return true as any + if (path === ServerFeatureFlag.SUPPORTS_PREVIEW_METADATA) return true return defaultValue } ) @@ -46,8 +45,7 @@ describe('useFeatureFlags', () => { it('should access maxUploadSize', () => { vi.mocked(api.getServerFeature).mockImplementation( (path, defaultValue) => { - if (path === ServerFeatureFlag.MAX_UPLOAD_SIZE) - return 209715200 as any // 200MB + if (path === ServerFeatureFlag.MAX_UPLOAD_SIZE) return 209715200 // 200MB return defaultValue } ) @@ -62,7 +60,7 @@ describe('useFeatureFlags', () => { it('should access supportsManagerV4', () => { vi.mocked(api.getServerFeature).mockImplementation( (path, defaultValue) => { - if (path === ServerFeatureFlag.MANAGER_SUPPORTS_V4) return true as any + if (path === ServerFeatureFlag.MANAGER_SUPPORTS_V4) return true return defaultValue } ) @@ -76,7 +74,7 @@ describe('useFeatureFlags', () => { it('should return undefined when features are not available and no default provided', () => { vi.mocked(api.getServerFeature).mockImplementation( - (_path, defaultValue) => defaultValue as any + (_path, defaultValue) => defaultValue ) const { flags } = useFeatureFlags() @@ -90,7 +88,7 @@ describe('useFeatureFlags', () => { it('should create reactive computed for custom feature flags', () => { vi.mocked(api.getServerFeature).mockImplementation( (path, defaultValue) => { - if (path === 'custom.feature') return 'custom-value' as any + if (path === 'custom.feature') return 'custom-value' return defaultValue } ) @@ -108,7 +106,7 @@ describe('useFeatureFlags', () => { it('should handle nested paths', () => { vi.mocked(api.getServerFeature).mockImplementation( (path, defaultValue) => { - if (path === 'extension.custom.nested.feature') return true as any + if (path === 'extension.custom.nested.feature') return true return defaultValue } ) @@ -122,8 +120,7 @@ describe('useFeatureFlags', () => { it('should work with ServerFeatureFlag enum', () => { vi.mocked(api.getServerFeature).mockImplementation( (path, defaultValue) => { - if (path === ServerFeatureFlag.MAX_UPLOAD_SIZE) - return 104857600 as any + if (path === ServerFeatureFlag.MAX_UPLOAD_SIZE) return 104857600 return defaultValue } ) diff --git a/src/composables/useFeatureFlags.ts b/src/composables/useFeatureFlags.ts index 643bb103f..ca54bb9c6 100644 --- a/src/composables/useFeatureFlags.ts +++ b/src/composables/useFeatureFlags.ts @@ -1,5 +1,7 @@ import { computed, reactive, readonly } from 'vue' +import { isCloud } from '@/platform/distribution/types' +import { remoteConfig } from '@/platform/remoteConfig/remoteConfig' import { api } from '@/scripts/api' /** @@ -9,7 +11,15 @@ export enum ServerFeatureFlag { SUPPORTS_PREVIEW_METADATA = 'supports_preview_metadata', MAX_UPLOAD_SIZE = 'max_upload_size', MANAGER_SUPPORTS_V4 = 'extension.manager.supports_v4', - MODEL_UPLOAD_BUTTON_ENABLED = 'model_upload_button_enabled' + MODEL_UPLOAD_BUTTON_ENABLED = 'model_upload_button_enabled', + ASSET_DELETION_ENABLED = 'asset_deletion_enabled', + ASSET_RENAME_ENABLED = 'asset_rename_enabled', + PRIVATE_MODELS_ENABLED = 'private_models_enabled', + ONBOARDING_SURVEY_ENABLED = 'onboarding_survey_enabled', + HUGGINGFACE_MODEL_IMPORT_ENABLED = 'huggingface_model_import_enabled', + LINEAR_TOGGLE_ENABLED = 'linear_toggle_enabled', + ASYNC_MODEL_UPLOAD_ENABLED = 'async_model_upload_enabled', + TEAM_WORKSPACES_ENABLED = 'team_workspaces_enabled' } /** @@ -27,9 +37,70 @@ export function useFeatureFlags() { return api.getServerFeature(ServerFeatureFlag.MANAGER_SUPPORTS_V4) }, get modelUploadButtonEnabled() { - return api.getServerFeature( - ServerFeatureFlag.MODEL_UPLOAD_BUTTON_ENABLED, - false + // Check remote config first (from /api/features), fall back to websocket feature flags + return ( + remoteConfig.value.model_upload_button_enabled ?? + api.getServerFeature( + ServerFeatureFlag.MODEL_UPLOAD_BUTTON_ENABLED, + false + ) + ) + }, + get assetDeletionEnabled() { + return ( + remoteConfig.value.asset_deletion_enabled ?? + api.getServerFeature(ServerFeatureFlag.ASSET_DELETION_ENABLED, false) + ) + }, + get assetRenameEnabled() { + return ( + remoteConfig.value.asset_rename_enabled ?? + api.getServerFeature(ServerFeatureFlag.ASSET_RENAME_ENABLED, false) + ) + }, + get privateModelsEnabled() { + // Check remote config first (from /api/features), fall back to websocket feature flags + return ( + remoteConfig.value.private_models_enabled ?? + api.getServerFeature(ServerFeatureFlag.PRIVATE_MODELS_ENABLED, false) + ) + }, + get onboardingSurveyEnabled() { + return ( + remoteConfig.value.onboarding_survey_enabled ?? + api.getServerFeature(ServerFeatureFlag.ONBOARDING_SURVEY_ENABLED, true) + ) + }, + get huggingfaceModelImportEnabled() { + return ( + remoteConfig.value.huggingface_model_import_enabled ?? + api.getServerFeature( + ServerFeatureFlag.HUGGINGFACE_MODEL_IMPORT_ENABLED, + false + ) + ) + }, + get linearToggleEnabled() { + return ( + remoteConfig.value.linear_toggle_enabled ?? + api.getServerFeature(ServerFeatureFlag.LINEAR_TOGGLE_ENABLED, false) + ) + }, + get asyncModelUploadEnabled() { + return ( + remoteConfig.value.async_model_upload_enabled ?? + api.getServerFeature( + ServerFeatureFlag.ASYNC_MODEL_UPLOAD_ENABLED, + false + ) + ) + }, + get teamWorkspacesEnabled() { + if (!isCloud) return false + + return ( + remoteConfig.value.team_workspaces_enabled ?? + api.getServerFeature(ServerFeatureFlag.TEAM_WORKSPACES_ENABLED, false) ) } }) diff --git a/src/composables/useHelpCenter.ts b/src/composables/useHelpCenter.ts new file mode 100644 index 000000000..3f156119f --- /dev/null +++ b/src/composables/useHelpCenter.ts @@ -0,0 +1,100 @@ +import { storeToRefs } from 'pinia' +import { computed, onMounted } from 'vue' + +import { useSettingStore } from '@/platform/settings/settingStore' +import { useTelemetry } from '@/platform/telemetry' +import { useReleaseStore } from '@/platform/updates/common/releaseStore' +import { useDialogService } from '@/services/dialogService' +import { useHelpCenterStore } from '@/stores/helpCenterStore' +import type { HelpCenterTriggerLocation } from '@/stores/helpCenterStore' +import { useConflictAcknowledgment } from '@/workbench/extensions/manager/composables/useConflictAcknowledgment' +import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection' + +export function useHelpCenter( + triggerFrom: HelpCenterTriggerLocation = 'sidebar' +) { + const settingStore = useSettingStore() + const releaseStore = useReleaseStore() + const helpCenterStore = useHelpCenterStore() + const { isVisible: isHelpCenterVisible, triggerLocation } = + storeToRefs(helpCenterStore) + const { shouldShowRedDot: showReleaseRedDot } = storeToRefs(releaseStore) + + const conflictDetection = useConflictDetection() + const { showNodeConflictDialog } = useDialogService() + + // Use conflict acknowledgment state from composable - call only once + const { shouldShowRedDot: shouldShowConflictRedDot, markConflictsAsSeen } = + useConflictAcknowledgment() + + // Use either release red dot or conflict red dot + const shouldShowRedDot = computed((): boolean => { + const releaseRedDot = showReleaseRedDot.value + return releaseRedDot || shouldShowConflictRedDot.value + }) + + const sidebarLocation = computed(() => + settingStore.get('Comfy.Sidebar.Location') + ) + + /** + * Toggle Help Center and track UI button click. + */ + const toggleHelpCenter = () => { + useTelemetry()?.trackUiButtonClicked({ + button_id: `${triggerFrom}_help_center_toggled` + }) + helpCenterStore.toggle(triggerFrom) + } + + const closeHelpCenter = () => { + helpCenterStore.hide() + } + + /** + * Handle What's New popup dismissal + * Check if conflict modal should be shown after ComfyUI update + */ + const handleWhatsNewDismissed = async () => { + try { + // Check if conflict modal should be shown after update + const shouldShow = + await conflictDetection.shouldShowConflictModalAfterUpdate() + if (shouldShow) { + showConflictModal() + } + } catch (error) { + console.error('[HelpCenter] Error checking conflict modal:', error) + } + } + + /** + * Show the node conflict dialog with current conflict data + */ + const showConflictModal = () => { + showNodeConflictDialog({ + showAfterWhatsNew: true, + dialogComponentProps: { + onClose: () => { + markConflictsAsSeen() + } + } + }) + } + + // Initialize release store on mount + onMounted(async () => { + // Initialize release store to fetch releases for toast and popup + await releaseStore.initialize() + }) + + return { + isHelpCenterVisible, + triggerLocation, + shouldShowRedDot, + sidebarLocation, + toggleHelpCenter, + closeHelpCenter, + handleWhatsNewDismissed + } +} diff --git a/src/composables/useImageCrop.ts b/src/composables/useImageCrop.ts new file mode 100644 index 000000000..da637ba0a --- /dev/null +++ b/src/composables/useImageCrop.ts @@ -0,0 +1,469 @@ +import { useResizeObserver } from '@vueuse/core' +import type { Ref } from 'vue' +import { computed, onMounted, ref, watch } from 'vue' + +import type { LGraphNode, NodeId } from '@/lib/litegraph/src/LGraphNode' +import type { Bounds } from '@/renderer/core/layout/types' +import { app } from '@/scripts/app' +import { useNodeOutputStore } from '@/stores/imagePreviewStore' + +type ResizeDirection = + | 'top' + | 'bottom' + | 'left' + | 'right' + | 'nw' + | 'ne' + | 'sw' + | 'se' + +const HANDLE_SIZE = 8 +const CORNER_SIZE = 10 +const MIN_CROP_SIZE = 16 +const CROP_BOX_BORDER = 2 + +interface UseImageCropOptions { + imageEl: Ref + containerEl: Ref + modelValue: Ref +} + +export function useImageCrop(nodeId: NodeId, options: UseImageCropOptions) { + const { imageEl, containerEl, modelValue } = options + const nodeOutputStore = useNodeOutputStore() + + const node = ref(null) + + const imageUrl = ref(null) + const isLoading = ref(false) + + const naturalWidth = ref(0) + const naturalHeight = ref(0) + const displayedWidth = ref(0) + const displayedHeight = ref(0) + const scaleFactor = ref(1) + const imageOffsetX = ref(0) + const imageOffsetY = ref(0) + + const cropX = computed({ + get: () => modelValue.value.x, + set: (v: number) => { + modelValue.value.x = v + } + }) + + const cropY = computed({ + get: () => modelValue.value.y, + set: (v: number) => { + modelValue.value.y = v + } + }) + + const cropWidth = computed({ + get: () => modelValue.value.width || 512, + set: (v: number) => { + modelValue.value.width = v + } + }) + + const cropHeight = computed({ + get: () => modelValue.value.height || 512, + set: (v: number) => { + modelValue.value.height = v + } + }) + + const isDragging = ref(false) + const dragStartX = ref(0) + const dragStartY = ref(0) + const dragStartCropX = ref(0) + const dragStartCropY = ref(0) + + const isResizing = ref(false) + const resizeDirection = ref(null) + const resizeStartX = ref(0) + const resizeStartY = ref(0) + const resizeStartCropX = ref(0) + const resizeStartCropY = ref(0) + const resizeStartCropWidth = ref(0) + const resizeStartCropHeight = ref(0) + + useResizeObserver(containerEl, () => { + if (imageEl.value && imageUrl.value) { + updateDisplayedDimensions() + } + }) + + const getInputImageUrl = (): string | null => { + if (!node.value) return null + + const inputNode = node.value.getInputNode(0) + + if (!inputNode) return null + + const urls = nodeOutputStore.getNodeImageUrls(inputNode) + + if (urls?.length) { + return urls[0] + } + + return null + } + + const updateImageUrl = () => { + imageUrl.value = getInputImageUrl() + } + + const updateDisplayedDimensions = () => { + if (!imageEl.value || !containerEl.value) return + + const img = imageEl.value + const container = containerEl.value + + naturalWidth.value = img.naturalWidth + naturalHeight.value = img.naturalHeight + + if (naturalWidth.value <= 0 || naturalHeight.value <= 0) { + scaleFactor.value = 1 + return + } + + const containerWidth = container.clientWidth + const containerHeight = container.clientHeight + + const imageAspect = naturalWidth.value / naturalHeight.value + const containerAspect = containerWidth / containerHeight + + if (imageAspect > containerAspect) { + displayedWidth.value = containerWidth + displayedHeight.value = containerWidth / imageAspect + imageOffsetX.value = 0 + imageOffsetY.value = (containerHeight - displayedHeight.value) / 2 + } else { + displayedHeight.value = containerHeight + displayedWidth.value = containerHeight * imageAspect + imageOffsetX.value = (containerWidth - displayedWidth.value) / 2 + imageOffsetY.value = 0 + } + + if (naturalWidth.value <= 0 || displayedWidth.value <= 0) { + scaleFactor.value = 1 + } else { + scaleFactor.value = displayedWidth.value / naturalWidth.value + } + } + + const getEffectiveScale = (): number => { + const container = containerEl.value + + if (!container || naturalWidth.value <= 0 || displayedWidth.value <= 0) { + return 1 + } + + const rect = container.getBoundingClientRect() + const clientWidth = container.clientWidth + + if (!clientWidth || !rect.width) return 1 + + const renderedDisplayedWidth = + (displayedWidth.value / clientWidth) * rect.width + + return renderedDisplayedWidth / naturalWidth.value + } + + const cropBoxStyle = computed(() => ({ + left: `${imageOffsetX.value + cropX.value * scaleFactor.value - CROP_BOX_BORDER}px`, + top: `${imageOffsetY.value + cropY.value * scaleFactor.value - CROP_BOX_BORDER}px`, + width: `${cropWidth.value * scaleFactor.value}px`, + height: `${cropHeight.value * scaleFactor.value}px` + })) + + const cropImageStyle = computed(() => { + if (!imageUrl.value) return {} + + return { + backgroundImage: `url(${imageUrl.value})`, + backgroundSize: `${displayedWidth.value}px ${displayedHeight.value}px`, + backgroundPosition: `-${cropX.value * scaleFactor.value}px -${cropY.value * scaleFactor.value}px`, + backgroundRepeat: 'no-repeat' + } + }) + + interface ResizeHandle { + direction: ResizeDirection + class: string + style: { + left: string + top: string + width?: string + height?: string + } + } + + const resizeHandles = computed(() => { + const x = imageOffsetX.value + cropX.value * scaleFactor.value + const y = imageOffsetY.value + cropY.value * scaleFactor.value + const w = cropWidth.value * scaleFactor.value + const h = cropHeight.value * scaleFactor.value + + return [ + { + direction: 'top', + class: 'h-2 cursor-ns-resize', + style: { + left: `${x + HANDLE_SIZE}px`, + top: `${y - HANDLE_SIZE / 2}px`, + width: `${Math.max(0, w - HANDLE_SIZE * 2)}px` + } + }, + { + direction: 'bottom', + class: 'h-2 cursor-ns-resize', + style: { + left: `${x + HANDLE_SIZE}px`, + top: `${y + h - HANDLE_SIZE / 2}px`, + width: `${Math.max(0, w - HANDLE_SIZE * 2)}px` + } + }, + { + direction: 'left', + class: 'w-2 cursor-ew-resize', + style: { + left: `${x - HANDLE_SIZE / 2}px`, + top: `${y + HANDLE_SIZE}px`, + height: `${Math.max(0, h - HANDLE_SIZE * 2)}px` + } + }, + { + direction: 'right', + class: 'w-2 cursor-ew-resize', + style: { + left: `${x + w - HANDLE_SIZE / 2}px`, + top: `${y + HANDLE_SIZE}px`, + height: `${Math.max(0, h - HANDLE_SIZE * 2)}px` + } + }, + { + direction: 'nw', + class: 'cursor-nwse-resize rounded-sm bg-white/80', + style: { + left: `${x - CORNER_SIZE / 2}px`, + top: `${y - CORNER_SIZE / 2}px`, + width: `${CORNER_SIZE}px`, + height: `${CORNER_SIZE}px` + } + }, + { + direction: 'ne', + class: 'cursor-nesw-resize rounded-sm bg-white/80', + style: { + left: `${x + w - CORNER_SIZE / 2}px`, + top: `${y - CORNER_SIZE / 2}px`, + width: `${CORNER_SIZE}px`, + height: `${CORNER_SIZE}px` + } + }, + { + direction: 'sw', + class: 'cursor-nesw-resize rounded-sm bg-white/80', + style: { + left: `${x - CORNER_SIZE / 2}px`, + top: `${y + h - CORNER_SIZE / 2}px`, + width: `${CORNER_SIZE}px`, + height: `${CORNER_SIZE}px` + } + }, + { + direction: 'se', + class: 'cursor-nwse-resize rounded-sm bg-white/80', + style: { + left: `${x + w - CORNER_SIZE / 2}px`, + top: `${y + h - CORNER_SIZE / 2}px`, + width: `${CORNER_SIZE}px`, + height: `${CORNER_SIZE}px` + } + } + ] + }) + + const handleImageLoad = () => { + isLoading.value = false + updateDisplayedDimensions() + } + + const handleImageError = () => { + isLoading.value = false + imageUrl.value = null + } + + const capturePointer = (e: PointerEvent) => + (e.target as HTMLElement).setPointerCapture(e.pointerId) + + const releasePointer = (e: PointerEvent) => + (e.target as HTMLElement).releasePointerCapture(e.pointerId) + + const handleDragStart = (e: PointerEvent) => { + if (!imageUrl.value) return + + isDragging.value = true + dragStartX.value = e.clientX + dragStartY.value = e.clientY + dragStartCropX.value = cropX.value + dragStartCropY.value = cropY.value + capturePointer(e) + } + + const handleDragMove = (e: PointerEvent) => { + if (!isDragging.value) return + + const effectiveScale = getEffectiveScale() + if (effectiveScale === 0) return + + const deltaX = (e.clientX - dragStartX.value) / effectiveScale + const deltaY = (e.clientY - dragStartY.value) / effectiveScale + + const maxX = naturalWidth.value - cropWidth.value + const maxY = naturalHeight.value - cropHeight.value + + cropX.value = Math.round( + Math.max(0, Math.min(maxX, dragStartCropX.value + deltaX)) + ) + cropY.value = Math.round( + Math.max(0, Math.min(maxY, dragStartCropY.value + deltaY)) + ) + } + + const handleDragEnd = (e: PointerEvent) => { + if (!isDragging.value) return + + isDragging.value = false + releasePointer(e) + } + + const handleResizeStart = (e: PointerEvent, direction: ResizeDirection) => { + if (!imageUrl.value) return + + e.stopPropagation() + isResizing.value = true + resizeDirection.value = direction + + resizeStartX.value = e.clientX + resizeStartY.value = e.clientY + resizeStartCropX.value = cropX.value + resizeStartCropY.value = cropY.value + resizeStartCropWidth.value = cropWidth.value + resizeStartCropHeight.value = cropHeight.value + capturePointer(e) + } + + const handleResizeMove = (e: PointerEvent) => { + if (!isResizing.value || !resizeDirection.value) return + + const effectiveScale = getEffectiveScale() + if (effectiveScale === 0) return + + const dir = resizeDirection.value + const deltaX = (e.clientX - resizeStartX.value) / effectiveScale + const deltaY = (e.clientY - resizeStartY.value) / effectiveScale + + const affectsLeft = dir === 'left' || dir === 'nw' || dir === 'sw' + const affectsRight = dir === 'right' || dir === 'ne' || dir === 'se' + const affectsTop = dir === 'top' || dir === 'nw' || dir === 'ne' + const affectsBottom = dir === 'bottom' || dir === 'sw' || dir === 'se' + + let newX = resizeStartCropX.value + let newY = resizeStartCropY.value + let newWidth = resizeStartCropWidth.value + let newHeight = resizeStartCropHeight.value + + if (affectsLeft) { + const maxDeltaX = resizeStartCropWidth.value - MIN_CROP_SIZE + const minDeltaX = -resizeStartCropX.value + const clampedDeltaX = Math.max(minDeltaX, Math.min(maxDeltaX, deltaX)) + newX = resizeStartCropX.value + clampedDeltaX + newWidth = resizeStartCropWidth.value - clampedDeltaX + } else if (affectsRight) { + const maxWidth = naturalWidth.value - resizeStartCropX.value + newWidth = Math.max( + MIN_CROP_SIZE, + Math.min(maxWidth, resizeStartCropWidth.value + deltaX) + ) + } + + if (affectsTop) { + const maxDeltaY = resizeStartCropHeight.value - MIN_CROP_SIZE + const minDeltaY = -resizeStartCropY.value + const clampedDeltaY = Math.max(minDeltaY, Math.min(maxDeltaY, deltaY)) + newY = resizeStartCropY.value + clampedDeltaY + newHeight = resizeStartCropHeight.value - clampedDeltaY + } else if (affectsBottom) { + const maxHeight = naturalHeight.value - resizeStartCropY.value + newHeight = Math.max( + MIN_CROP_SIZE, + Math.min(maxHeight, resizeStartCropHeight.value + deltaY) + ) + } + + if (affectsLeft || affectsRight) { + cropX.value = Math.round(newX) + cropWidth.value = Math.round(newWidth) + } + if (affectsTop || affectsBottom) { + cropY.value = Math.round(newY) + cropHeight.value = Math.round(newHeight) + } + } + + const handleResizeEnd = (e: PointerEvent) => { + if (!isResizing.value) return + + isResizing.value = false + resizeDirection.value = null + releasePointer(e) + } + + const initialize = () => { + if (nodeId != null) { + node.value = app.rootGraph?.getNodeById(nodeId) || null + } + + updateImageUrl() + } + + watch( + () => nodeOutputStore.nodeOutputs, + () => updateImageUrl(), + { deep: true } + ) + + watch( + () => nodeOutputStore.nodePreviewImages, + () => updateImageUrl(), + { deep: true } + ) + + onMounted(initialize) + + return { + imageUrl, + isLoading, + + cropX, + cropY, + cropWidth, + cropHeight, + + cropBoxStyle, + cropImageStyle, + resizeHandles, + + handleImageLoad, + handleImageError, + handleDragStart, + handleDragMove, + handleDragEnd, + handleResizeStart, + handleResizeMove, + handleResizeEnd + } +} diff --git a/tests-ui/tests/composables/useLoad3d.test.ts b/src/composables/useLoad3d.test.ts similarity index 83% rename from tests-ui/tests/composables/useLoad3d.test.ts rename to src/composables/useLoad3d.test.ts index a471d2ff6..dafc19931 100644 --- a/tests-ui/tests/composables/useLoad3d.test.ts +++ b/src/composables/useLoad3d.test.ts @@ -1,11 +1,19 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { nextTick, ref } from 'vue' +import { nextTick, ref, shallowRef } from 'vue' import { nodeToLoad3dMap, useLoad3d } from '@/composables/useLoad3d' import Load3d from '@/extensions/core/load3d/Load3d' import Load3dUtils from '@/extensions/core/load3d/Load3dUtils' +import type { Size } from '@/lib/litegraph/src/interfaces' +import type { LGraph } from '@/lib/litegraph/src/LGraph' +import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' +import type { IWidget } from '@/lib/litegraph/src/types/widgets' import { useToastStore } from '@/platform/updates/common/toastStore' import { api } from '@/scripts/api' +import { + createMockCanvasPointerEvent, + createMockLGraphNode +} from '@/utils/__tests__/litegraphTestUtils' vi.mock('@/extensions/core/load3d/Load3d', () => ({ default: vi.fn() @@ -36,15 +44,15 @@ vi.mock('@/i18n', () => ({ })) describe('useLoad3d', () => { - let mockLoad3d: any - let mockNode: any - let mockToastStore: any + let mockLoad3d: Partial + let mockNode: LGraphNode + let mockToastStore: ReturnType beforeEach(() => { vi.clearAllMocks() nodeToLoad3dMap.clear() - mockNode = { + mockNode = createMockLGraphNode({ properties: { 'Scene Config': { showGrid: true, @@ -54,7 +62,8 @@ describe('useLoad3d', () => { }, 'Model Config': { upDirection: 'original', - materialMode: 'original' + materialMode: 'original', + showSkeleton: false }, 'Camera Config': { cameraType: 'perspective', @@ -67,18 +76,21 @@ describe('useLoad3d', () => { 'Resource Folder': '' }, widgets: [ - { name: 'width', value: 512 }, - { name: 'height', value: 512 } + { name: 'width', value: 512, type: 'number' } as IWidget, + { name: 'height', value: 512, type: 'number' } as IWidget ], graph: { setDirtyCanvas: vi.fn() - }, + } as Partial as LGraph, flags: {}, - onMouseEnter: null, - onMouseLeave: null, - onResize: null, - onDrawBackground: null - } + onMouseEnter: undefined, + onMouseLeave: undefined, + onResize: undefined, + onDrawBackground: undefined + }) + + const mockCanvas = document.createElement('canvas') + mockCanvas.hidden = false mockLoad3d = { toggleGrid: vi.fn(), @@ -105,21 +117,28 @@ describe('useLoad3d', () => { exportRecording: vi.fn(), clearRecording: vi.fn(), exportModel: vi.fn().mockResolvedValue(undefined), + isSplatModel: vi.fn().mockReturnValue(false), + isPlyModel: vi.fn().mockReturnValue(false), + hasSkeleton: vi.fn().mockReturnValue(false), + setShowSkeleton: vi.fn(), addEventListener: vi.fn(), removeEventListener: vi.fn(), remove: vi.fn(), renderer: { - domElement: { - hidden: false - } - } + domElement: mockCanvas + } as Partial as Load3d['renderer'] } - vi.mocked(Load3d).mockImplementation(() => mockLoad3d) + vi.mocked(Load3d).mockImplementation(function (this: Load3d) { + Object.assign(this, mockLoad3d) + return this + }) mockToastStore = { addAlert: vi.fn() - } + } as Partial> as ReturnType< + typeof useToastStore + > vi.mocked(useToastStore).mockReturnValue(mockToastStore) }) @@ -139,7 +158,8 @@ describe('useLoad3d', () => { }) expect(composable.modelConfig.value).toEqual({ upDirection: 'original', - materialMode: 'original' + materialMode: 'original', + showSkeleton: false }) expect(composable.cameraConfig.value).toEqual({ cameraType: 'perspective', @@ -200,14 +220,14 @@ describe('useLoad3d', () => { expect(mockNode.onDrawBackground).toBeDefined() // Test the handlers - mockNode.onMouseEnter() + mockNode.onMouseEnter?.(createMockCanvasPointerEvent(0, 0)) expect(mockLoad3d.refreshViewport).toHaveBeenCalled() expect(mockLoad3d.updateStatusMouseOnNode).toHaveBeenCalledWith(true) - mockNode.onMouseLeave() + mockNode.onMouseLeave?.(createMockCanvasPointerEvent(0, 0)) expect(mockLoad3d.updateStatusMouseOnNode).toHaveBeenCalledWith(false) - mockNode.onResize() + mockNode.onResize?.([512, 512] as Size) expect(mockLoad3d.handleResize).toHaveBeenCalled() }) @@ -218,13 +238,17 @@ describe('useLoad3d', () => { await composable.initializeLoad3d(containerRef) mockNode.flags.collapsed = true - mockNode.onDrawBackground() + mockNode.onDrawBackground?.({} as CanvasRenderingContext2D) - expect(mockLoad3d.renderer.domElement.hidden).toBe(true) + expect(mockLoad3d.renderer!.domElement.hidden).toBe(true) }) it('should load model if model_file widget exists', async () => { - mockNode.widgets.push({ name: 'model_file', value: 'test.glb' }) + mockNode.widgets!.push({ + name: 'model_file', + value: 'test.glb', + type: 'text' + } as IWidget) vi.mocked(Load3dUtils.splitFilePath).mockReturnValue([ 'subfolder', 'test.glb' @@ -247,8 +271,12 @@ describe('useLoad3d', () => { }) it('should restore camera state after loading model', async () => { - mockNode.widgets.push({ name: 'model_file', value: 'test.glb' }) - mockNode.properties['Camera Config'].state = { + mockNode.widgets!.push({ + name: 'model_file', + value: 'test.glb', + type: 'text' + } as IWidget) + ;(mockNode.properties!['Camera Config'] as { state: unknown }).state = { position: { x: 1, y: 2, z: 3 }, target: { x: 0, y: 0, z: 0 } } @@ -287,7 +315,7 @@ describe('useLoad3d', () => { }) it('should handle initialization errors', async () => { - vi.mocked(Load3d).mockImplementationOnce(() => { + vi.mocked(Load3d).mockImplementationOnce(function () { throw new Error('Load3d creation failed') }) @@ -304,13 +332,13 @@ describe('useLoad3d', () => { it('should handle missing container or node', async () => { const composable = useLoad3d(mockNode) - await composable.initializeLoad3d(null as any) + await composable.initializeLoad3d(null!) expect(Load3d).not.toHaveBeenCalled() }) it('should accept ref as parameter', () => { - const nodeRef = ref(mockNode) + const nodeRef = shallowRef(mockNode) const composable = useLoad3d(nodeRef) expect(composable.sceneConfig.value.backgroundColor).toBe('#000000') @@ -362,9 +390,9 @@ describe('useLoad3d', () => { await composable.initializeLoad3d(containerRef) - mockLoad3d.toggleGrid.mockClear() - mockLoad3d.setBackgroundColor.mockClear() - mockLoad3d.setBackgroundImage.mockClear() + vi.mocked(mockLoad3d.toggleGrid!).mockClear() + vi.mocked(mockLoad3d.setBackgroundColor!).mockClear() + vi.mocked(mockLoad3d.setBackgroundImage!).mockClear() composable.sceneConfig.value = { showGrid: false, @@ -395,8 +423,8 @@ describe('useLoad3d', () => { await composable.initializeLoad3d(containerRef) await nextTick() - mockLoad3d.setUpDirection.mockClear() - mockLoad3d.setMaterialMode.mockClear() + vi.mocked(mockLoad3d.setUpDirection!).mockClear() + vi.mocked(mockLoad3d.setMaterialMode!).mockClear() composable.modelConfig.value.upDirection = '+y' composable.modelConfig.value.materialMode = 'wireframe' @@ -406,7 +434,8 @@ describe('useLoad3d', () => { expect(mockLoad3d.setMaterialMode).toHaveBeenCalledWith('wireframe') expect(mockNode.properties['Model Config']).toEqual({ upDirection: '+y', - materialMode: 'wireframe' + materialMode: 'wireframe', + showSkeleton: false }) }) @@ -417,8 +446,8 @@ describe('useLoad3d', () => { await composable.initializeLoad3d(containerRef) await nextTick() - mockLoad3d.toggleCamera.mockClear() - mockLoad3d.setFOV.mockClear() + vi.mocked(mockLoad3d.toggleCamera!).mockClear() + vi.mocked(mockLoad3d.setFOV!).mockClear() composable.cameraConfig.value.cameraType = 'orthographic' composable.cameraConfig.value.fov = 90 @@ -440,7 +469,7 @@ describe('useLoad3d', () => { await composable.initializeLoad3d(containerRef) await nextTick() - mockLoad3d.setLightIntensity.mockClear() + vi.mocked(mockLoad3d.setLightIntensity!).mockClear() composable.lightConfig.value.intensity = 10 await nextTick() @@ -580,7 +609,7 @@ describe('useLoad3d', () => { }) it('should use resource folder for upload', async () => { - mockNode.properties['Resource Folder'] = 'subfolder' + mockNode.properties!['Resource Folder'] = 'subfolder' vi.mocked(Load3dUtils.uploadFile).mockResolvedValue('uploaded-image.jpg') const composable = useLoad3d(mockNode) @@ -632,7 +661,9 @@ describe('useLoad3d', () => { }) it('should handle export errors', async () => { - mockLoad3d.exportModel.mockRejectedValueOnce(new Error('Export failed')) + vi.mocked(mockLoad3d.exportModel!).mockRejectedValueOnce( + new Error('Export failed') + ) const composable = useLoad3d(mockNode) const containerRef = document.createElement('div') @@ -692,10 +723,13 @@ describe('useLoad3d', () => { 'backgroundImageLoadingEnd', 'modelLoadingStart', 'modelLoadingEnd', + 'skeletonVisibilityChange', 'exportLoadingStart', 'exportLoadingEnd', 'recordingStatusChange', - 'animationListChange' + 'animationListChange', + 'animationProgressChange', + 'cameraChanged' ] expectedEvents.forEach((event) => { @@ -707,12 +741,12 @@ describe('useLoad3d', () => { }) it('should handle materialModeChange event', async () => { - let materialModeHandler: any + let materialModeHandler: ((mode: string) => void) | undefined - mockLoad3d.addEventListener.mockImplementation( - (event: string, handler: any) => { + vi.mocked(mockLoad3d.addEventListener!).mockImplementation( + (event: string, handler: unknown) => { if (event === 'materialModeChange') { - materialModeHandler = handler + materialModeHandler = handler as (mode: string) => void } } ) @@ -722,21 +756,21 @@ describe('useLoad3d', () => { await composable.initializeLoad3d(containerRef) - materialModeHandler('wireframe') + materialModeHandler?.('wireframe') expect(composable.modelConfig.value.materialMode).toBe('wireframe') }) it('should handle loading events', async () => { - let modelLoadingStartHandler: any - let modelLoadingEndHandler: any + let modelLoadingStartHandler: (() => void) | undefined + let modelLoadingEndHandler: (() => void) | undefined - mockLoad3d.addEventListener.mockImplementation( - (event: string, handler: any) => { + vi.mocked(mockLoad3d.addEventListener!).mockImplementation( + (event: string, handler: unknown) => { if (event === 'modelLoadingStart') { - modelLoadingStartHandler = handler + modelLoadingStartHandler = handler as () => void } else if (event === 'modelLoadingEnd') { - modelLoadingEndHandler = handler + modelLoadingEndHandler = handler as () => void } } ) @@ -746,22 +780,22 @@ describe('useLoad3d', () => { await composable.initializeLoad3d(containerRef) - modelLoadingStartHandler() + modelLoadingStartHandler?.() expect(composable.loading.value).toBe(true) expect(composable.loadingMessage.value).toBe('load3d.loadingModel') - modelLoadingEndHandler() + modelLoadingEndHandler?.() expect(composable.loading.value).toBe(false) expect(composable.loadingMessage.value).toBe('') }) it('should handle recordingStatusChange event', async () => { - let recordingStatusHandler: any + let recordingStatusHandler: ((status: boolean) => void) | undefined - mockLoad3d.addEventListener.mockImplementation( - (event: string, handler: any) => { + vi.mocked(mockLoad3d.addEventListener!).mockImplementation( + (event: string, handler: unknown) => { if (event === 'recordingStatusChange') { - recordingStatusHandler = handler + recordingStatusHandler = handler as (status: boolean) => void } } ) @@ -771,7 +805,7 @@ describe('useLoad3d', () => { await composable.initializeLoad3d(containerRef) - recordingStatusHandler(false) + recordingStatusHandler?.(false) expect(composable.isRecording.value).toBe(false) expect(composable.recordingDuration.value).toBe(10) @@ -802,10 +836,11 @@ describe('useLoad3d', () => { describe('getModelUrl', () => { it('should handle http URLs directly', async () => { - mockNode.widgets.push({ + mockNode.widgets!.push({ name: 'model_file', - value: 'http://example.com/model.glb' - }) + value: 'http://example.com/model.glb', + type: 'text' + } as IWidget) const composable = useLoad3d(mockNode) const containerRef = document.createElement('div') @@ -818,7 +853,11 @@ describe('useLoad3d', () => { }) it('should construct URL for local files', async () => { - mockNode.widgets.push({ name: 'model_file', value: 'models/test.glb' }) + mockNode.widgets!.push({ + name: 'model_file', + value: 'models/test.glb', + type: 'text' + } as IWidget) vi.mocked(Load3dUtils.splitFilePath).mockReturnValue([ 'models', 'test.glb' @@ -848,7 +887,9 @@ describe('useLoad3d', () => { }) it('should use output type for preview mode', async () => { - mockNode.widgets = [{ name: 'model_file', value: 'test.glb' }] // No width/height widgets + mockNode.widgets = [ + { name: 'model_file', value: 'test.glb', type: 'text' } as IWidget + ] // No width/height widgets vi.mocked(Load3dUtils.splitFilePath).mockReturnValue(['', 'test.glb']) vi.mocked(Load3dUtils.getResourceURL).mockReturnValue( '/api/view/test.glb' @@ -882,10 +923,10 @@ describe('useLoad3d', () => { }) it('should handle missing configurations', async () => { - delete mockNode.properties['Scene Config'] - delete mockNode.properties['Model Config'] - delete mockNode.properties['Camera Config'] - delete mockNode.properties['Light Config'] + delete mockNode.properties!['Scene Config'] + delete mockNode.properties!['Model Config'] + delete mockNode.properties!['Camera Config'] + delete mockNode.properties!['Light Config'] const composable = useLoad3d(mockNode) const containerRef = document.createElement('div') @@ -897,7 +938,11 @@ describe('useLoad3d', () => { }) it('should handle background image with existing config', async () => { - mockNode.properties['Scene Config'].backgroundImage = 'existing.jpg' + ;( + mockNode.properties!['Scene Config'] as { + backgroundImage: string + } + ).backgroundImage = 'existing.jpg' const composable = useLoad3d(mockNode) const containerRef = document.createElement('div') diff --git a/src/composables/useLoad3d.ts b/src/composables/useLoad3d.ts index 41a2cfff6..b07daba2e 100644 --- a/src/composables/useLoad3d.ts +++ b/src/composables/useLoad3d.ts @@ -9,6 +9,7 @@ import type { CameraConfig, CameraState, CameraType, + EventCallback, LightConfig, MaterialMode, ModelConfig, @@ -40,9 +41,12 @@ export const useLoad3d = (nodeOrRef: MaybeRef) => { const modelConfig = ref({ upDirection: 'original', - materialMode: 'original' + materialMode: 'original', + showSkeleton: false }) + const hasSkeleton = ref(false) + const cameraConfig = ref({ cameraType: 'perspective', fov: 75 @@ -60,9 +64,13 @@ export const useLoad3d = (nodeOrRef: MaybeRef) => { const playing = ref(false) const selectedSpeed = ref(1) const selectedAnimation = ref(0) + const animationProgress = ref(0) + const animationDuration = ref(0) const loading = ref(false) const loadingMessage = ref('') const isPreview = ref(false) + const isSplatModel = ref(false) + const isPlyModel = ref(false) const initializeLoad3d = async (containerRef: HTMLElement) => { const rawNode = toRaw(nodeRef.value) @@ -269,6 +277,7 @@ export const useLoad3d = (nodeOrRef: MaybeRef) => { nodeRef.value.properties['Model Config'] = newValue load3d.setUpDirection(newValue.upDirection) load3d.setMaterialMode(newValue.materialMode) + load3d.setShowSkeleton(newValue.showSkeleton) } }, { deep: true } @@ -355,6 +364,13 @@ export const useLoad3d = (nodeOrRef: MaybeRef) => { } } + const handleSeek = (progress: number) => { + if (load3d && animationDuration.value > 0) { + const time = (progress / 100) * animationDuration.value + load3d.setAnimationTime(time) + } + } + const handleBackgroundImageUpdate = async (file: File | null) => { if (!file) { sceneConfig.value.backgroundImage = '' @@ -490,6 +506,30 @@ export const useLoad3d = (nodeOrRef: MaybeRef) => { modelLoadingEnd: () => { loadingMessage.value = '' loading.value = false + isSplatModel.value = load3d?.isSplatModel() ?? false + isPlyModel.value = load3d?.isPlyModel() ?? false + hasSkeleton.value = load3d?.hasSkeleton() ?? false + // Reset skeleton visibility when loading new model + modelConfig.value.showSkeleton = false + + if (load3d) { + const node = nodeRef.value + + const modelWidget = node?.widgets?.find( + (w) => w.name === 'model_file' || w.name === 'image' + ) + const value = modelWidget?.value + if (typeof value === 'string') { + void Load3dUtils.generateThumbnailIfNeeded( + load3d, + value, + isPreview.value ? 'output' : 'input' + ) + } + } + }, + skeletonVisibilityChange: (value: boolean) => { + modelConfig.value.showSkeleton = value }, exportLoadingStart: (message: string) => { loadingMessage.value = message || t('load3d.exportingModel') @@ -510,6 +550,14 @@ export const useLoad3d = (nodeOrRef: MaybeRef) => { animationListChange: (newValue: AnimationItem[]) => { animations.value = newValue }, + animationProgressChange: (data: { + progress: number + currentTime: number + duration: number + }) => { + animationProgress.value = data.progress + animationDuration.value = data.duration + }, cameraChanged: (cameraState: CameraState) => { const rawNode = toRaw(nodeRef.value) if (rawNode) { @@ -533,7 +581,7 @@ export const useLoad3d = (nodeOrRef: MaybeRef) => { const handleEvents = (action: 'add' | 'remove') => { Object.entries(eventConfig).forEach(([event, handler]) => { const method = `${action}EventListener` as const - load3d?.[method](event, handler) + load3d?.[method](event, handler as EventCallback) }) } @@ -561,12 +609,17 @@ export const useLoad3d = (nodeOrRef: MaybeRef) => { lightConfig, isRecording, isPreview, + isSplatModel, + isPlyModel, + hasSkeleton, hasRecording, recordingDuration, animations, playing, selectedSpeed, selectedAnimation, + animationProgress, + animationDuration, loading, loadingMessage, @@ -579,6 +632,7 @@ export const useLoad3d = (nodeOrRef: MaybeRef) => { handleStopRecording, handleExportRecording, handleClearRecording, + handleSeek, handleBackgroundImageUpdate, handleExportModel, handleModelDrop, diff --git a/tests-ui/tests/composables/useLoad3dDrag.test.ts b/src/composables/useLoad3dDrag.test.ts similarity index 92% rename from tests-ui/tests/composables/useLoad3dDrag.test.ts rename to src/composables/useLoad3dDrag.test.ts index 284d21c23..a682f5af8 100644 --- a/tests-ui/tests/composables/useLoad3dDrag.test.ts +++ b/src/composables/useLoad3dDrag.test.ts @@ -3,6 +3,7 @@ import { ref } from 'vue' import { useLoad3dDrag } from '@/composables/useLoad3dDrag' import { useToastStore } from '@/platform/updates/common/toastStore' +import { createMockFileList } from '@/utils/__tests__/litegraphTestUtils' vi.mock('@/platform/updates/common/toastStore', () => ({ useToastStore: vi.fn() @@ -19,30 +20,32 @@ function createMockDragEvent( const files = options.files || [] const types = options.hasFiles ? ['Files'] : [] - const dataTransfer = { + const dataTransfer: Partial = { types, - files, + files: createMockFileList(files), dropEffect: 'none' as DataTransfer['dropEffect'] } - const event = { + const event: Partial = { type, - dataTransfer - } as unknown as DragEvent + dataTransfer: dataTransfer as DataTransfer + } - return event + return event as DragEvent } describe('useLoad3dDrag', () => { - let mockToastStore: any - let mockOnModelDrop: ReturnType + let mockToastStore: ReturnType + let mockOnModelDrop: (file: File) => void | Promise beforeEach(() => { vi.clearAllMocks() mockToastStore = { addAlert: vi.fn() - } + } as Partial> as ReturnType< + typeof useToastStore + > vi.mocked(useToastStore).mockReturnValue(mockToastStore) mockOnModelDrop = vi.fn() @@ -199,7 +202,7 @@ describe('useLoad3dDrag', () => { const extensions = ['.gltf', '.glb', '.obj', '.fbx', '.stl'] for (const ext of extensions) { - mockOnModelDrop.mockClear() + vi.mocked(mockOnModelDrop).mockClear() const modelFile = new File([], `model${ext}`) const event = createMockDragEvent('drop', { diff --git a/tests-ui/tests/composables/useLoad3dViewer.test.ts b/src/composables/useLoad3dViewer.test.ts similarity index 80% rename from tests-ui/tests/composables/useLoad3dViewer.test.ts rename to src/composables/useLoad3dViewer.test.ts index a49c7aa75..757e1400d 100644 --- a/tests-ui/tests/composables/useLoad3dViewer.test.ts +++ b/src/composables/useLoad3dViewer.test.ts @@ -4,8 +4,11 @@ import { nextTick } from 'vue' import { useLoad3dViewer } from '@/composables/useLoad3dViewer' import Load3d from '@/extensions/core/load3d/Load3d' import Load3dUtils from '@/extensions/core/load3d/Load3dUtils' +import type { LGraph } from '@/lib/litegraph/src/LGraph' +import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' import { useToastStore } from '@/platform/updates/common/toastStore' import { useLoad3dService } from '@/services/load3dService' +import { createMockLGraphNode } from '@/utils/__tests__/litegraphTestUtils' vi.mock('@/services/load3dService', () => ({ useLoad3dService: vi.fn() @@ -29,17 +32,32 @@ vi.mock('@/extensions/core/load3d/Load3d', () => ({ default: vi.fn() })) +function createMockSceneManager(): Load3d['sceneManager'] { + const mock: Partial = { + scene: {} as Load3d['sceneManager']['scene'], + backgroundScene: {} as Load3d['sceneManager']['backgroundScene'], + backgroundCamera: {} as Load3d['sceneManager']['backgroundCamera'], + currentBackgroundColor: '#282828', + gridHelper: { visible: true } as Load3d['sceneManager']['gridHelper'], + getCurrentBackgroundInfo: vi.fn().mockReturnValue({ + type: 'color', + value: '#282828' + }) + } + return mock as Load3d['sceneManager'] +} + describe('useLoad3dViewer', () => { - let mockLoad3d: any - let mockSourceLoad3d: any - let mockLoad3dService: any - let mockToastStore: any - let mockNode: any + let mockLoad3d: Partial + let mockSourceLoad3d: Partial + let mockLoad3dService: ReturnType + let mockToastStore: ReturnType + let mockNode: LGraphNode beforeEach(() => { vi.clearAllMocks() - mockNode = { + mockNode = createMockLGraphNode({ properties: { 'Scene Config': { backgroundColor: '#282828', @@ -62,9 +80,9 @@ describe('useLoad3dViewer', () => { }, graph: { setDirtyCanvas: vi.fn() - }, + } as Partial as LGraph, widgets: [] - } as any + }) mockLoad3d = { setBackgroundColor: vi.fn(), @@ -97,41 +115,40 @@ describe('useLoad3dViewer', () => { zoom: 1, cameraType: 'perspective' }), - sceneManager: { - currentBackgroundColor: '#282828', - gridHelper: { visible: true }, - getCurrentBackgroundInfo: vi.fn().mockReturnValue({ - type: 'color', - value: '#282828' - }) - }, + sceneManager: createMockSceneManager(), lightingManager: { lights: [null, { intensity: 1 }] - }, + } as Load3d['lightingManager'], cameraManager: { perspectiveCamera: { fov: 75 } - }, + } as Load3d['cameraManager'], modelManager: { currentUpDirection: 'original', materialMode: 'original' - }, + } as Load3d['modelManager'], setBackgroundImage: vi.fn().mockResolvedValue(undefined), setBackgroundRenderMode: vi.fn(), forceRender: vi.fn() } - vi.mocked(Load3d).mockImplementation(() => mockLoad3d) + vi.mocked(Load3d).mockImplementation(function () { + Object.assign(this, mockLoad3d) + }) mockLoad3dService = { copyLoad3dState: vi.fn().mockResolvedValue(undefined), handleViewportRefresh: vi.fn(), getLoad3d: vi.fn().mockReturnValue(mockSourceLoad3d) - } + } as Partial> as ReturnType< + typeof useLoad3dService + > vi.mocked(useLoad3dService).mockReturnValue(mockLoad3dService) mockToastStore = { addAlert: vi.fn() - } + } as Partial> as ReturnType< + typeof useToastStore + > vi.mocked(useToastStore).mockReturnValue(mockToastStore) }) @@ -158,13 +175,13 @@ describe('useLoad3dViewer', () => { const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) expect(Load3d).toHaveBeenCalledWith(containerRef, { width: undefined, height: undefined, getDimensions: undefined, - isViewerMode: true + isViewerMode: false }) expect(mockLoad3dService.copyLoad3dState).toHaveBeenCalledWith( @@ -182,30 +199,34 @@ describe('useLoad3dViewer', () => { }) it('should handle background image during initialization', async () => { - mockSourceLoad3d.sceneManager.getCurrentBackgroundInfo.mockReturnValue({ + vi.mocked( + mockSourceLoad3d.sceneManager!.getCurrentBackgroundInfo + ).mockReturnValue({ type: 'image', value: '' }) - mockNode.properties['Scene Config'].backgroundImage = 'test-image.jpg' + ;( + mockNode.properties!['Scene Config'] as Record + ).backgroundImage = 'test-image.jpg' const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) expect(viewer.backgroundImage.value).toBe('test-image.jpg') expect(viewer.hasBackgroundImage.value).toBe(true) }) it('should handle initialization errors', async () => { - vi.mocked(Load3d).mockImplementationOnce(() => { + vi.mocked(Load3d).mockImplementationOnce(function () { throw new Error('Load3d creation failed') }) const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) expect(mockToastStore.addAlert).toHaveBeenCalledWith( 'toastMessages.failedToInitializeLoad3dViewer' @@ -218,7 +239,7 @@ describe('useLoad3dViewer', () => { const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) viewer.backgroundColor.value = '#ff0000' await nextTick() @@ -230,7 +251,7 @@ describe('useLoad3dViewer', () => { const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) viewer.showGrid.value = false await nextTick() @@ -242,7 +263,7 @@ describe('useLoad3dViewer', () => { const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) viewer.cameraType.value = 'orthographic' await nextTick() @@ -254,7 +275,7 @@ describe('useLoad3dViewer', () => { const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) viewer.fov.value = 90 await nextTick() @@ -266,7 +287,7 @@ describe('useLoad3dViewer', () => { const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) viewer.lightIntensity.value = 2 await nextTick() @@ -278,7 +299,7 @@ describe('useLoad3dViewer', () => { const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) viewer.backgroundImage.value = 'new-bg.jpg' await nextTick() @@ -291,7 +312,7 @@ describe('useLoad3dViewer', () => { const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) viewer.upDirection.value = '+y' await nextTick() @@ -303,7 +324,7 @@ describe('useLoad3dViewer', () => { const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) viewer.materialMode.value = 'wireframe' await nextTick() @@ -312,14 +333,16 @@ describe('useLoad3dViewer', () => { }) it('should handle watcher errors gracefully', async () => { - mockLoad3d.setBackgroundColor.mockImplementationOnce(() => { - throw new Error('Color update failed') - }) + vi.mocked(mockLoad3d.setBackgroundColor!).mockImplementationOnce( + function () { + throw new Error('Color update failed') + } + ) const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) viewer.backgroundColor.value = '#ff0000' await nextTick() @@ -335,7 +358,7 @@ describe('useLoad3dViewer', () => { const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) await viewer.exportModel('glb') @@ -343,12 +366,14 @@ describe('useLoad3dViewer', () => { }) it('should handle export errors', async () => { - mockLoad3d.exportModel.mockRejectedValueOnce(new Error('Export failed')) + vi.mocked(mockLoad3d.exportModel!).mockRejectedValueOnce( + new Error('Export failed') + ) const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) await viewer.exportModel('glb') @@ -371,7 +396,7 @@ describe('useLoad3dViewer', () => { const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) viewer.handleResize() @@ -382,7 +407,7 @@ describe('useLoad3dViewer', () => { const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) viewer.handleMouseEnter() @@ -393,7 +418,7 @@ describe('useLoad3dViewer', () => { const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) viewer.handleMouseLeave() @@ -406,22 +431,35 @@ describe('useLoad3dViewer', () => { const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) - - mockNode.properties['Scene Config'].backgroundColor = '#ff0000' - mockNode.properties['Scene Config'].showGrid = false + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) + ;( + mockNode.properties!['Scene Config'] as Record + ).backgroundColor = '#ff0000' + ;( + mockNode.properties!['Scene Config'] as Record + ).showGrid = false viewer.restoreInitialState() - expect(mockNode.properties['Scene Config'].backgroundColor).toBe( - '#282828' - ) - expect(mockNode.properties['Scene Config'].showGrid).toBe(true) - expect(mockNode.properties['Camera Config'].cameraType).toBe( - 'perspective' - ) - expect(mockNode.properties['Camera Config'].fov).toBe(75) - expect(mockNode.properties['Light Config'].intensity).toBe(1) + expect( + (mockNode.properties!['Scene Config'] as Record) + .backgroundColor + ).toBe('#282828') + expect( + (mockNode.properties!['Scene Config'] as Record) + .showGrid + ).toBe(true) + expect( + (mockNode.properties!['Camera Config'] as Record) + .cameraType + ).toBe('perspective') + expect( + (mockNode.properties!['Camera Config'] as Record).fov + ).toBe(75) + expect( + (mockNode.properties!['Light Config'] as Record) + .intensity + ).toBe(1) }) }) @@ -430,7 +468,7 @@ describe('useLoad3dViewer', () => { const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) viewer.backgroundColor.value = '#ff0000' viewer.showGrid.value = false @@ -438,23 +476,27 @@ describe('useLoad3dViewer', () => { const result = await viewer.applyChanges() expect(result).toBe(true) - expect(mockNode.properties['Scene Config'].backgroundColor).toBe( - '#ff0000' - ) - expect(mockNode.properties['Scene Config'].showGrid).toBe(false) + expect( + (mockNode.properties!['Scene Config'] as Record) + .backgroundColor + ).toBe('#ff0000') + expect( + (mockNode.properties!['Scene Config'] as Record) + .showGrid + ).toBe(false) expect(mockLoad3dService.copyLoad3dState).toHaveBeenCalledWith( mockLoad3d, mockSourceLoad3d ) expect(mockSourceLoad3d.forceRender).toHaveBeenCalled() - expect(mockNode.graph.setDirtyCanvas).toHaveBeenCalledWith(true, true) + expect(mockNode.graph!.setDirtyCanvas).toHaveBeenCalledWith(true, true) }) it('should handle background image during apply', async () => { const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) viewer.backgroundImage.value = 'new-bg.jpg' @@ -479,7 +521,7 @@ describe('useLoad3dViewer', () => { const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) viewer.refreshViewport() @@ -496,7 +538,7 @@ describe('useLoad3dViewer', () => { const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) const file = new File([''], 'test.jpg', { type: 'image/jpeg' }) await viewer.handleBackgroundImageUpdate(file) @@ -513,7 +555,7 @@ describe('useLoad3dViewer', () => { const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) const file = new File([''], 'test.jpg', { type: 'image/jpeg' }) await viewer.handleBackgroundImageUpdate(file) @@ -525,7 +567,7 @@ describe('useLoad3dViewer', () => { const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) viewer.backgroundImage.value = 'existing.jpg' viewer.hasBackgroundImage.value = true @@ -544,7 +586,7 @@ describe('useLoad3dViewer', () => { const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) const file = new File([''], 'test.jpg', { type: 'image/jpeg' }) await viewer.handleBackgroundImageUpdate(file) @@ -560,7 +602,7 @@ describe('useLoad3dViewer', () => { const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) viewer.cleanup() @@ -578,33 +620,36 @@ describe('useLoad3dViewer', () => { it('should handle missing container ref', async () => { const viewer = useLoad3dViewer(mockNode) - await viewer.initializeViewer(null as any, mockSourceLoad3d) + await viewer.initializeViewer(null!, mockSourceLoad3d as Load3d) expect(Load3d).not.toHaveBeenCalled() }) it('should handle orthographic camera', async () => { - mockSourceLoad3d.getCurrentCameraType.mockReturnValue('orthographic') + vi.mocked(mockSourceLoad3d.getCurrentCameraType!).mockReturnValue( + 'orthographic' + ) mockSourceLoad3d.cameraManager = { perspectiveCamera: { fov: 75 } - } - delete mockNode.properties['Camera Config'].cameraType + } as Partial as Load3d['cameraManager'] + delete (mockNode.properties!['Camera Config'] as Record) + .cameraType const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) expect(viewer.cameraType.value).toBe('orthographic') }) it('should handle missing lights', async () => { - mockSourceLoad3d.lightingManager.lights = [] + mockSourceLoad3d.lightingManager!.lights = [] const viewer = useLoad3dViewer(mockNode) const containerRef = document.createElement('div') - await viewer.initializeViewer(containerRef, mockSourceLoad3d) + await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d) expect(viewer.lightIntensity.value).toBe(1) // Default value }) diff --git a/src/composables/useLoad3dViewer.ts b/src/composables/useLoad3dViewer.ts index eb536c735..94e4ebc24 100644 --- a/src/composables/useLoad3dViewer.ts +++ b/src/composables/useLoad3dViewer.ts @@ -3,10 +3,15 @@ import { ref, toRaw, watch } from 'vue' import Load3d from '@/extensions/core/load3d/Load3d' import Load3dUtils from '@/extensions/core/load3d/Load3dUtils' import type { + AnimationItem, BackgroundRenderModeType, + CameraConfig, CameraState, CameraType, + LightConfig, MaterialMode, + ModelConfig, + SceneConfig, UpDirection } from '@/extensions/core/load3d/interfaces' import { t } from '@/i18n' @@ -46,6 +51,16 @@ export const useLoad3dViewer = (node?: LGraphNode) => { const needApplyChanges = ref(true) const isPreview = ref(false) const isStandaloneMode = ref(false) + const isSplatModel = ref(false) + const isPlyModel = ref(false) + + // Animation state + const animations = ref([]) + const playing = ref(false) + const selectedSpeed = ref(1) + const selectedAnimation = ref(0) + const animationProgress = ref(0) + const animationDuration = ref(0) let load3d: Load3d | null = null let sourceLoad3d: Load3d | null = null @@ -172,6 +187,61 @@ export const useLoad3dViewer = (node?: LGraphNode) => { } }) + // Animation watches + watch(playing, (newValue) => { + if (load3d) { + load3d.toggleAnimation(newValue) + } + }) + + watch(selectedSpeed, (newValue) => { + if (load3d && newValue) { + load3d.setAnimationSpeed(newValue) + } + }) + + watch(selectedAnimation, (newValue) => { + if (load3d && newValue !== undefined) { + load3d.updateSelectedAnimation(newValue) + } + }) + + const handleSeek = (progress: number) => { + if (load3d && animationDuration.value > 0) { + const time = (progress / 100) * animationDuration.value + load3d.setAnimationTime(time) + } + } + + const setupAnimationEvents = () => { + if (!load3d) return + + load3d.addEventListener( + 'animationListChange', + (newValue: AnimationItem[]) => { + animations.value = newValue + } + ) + + load3d.addEventListener( + 'animationProgressChange', + (data: { progress: number; currentTime: number; duration: number }) => { + animationProgress.value = data.progress + animationDuration.value = data.duration + } + ) + + // Initialize animation list if animations already exist + if (load3d.hasAnimations()) { + const clips = load3d.animationManager.animationClips + animations.value = clips.map((clip, index) => ({ + name: clip.name || `Animation ${index + 1}`, + index + })) + animationDuration.value = load3d.getAnimationDuration() + } + } + /** * Initialize viewer in node mode (with source Load3d) */ @@ -187,27 +257,36 @@ export const useLoad3dViewer = (node?: LGraphNode) => { const width = node.widgets?.find((w) => w.name === 'width') const height = node.widgets?.find((w) => w.name === 'height') + const hasTargetDimensions = !!(width && height) + load3d = new Load3d(containerRef, { width: width ? (toRaw(width).value as number) : undefined, height: height ? (toRaw(height).value as number) : undefined, - getDimensions: - width && height - ? () => ({ - width: width.value as number, - height: height.value as number - }) - : undefined, - isViewerMode: true + getDimensions: hasTargetDimensions + ? () => ({ + width: width.value as number, + height: height.value as number + }) + : undefined, + isViewerMode: hasTargetDimensions }) await useLoad3dService().copyLoad3dState(source, load3d) const sourceCameraState = source.getCameraState() - const sceneConfig = node.properties['Scene Config'] as any - const modelConfig = node.properties['Model Config'] as any - const cameraConfig = node.properties['Camera Config'] as any - const lightConfig = node.properties['Light Config'] as any + const sceneConfig = node.properties['Scene Config'] as + | SceneConfig + | undefined + const modelConfig = node.properties['Model Config'] as + | ModelConfig + | undefined + const cameraConfig = node.properties['Camera Config'] as + | CameraConfig + | undefined + const lightConfig = node.properties['Light Config'] as + | LightConfig + | undefined isPreview.value = node.type === 'Preview3D' @@ -252,6 +331,9 @@ export const useLoad3dViewer = (node?: LGraphNode) => { modelConfig.materialMode || source.modelManager.materialMode } + isSplatModel.value = source.isSplatModel() + isPlyModel.value = source.isPlyModel() + initialState.value = { backgroundColor: backgroundColor.value, showGrid: showGrid.value, @@ -264,6 +346,8 @@ export const useLoad3dViewer = (node?: LGraphNode) => { upDirection: upDirection.value, materialMode: materialMode.value } + + setupAnimationEvents() } catch (error) { console.error('Error initializing Load3d viewer:', error) useToastStore().addAlert( @@ -300,8 +384,12 @@ export const useLoad3dViewer = (node?: LGraphNode) => { backgroundRenderMode.value = 'tiled' upDirection.value = 'original' materialMode.value = 'original' + isSplatModel.value = load3d.isSplatModel() + isPlyModel.value = load3d.isPlyModel() isPreview.value = true + + setupAnimationEvents() } catch (error) { console.error('Error initializing standalone 3D viewer:', error) useToastStore().addAlert('Failed to load 3D model') @@ -362,7 +450,9 @@ export const useLoad3dViewer = (node?: LGraphNode) => { materialMode: initialState.value.materialMode } - const currentCameraConfig = nodeValue.properties['Camera Config'] as any + const currentCameraConfig = nodeValue.properties['Camera Config'] as + | CameraConfig + | undefined nodeValue.properties['Camera Config'] = { ...currentCameraConfig, state: initialState.value.cameraState @@ -516,6 +606,16 @@ export const useLoad3dViewer = (node?: LGraphNode) => { needApplyChanges, isPreview, isStandaloneMode, + isSplatModel, + isPlyModel, + + // Animation state + animations, + playing, + selectedSpeed, + selectedAnimation, + animationProgress, + animationDuration, // Methods initializeViewer, @@ -529,6 +629,11 @@ export const useLoad3dViewer = (node?: LGraphNode) => { refreshViewport, handleBackgroundImageUpdate, handleModelDrop, - cleanup + handleSeek, + cleanup, + + hasSkeleton: false, + intensity: lightIntensity, + showSkeleton: false } } diff --git a/tests-ui/tests/store/nodeHelpStore.test.ts b/src/composables/useNodeHelpContent.test.ts similarity index 50% rename from tests-ui/tests/store/nodeHelpStore.test.ts rename to src/composables/useNodeHelpContent.test.ts index dc8b7b466..02b3d102f 100644 --- a/tests-ui/tests/store/nodeHelpStore.test.ts +++ b/src/composables/useNodeHelpContent.test.ts @@ -1,9 +1,28 @@ import { flushPromises } from '@vue/test-utils' -import { createPinia, setActivePinia } from 'pinia' -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { nextTick } from 'vue' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { nextTick, ref } from 'vue' -import { useNodeHelpStore } from '@/stores/workspace/nodeHelpStore' +import { useNodeHelpContent } from '@/composables/useNodeHelpContent' +import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore' + +function createMockNode( + overrides: Partial +): ComfyNodeDefImpl { + return { + name: 'TestNode', + display_name: 'Test Node', + description: 'A test node', + category: 'test', + python_module: 'comfy.test_node', + inputs: {}, + outputs: [], + deprecated: false, + experimental: false, + output_node: false, + api_node: false, + ...overrides + } as ComfyNodeDefImpl +} vi.mock('@/scripts/api', () => ({ api: { @@ -11,14 +30,10 @@ vi.mock('@/scripts/api', () => ({ } })) -vi.mock('@/i18n', () => ({ - i18n: { - global: { - locale: { - value: 'en' - } - } - } +vi.mock('vue-i18n', () => ({ + useI18n: () => ({ + locale: ref('en') + }) })) vi.mock('@/types/nodeSource', () => ({ @@ -34,233 +49,140 @@ vi.mock('@/types/nodeSource', () => ({ }) })) -vi.mock('dompurify', () => ({ - default: { - sanitize: vi.fn((html) => html) - } -})) - -vi.mock('marked', () => ({ - marked: { - parse: vi.fn((markdown, options) => { - if (options?.renderer) { - if (markdown.includes('![')) { - const matches = markdown.match(/!\[(.*?)\]\((.*?)\)/) - if (matches) { - const [, text, href] = matches - return options.renderer.image({ href, text, title: '' }) - } - } - } - return `

${markdown}

` - }) - }, - Renderer: class Renderer { - image = vi.fn( - ({ href, title, text }) => - `${text}` - ) - link = vi.fn( - ({ href, title, text }) => - `${text}` - ) - } -})) - -describe('nodeHelpStore', () => { - // Define a mock node for testing - const mockCoreNode = { +describe('useNodeHelpContent', () => { + const mockCoreNode = createMockNode({ name: 'TestNode', display_name: 'Test Node', description: 'A test node', - inputs: {}, - outputs: [], python_module: 'comfy.test_node' - } + }) - const mockCustomNode = { + const mockCustomNode = createMockNode({ name: 'CustomNode', display_name: 'Custom Node', description: 'A custom node', - inputs: {}, - outputs: [], python_module: 'custom_nodes.test_module.custom@1.0.0' - } + }) - // Mock fetch responses const mockFetch = vi.fn() - global.fetch = mockFetch beforeEach(() => { - // Setup Pinia - setActivePinia(createPinia()) mockFetch.mockReset() + vi.stubGlobal('fetch', mockFetch) }) - it('should initialize with empty state', () => { - const nodeHelpStore = useNodeHelpStore() - expect(nodeHelpStore.currentHelpNode).toBeNull() - expect(nodeHelpStore.isHelpOpen).toBe(false) - }) - - it('should open help for a node', () => { - const nodeHelpStore = useNodeHelpStore() - - nodeHelpStore.openHelp(mockCoreNode as any) - - expect(nodeHelpStore.currentHelpNode).toStrictEqual(mockCoreNode) - expect(nodeHelpStore.isHelpOpen).toBe(true) - }) - - it('should close help', () => { - const nodeHelpStore = useNodeHelpStore() - - nodeHelpStore.openHelp(mockCoreNode as any) - expect(nodeHelpStore.isHelpOpen).toBe(true) - - nodeHelpStore.closeHelp() - expect(nodeHelpStore.currentHelpNode).toBeNull() - expect(nodeHelpStore.isHelpOpen).toBe(false) + afterEach(() => { + vi.unstubAllGlobals() }) it('should generate correct baseUrl for core nodes', async () => { - const nodeHelpStore = useNodeHelpStore() + const nodeRef = ref(mockCoreNode) + mockFetch.mockResolvedValueOnce({ + ok: true, + text: async () => '# Test' + }) - nodeHelpStore.openHelp(mockCoreNode as any) + const { baseUrl } = useNodeHelpContent(nodeRef) await nextTick() - expect(nodeHelpStore.baseUrl).toBe(`/docs/${mockCoreNode.name}/`) + expect(baseUrl.value).toBe(`/docs/${mockCoreNode.name}/`) }) it('should generate correct baseUrl for custom nodes', async () => { - const nodeHelpStore = useNodeHelpStore() + const nodeRef = ref(mockCustomNode) + mockFetch.mockResolvedValueOnce({ + ok: true, + text: async () => '# Test' + }) - nodeHelpStore.openHelp(mockCustomNode as any) + const { baseUrl } = useNodeHelpContent(nodeRef) await nextTick() - expect(nodeHelpStore.baseUrl).toBe('/extensions/test_module/docs/') + expect(baseUrl.value).toBe('/extensions/test_module/docs/') }) it('should render markdown content correctly', async () => { - const nodeHelpStore = useNodeHelpStore() - + const nodeRef = ref(mockCoreNode) mockFetch.mockResolvedValueOnce({ ok: true, text: async () => '# Test Help\nThis is test help content' }) - nodeHelpStore.openHelp(mockCoreNode as any) + const { renderedHelpHtml } = useNodeHelpContent(nodeRef) await flushPromises() - expect(nodeHelpStore.renderedHelpHtml).toContain( - 'This is test help content' - ) + expect(renderedHelpHtml.value).toContain('This is test help content') }) it('should handle fetch errors and fall back to description', async () => { - const nodeHelpStore = useNodeHelpStore() - + const nodeRef = ref(mockCoreNode) mockFetch.mockResolvedValueOnce({ ok: false, statusText: 'Not Found' }) - nodeHelpStore.openHelp(mockCoreNode as any) + const { error, renderedHelpHtml } = useNodeHelpContent(nodeRef) await flushPromises() - expect(nodeHelpStore.error).toBe('Not Found') - expect(nodeHelpStore.renderedHelpHtml).toContain(mockCoreNode.description) + expect(error.value).toBe('Not Found') + expect(renderedHelpHtml.value).toContain(mockCoreNode.description) }) it('should include alt attribute for images', async () => { - const nodeHelpStore = useNodeHelpStore() - + const nodeRef = ref(mockCustomNode) mockFetch.mockResolvedValueOnce({ ok: true, text: async () => '![image](test.jpg)' }) - nodeHelpStore.openHelp(mockCustomNode as any) + const { renderedHelpHtml } = useNodeHelpContent(nodeRef) await flushPromises() - expect(nodeHelpStore.renderedHelpHtml).toContain('alt="image"') + + expect(renderedHelpHtml.value).toContain('alt="image"') }) it('should prefix relative video src in custom nodes', async () => { - const nodeHelpStore = useNodeHelpStore() - + const nodeRef = ref(mockCustomNode) mockFetch.mockResolvedValueOnce({ ok: true, text: async () => '' }) - nodeHelpStore.openHelp(mockCustomNode as any) + const { renderedHelpHtml } = useNodeHelpContent(nodeRef) await flushPromises() - expect(nodeHelpStore.renderedHelpHtml).toContain( + + expect(renderedHelpHtml.value).toContain( 'src="/extensions/test_module/docs/video.mp4"' ) }) it('should prefix relative video src for core nodes with node-specific base URL', async () => { - const nodeHelpStore = useNodeHelpStore() - + const nodeRef = ref(mockCoreNode) mockFetch.mockResolvedValueOnce({ ok: true, text: async () => '' }) - nodeHelpStore.openHelp(mockCoreNode as any) + const { renderedHelpHtml } = useNodeHelpContent(nodeRef) await flushPromises() - expect(nodeHelpStore.renderedHelpHtml).toContain( + + expect(renderedHelpHtml.value).toContain( `src="/docs/${mockCoreNode.name}/video.mp4"` ) }) - it('should prefix relative source src in custom nodes', async () => { - const nodeHelpStore = useNodeHelpStore() - - mockFetch.mockResolvedValueOnce({ - ok: true, - text: async () => - '' - }) - - nodeHelpStore.openHelp(mockCustomNode as any) - await flushPromises() - expect(nodeHelpStore.renderedHelpHtml).toContain( - 'src="/extensions/test_module/docs/video.mp4"' - ) - }) - - it('should prefix relative source src for core nodes with node-specific base URL', async () => { - const nodeHelpStore = useNodeHelpStore() - - mockFetch.mockResolvedValueOnce({ - ok: true, - text: async () => - '' - }) - - nodeHelpStore.openHelp(mockCoreNode as any) - await flushPromises() - expect(nodeHelpStore.renderedHelpHtml).toContain( - `src="/docs/${mockCoreNode.name}/video.webm"` - ) - }) - it('should handle loading state', async () => { - const nodeHelpStore = useNodeHelpStore() - + const nodeRef = ref(mockCoreNode) mockFetch.mockImplementationOnce(() => new Promise(() => {})) // Never resolves - nodeHelpStore.openHelp(mockCoreNode as any) + const { isLoading } = useNodeHelpContent(nodeRef) await nextTick() - expect(nodeHelpStore.isLoading).toBe(true) + expect(isLoading.value).toBe(true) }) it('should try fallback URL for custom nodes', async () => { - const nodeHelpStore = useNodeHelpStore() - + const nodeRef = ref(mockCustomNode) mockFetch .mockResolvedValueOnce({ ok: false, @@ -271,7 +193,7 @@ describe('nodeHelpStore', () => { text: async () => '# Fallback content' }) - nodeHelpStore.openHelp(mockCustomNode as any) + useNodeHelpContent(nodeRef) await flushPromises() expect(mockFetch).toHaveBeenCalledTimes(2) @@ -283,74 +205,103 @@ describe('nodeHelpStore', () => { ) }) - it('should prefix relative img src in raw HTML for custom nodes', async () => { - const nodeHelpStore = useNodeHelpStore() + it('should prefix relative source src in custom nodes', async () => { + const nodeRef = ref(mockCustomNode) + mockFetch.mockResolvedValueOnce({ + ok: true, + text: async () => + '' + }) + const { renderedHelpHtml } = useNodeHelpContent(nodeRef) + await flushPromises() + + expect(renderedHelpHtml.value).toContain( + 'src="/extensions/test_module/docs/video.mp4"' + ) + }) + + it('should prefix relative source src for core nodes with node-specific base URL', async () => { + const nodeRef = ref(mockCoreNode) + mockFetch.mockResolvedValueOnce({ + ok: true, + text: async () => + '' + }) + + const { renderedHelpHtml } = useNodeHelpContent(nodeRef) + await flushPromises() + + expect(renderedHelpHtml.value).toContain( + `src="/docs/${mockCoreNode.name}/video.webm"` + ) + }) + + it('should prefix relative img src in raw HTML for custom nodes', async () => { + const nodeRef = ref(mockCustomNode) mockFetch.mockResolvedValueOnce({ ok: true, text: async () => '# Test\nTest image' }) - nodeHelpStore.openHelp(mockCustomNode as any) + const { renderedHelpHtml } = useNodeHelpContent(nodeRef) await flushPromises() - expect(nodeHelpStore.renderedHelpHtml).toContain( + + expect(renderedHelpHtml.value).toContain( 'src="/extensions/test_module/docs/image.png"' ) - expect(nodeHelpStore.renderedHelpHtml).toContain('alt="Test image"') + expect(renderedHelpHtml.value).toContain('alt="Test image"') }) it('should prefix relative img src in raw HTML for core nodes', async () => { - const nodeHelpStore = useNodeHelpStore() - + const nodeRef = ref(mockCoreNode) mockFetch.mockResolvedValueOnce({ ok: true, text: async () => '# Test\nTest image' }) - nodeHelpStore.openHelp(mockCoreNode as any) + const { renderedHelpHtml } = useNodeHelpContent(nodeRef) await flushPromises() - expect(nodeHelpStore.renderedHelpHtml).toContain( + + expect(renderedHelpHtml.value).toContain( `src="/docs/${mockCoreNode.name}/image.png"` ) - expect(nodeHelpStore.renderedHelpHtml).toContain('alt="Test image"') + expect(renderedHelpHtml.value).toContain('alt="Test image"') }) it('should not prefix absolute img src in raw HTML', async () => { - const nodeHelpStore = useNodeHelpStore() - + const nodeRef = ref(mockCustomNode) mockFetch.mockResolvedValueOnce({ ok: true, text: async () => 'Absolute' }) - nodeHelpStore.openHelp(mockCustomNode as any) + const { renderedHelpHtml } = useNodeHelpContent(nodeRef) await flushPromises() - expect(nodeHelpStore.renderedHelpHtml).toContain( - 'src="/absolute/image.png"' - ) - expect(nodeHelpStore.renderedHelpHtml).toContain('alt="Absolute"') + + expect(renderedHelpHtml.value).toContain('src="/absolute/image.png"') + expect(renderedHelpHtml.value).toContain('alt="Absolute"') }) it('should not prefix external img src in raw HTML', async () => { - const nodeHelpStore = useNodeHelpStore() - + const nodeRef = ref(mockCustomNode) mockFetch.mockResolvedValueOnce({ ok: true, text: async () => 'External' }) - nodeHelpStore.openHelp(mockCustomNode as any) + const { renderedHelpHtml } = useNodeHelpContent(nodeRef) await flushPromises() - expect(nodeHelpStore.renderedHelpHtml).toContain( + + expect(renderedHelpHtml.value).toContain( 'src="https://example.com/image.png"' ) - expect(nodeHelpStore.renderedHelpHtml).toContain('alt="External"') + expect(renderedHelpHtml.value).toContain('alt="External"') }) it('should handle various quote styles in media src attributes', async () => { - const nodeHelpStore = useNodeHelpStore() - + const nodeRef = ref(mockCoreNode) mockFetch.mockResolvedValueOnce({ ok: true, text: async () => `# Media Test @@ -370,30 +321,61 @@ Testing quote styles in properly formed HTML: The MEDIA_SRC_REGEX handles both single and double quotes in img, video and source tags.` }) - nodeHelpStore.openHelp(mockCoreNode as any) + const { renderedHelpHtml } = useNodeHelpContent(nodeRef) await flushPromises() - // Check that all media elements with different quote styles are prefixed correctly - // Double quotes remain as double quotes - expect(nodeHelpStore.renderedHelpHtml).toContain( + // All media src attributes should be prefixed correctly + // Note: marked normalizes quotes to double quotes in output + expect(renderedHelpHtml.value).toContain( `src="/docs/${mockCoreNode.name}/video1.mp4"` ) - expect(nodeHelpStore.renderedHelpHtml).toContain( + expect(renderedHelpHtml.value).toContain( + `src="/docs/${mockCoreNode.name}/video2.mp4"` + ) + expect(renderedHelpHtml.value).toContain( `src="/docs/${mockCoreNode.name}/image1.png"` ) - expect(nodeHelpStore.renderedHelpHtml).toContain( + expect(renderedHelpHtml.value).toContain( + `src="/docs/${mockCoreNode.name}/image2.png"` + ) + expect(renderedHelpHtml.value).toContain( `src="/docs/${mockCoreNode.name}/video3.mp4"` ) - - // Single quotes remain as single quotes in the output - expect(nodeHelpStore.renderedHelpHtml).toContain( - `src='/docs/${mockCoreNode.name}/video2.mp4'` - ) - expect(nodeHelpStore.renderedHelpHtml).toContain( - `src='/docs/${mockCoreNode.name}/image2.png'` - ) - expect(nodeHelpStore.renderedHelpHtml).toContain( - `src='/docs/${mockCoreNode.name}/video3.webm'` + expect(renderedHelpHtml.value).toContain( + `src="/docs/${mockCoreNode.name}/video3.webm"` ) }) + + it('should ignore stale requests when node changes', async () => { + const nodeRef = ref(mockCoreNode) + let resolveFirst: (value: unknown) => void + const firstRequest = new Promise((resolve) => { + resolveFirst = resolve + }) + + mockFetch + .mockImplementationOnce(() => firstRequest) + .mockResolvedValueOnce({ + ok: true, + text: async () => '# Second node content' + }) + + const { helpContent } = useNodeHelpContent(nodeRef) + await nextTick() + + // Change node before first request completes + nodeRef.value = mockCustomNode + await nextTick() + await flushPromises() + + // Now resolve the first (stale) request + resolveFirst!({ + ok: true, + text: async () => '# First node content' + }) + await flushPromises() + + // Should have second node's content, not first + expect(helpContent.value).toBe('# Second node content') + }) }) diff --git a/src/composables/useNodeHelpContent.ts b/src/composables/useNodeHelpContent.ts new file mode 100644 index 000000000..81ef9ae47 --- /dev/null +++ b/src/composables/useNodeHelpContent.ts @@ -0,0 +1,79 @@ +import type { MaybeRefOrGetter } from 'vue' +import { computed, ref, toValue, watch } from 'vue' +import { useI18n } from 'vue-i18n' + +import { nodeHelpService } from '@/services/nodeHelpService' +import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore' +import { renderMarkdownToHtml } from '@/utils/markdownRendererUtil' +import { getNodeHelpBaseUrl } from '@/workbench/utils/nodeHelpUtil' + +/** + * Composable for fetching and rendering node help content. + * Creates independent state for each usage, allowing multiple panels + * to show help content without interfering with each other. + * + * @param nodeRef - Reactive reference to the node to show help for + * @returns Reactive help content state and rendered HTML + */ +export function useNodeHelpContent( + nodeRef: MaybeRefOrGetter +) { + const { locale } = useI18n() + + const helpContent = ref('') + const isLoading = ref(false) + const error = ref(null) + + let currentRequest: Promise | null = null + + const baseUrl = computed(() => { + const node = toValue(nodeRef) + if (!node) return '' + return getNodeHelpBaseUrl(node) + }) + + const renderedHelpHtml = computed(() => { + return renderMarkdownToHtml(helpContent.value, baseUrl.value) + }) + + // Watch for node changes and fetch help content + watch( + () => toValue(nodeRef), + async (node) => { + helpContent.value = '' + error.value = null + + if (node) { + isLoading.value = true + const request = (currentRequest = nodeHelpService.fetchNodeHelp( + node, + locale.value || 'en' + )) + + try { + const content = await request + if (currentRequest !== request) return + helpContent.value = content + } catch (e: unknown) { + if (currentRequest !== request) return + error.value = e instanceof Error ? e.message : String(e) + helpContent.value = node.description || '' + } finally { + if (currentRequest === request) { + currentRequest = null + isLoading.value = false + } + } + } + }, + { immediate: true } + ) + + return { + helpContent, + isLoading, + error, + baseUrl, + renderedHelpHtml + } +} diff --git a/src/composables/usePaste.test.ts b/src/composables/usePaste.test.ts new file mode 100644 index 000000000..4e1ac3503 --- /dev/null +++ b/src/composables/usePaste.test.ts @@ -0,0 +1,314 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import type { + LGraphCanvas, + LGraph, + LGraphGroup, + LGraphNode +} from '@/lib/litegraph/src/litegraph' +import { LiteGraph } from '@/lib/litegraph/src/litegraph' +import { app } from '@/scripts/app' +import { isImageNode } from '@/utils/litegraphUtil' +import { pasteImageNode, usePaste } from './usePaste' + +function createMockNode() { + return { + pos: [0, 0], + pasteFile: vi.fn(), + pasteFiles: vi.fn() + } +} + +function createImageFile( + name: string = 'test.png', + type: string = 'image/png' +): File { + return new File([''], name, { type }) +} + +function createAudioFile( + name: string = 'test.mp3', + type: string = 'audio/mpeg' +): File { + return new File([''], name, { type }) +} + +function createDataTransfer(files: File[] = []): DataTransfer { + const dataTransfer = new DataTransfer() + files.forEach((file) => dataTransfer.items.add(file)) + return dataTransfer +} + +const mockCanvas = { + current_node: null as LGraphNode | null, + graph: { + add: vi.fn(), + change: vi.fn() + } as Partial as LGraph, + graph_mouse: [100, 200], + pasteFromClipboard: vi.fn(), + _deserializeItems: vi.fn() +} as Partial as LGraphCanvas + +const mockCanvasStore = { + canvas: mockCanvas, + getCanvas: vi.fn(() => mockCanvas) +} + +const mockWorkspaceStore = { + shiftDown: false +} + +vi.mock('@vueuse/core', () => ({ + useEventListener: vi.fn((target, event, handler) => { + target.addEventListener(event, handler) + return () => target.removeEventListener(event, handler) + }) +})) + +vi.mock('@/renderer/core/canvas/canvasStore', () => ({ + useCanvasStore: () => mockCanvasStore +})) + +vi.mock('@/stores/workspaceStore', () => ({ + useWorkspaceStore: () => mockWorkspaceStore +})) + +vi.mock('@/scripts/app', () => ({ + app: { + loadGraphData: vi.fn() + } +})) + +vi.mock('@/lib/litegraph/src/litegraph', () => ({ + LiteGraph: { + createNode: vi.fn() + } +})) + +vi.mock('@/utils/litegraphUtil', () => ({ + isAudioNode: vi.fn(), + isImageNode: vi.fn(), + isVideoNode: vi.fn() +})) + +vi.mock('@/workbench/eventHelpers', () => ({ + shouldIgnoreCopyPaste: vi.fn() +})) + +describe('pasteImageNode', () => { + beforeEach(() => { + vi.clearAllMocks() + vi.mocked(mockCanvas.graph!.add).mockImplementation( + (node: LGraphNode | LGraphGroup) => node as LGraphNode + ) + }) + + it('should create new LoadImage node when no image node provided', () => { + const mockNode = createMockNode() + vi.mocked(LiteGraph.createNode).mockReturnValue( + mockNode as unknown as LGraphNode + ) + + const file = createImageFile() + const dataTransfer = createDataTransfer([file]) + + pasteImageNode(mockCanvas as unknown as LGraphCanvas, dataTransfer.items) + + expect(LiteGraph.createNode).toHaveBeenCalledWith('LoadImage') + expect(mockNode.pos).toEqual([100, 200]) + expect(mockCanvas.graph!.add).toHaveBeenCalledWith(mockNode) + expect(mockCanvas.graph!.change).toHaveBeenCalled() + expect(mockNode.pasteFile).toHaveBeenCalledWith(file) + }) + + it('should use existing image node when provided', () => { + const mockNode = createMockNode() + const file = createImageFile() + const dataTransfer = createDataTransfer([file]) + + pasteImageNode( + mockCanvas as unknown as LGraphCanvas, + dataTransfer.items, + mockNode as unknown as LGraphNode + ) + + expect(mockNode.pasteFile).toHaveBeenCalledWith(file) + expect(mockNode.pasteFiles).toHaveBeenCalledWith([file]) + }) + + it('should handle multiple image files', () => { + const mockNode = createMockNode() + const file1 = createImageFile('test1.png') + const file2 = createImageFile('test2.jpg', 'image/jpeg') + const dataTransfer = createDataTransfer([file1, file2]) + + pasteImageNode( + mockCanvas as unknown as LGraphCanvas, + dataTransfer.items, + mockNode as unknown as LGraphNode + ) + + expect(mockNode.pasteFile).toHaveBeenCalledWith(file1) + expect(mockNode.pasteFiles).toHaveBeenCalledWith([file1, file2]) + }) + + it('should do nothing when no image files present', () => { + const mockNode = createMockNode() + const dataTransfer = createDataTransfer() + + pasteImageNode( + mockCanvas as unknown as LGraphCanvas, + dataTransfer.items, + mockNode as unknown as LGraphNode + ) + + expect(mockNode.pasteFile).not.toHaveBeenCalled() + expect(mockNode.pasteFiles).not.toHaveBeenCalled() + }) + + it('should filter non-image items', () => { + const mockNode = createMockNode() + const imageFile = createImageFile() + const textFile = new File([''], 'test.txt', { type: 'text/plain' }) + const dataTransfer = createDataTransfer([textFile, imageFile]) + + pasteImageNode( + mockCanvas as unknown as LGraphCanvas, + dataTransfer.items, + mockNode as unknown as LGraphNode + ) + + expect(mockNode.pasteFile).toHaveBeenCalledWith(imageFile) + expect(mockNode.pasteFiles).toHaveBeenCalledWith([imageFile]) + }) +}) + +describe('usePaste', () => { + beforeEach(() => { + vi.clearAllMocks() + mockCanvas.current_node = null + mockWorkspaceStore.shiftDown = false + vi.mocked(mockCanvas.graph!.add).mockImplementation( + (node: LGraphNode | LGraphGroup) => node as LGraphNode + ) + }) + + it('should handle image paste', async () => { + const mockNode = createMockNode() + vi.mocked(LiteGraph.createNode).mockReturnValue( + mockNode as unknown as LGraphNode + ) + + usePaste() + + const file = createImageFile() + const dataTransfer = createDataTransfer([file]) + const event = new ClipboardEvent('paste', { clipboardData: dataTransfer }) + document.dispatchEvent(event) + + await vi.waitFor(() => { + expect(LiteGraph.createNode).toHaveBeenCalledWith('LoadImage') + expect(mockNode.pasteFile).toHaveBeenCalledWith(file) + }) + }) + + it('should handle audio paste', async () => { + const mockNode = createMockNode() + vi.mocked(LiteGraph.createNode).mockReturnValue( + mockNode as unknown as LGraphNode + ) + + usePaste() + + const file = createAudioFile() + const dataTransfer = createDataTransfer([file]) + const event = new ClipboardEvent('paste', { clipboardData: dataTransfer }) + document.dispatchEvent(event) + + await vi.waitFor(() => { + expect(LiteGraph.createNode).toHaveBeenCalledWith('LoadAudio') + expect(mockNode.pasteFile).toHaveBeenCalledWith(file) + }) + }) + + it('should handle workflow JSON paste', async () => { + const workflow = { version: '1.0', nodes: [], extra: {} } + + usePaste() + + const dataTransfer = new DataTransfer() + dataTransfer.setData('text/plain', JSON.stringify(workflow)) + + const event = new ClipboardEvent('paste', { clipboardData: dataTransfer }) + document.dispatchEvent(event) + + await vi.waitFor(() => { + expect(app.loadGraphData).toHaveBeenCalledWith(workflow) + }) + }) + + it('should ignore paste when shift is down', () => { + mockWorkspaceStore.shiftDown = true + + usePaste() + + const file = createImageFile() + const dataTransfer = createDataTransfer([file]) + const event = new ClipboardEvent('paste', { clipboardData: dataTransfer }) + document.dispatchEvent(event) + + expect(LiteGraph.createNode).not.toHaveBeenCalled() + }) + + it('should use existing image node when selected', () => { + const mockNode = { + is_selected: true, + pasteFile: vi.fn(), + pasteFiles: vi.fn() + } as unknown as Partial as LGraphNode + mockCanvas.current_node = mockNode + vi.mocked(isImageNode).mockReturnValue(true) + + usePaste() + + const file = createImageFile() + const dataTransfer = createDataTransfer([file]) + const event = new ClipboardEvent('paste', { clipboardData: dataTransfer }) + document.dispatchEvent(event) + + expect(mockNode.pasteFile).toHaveBeenCalledWith(file) + }) + + it('should call canvas pasteFromClipboard for non-workflow text', () => { + usePaste() + + const dataTransfer = new DataTransfer() + dataTransfer.setData('text/plain', 'just some text') + + const event = new ClipboardEvent('paste', { clipboardData: dataTransfer }) + document.dispatchEvent(event) + + expect(mockCanvas.pasteFromClipboard).toHaveBeenCalled() + }) + + it('should handle clipboard items with metadata', async () => { + const data = { test: 'data' } + const encoded = btoa(JSON.stringify(data)) + const html = `
` + + usePaste() + + const dataTransfer = new DataTransfer() + dataTransfer.setData('text/html', html) + + const event = new ClipboardEvent('paste', { clipboardData: dataTransfer }) + document.dispatchEvent(event) + + await vi.waitFor(() => { + expect(mockCanvas._deserializeItems).toHaveBeenCalledWith( + data, + expect.any(Object) + ) + }) + }) +}) diff --git a/src/composables/usePaste.ts b/src/composables/usePaste.ts index 551065f0d..1809eb838 100644 --- a/src/composables/usePaste.ts +++ b/src/composables/usePaste.ts @@ -1,7 +1,7 @@ import { useEventListener } from '@vueuse/core' +import type { LGraphCanvas, LGraphNode } from '@/lib/litegraph/src/litegraph' import { LiteGraph } from '@/lib/litegraph/src/litegraph' -import type { LGraphNode } from '@/lib/litegraph/src/litegraph' import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema' import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' import { app } from '@/scripts/app' @@ -14,9 +14,11 @@ function pasteClipboardItems(data: DataTransfer): boolean { const match = rawData.match(/data-metadata="([A-Za-z0-9+/=]+)"/)?.[1] if (!match) return false try { - useCanvasStore() - .getCanvas() - ._deserializeItems(JSON.parse(atob(match)), {}) + // Decode UTF-8 safe base64 + const binaryString = atob(match) + const bytes = Uint8Array.from(binaryString, (c) => c.charCodeAt(0)) + const decodedData = new TextDecoder().decode(bytes) + useCanvasStore().getCanvas()._deserializeItems(JSON.parse(decodedData), {}) return true } catch (err) { console.error(err) @@ -24,6 +26,51 @@ function pasteClipboardItems(data: DataTransfer): boolean { return false } +function pasteItemsOnNode( + items: DataTransferItemList, + node: LGraphNode | null, + contentType: string +): void { + if (!node) return + + const filteredItems = Array.from(items).filter((item) => + item.type.startsWith(contentType) + ) + + const blob = filteredItems[0]?.getAsFile() + if (!blob) return + + node.pasteFile?.(blob) + node.pasteFiles?.( + Array.from(filteredItems) + .map((i) => i.getAsFile()) + .filter((f) => f !== null) + ) +} + +export function pasteImageNode( + canvas: LGraphCanvas, + items: DataTransferItemList, + imageNode: LGraphNode | null = null +): void { + const { + graph, + graph_mouse: [posX, posY] + } = canvas + + if (!imageNode) { + // No image node selected: add a new one + const newNode = LiteGraph.createNode('LoadImage') + if (newNode) { + newNode.pos = [posX, posY] + imageNode = graph?.add(newNode) ?? null + } + graph?.change() + } + + pasteItemsOnNode(items, imageNode, 'image') +} + /** * Adds a handler on paste that extracts and loads images or workflows from pasted JSON data */ @@ -31,28 +78,6 @@ export const usePaste = () => { const workspaceStore = useWorkspaceStore() const canvasStore = useCanvasStore() - const pasteItemsOnNode = ( - items: DataTransferItemList, - node: LGraphNode | null, - contentType: string - ) => { - if (!node) return - - const filteredItems = Array.from(items).filter((item) => - item.type.startsWith(contentType) - ) - - const blob = filteredItems[0]?.getAsFile() - if (!blob) return - - node.pasteFile?.(blob) - node.pasteFiles?.( - Array.from(filteredItems) - .map((i) => i.getAsFile()) - .filter((f) => f !== null) - ) - } - useEventListener(document, 'paste', async (e) => { if (shouldIgnoreCopyPaste(e.target)) { // Default system copy @@ -78,8 +103,10 @@ export const usePaste = () => { const isVideoNodeSelected = isNodeSelected && isVideoNode(currentNode) const isAudioNodeSelected = isNodeSelected && isAudioNode(currentNode) - let imageNode: LGraphNode | null = isImageNodeSelected ? currentNode : null let audioNode: LGraphNode | null = isAudioNodeSelected ? currentNode : null + const imageNode: LGraphNode | null = isImageNodeSelected + ? currentNode + : null const videoNode: LGraphNode | null = isVideoNodeSelected ? currentNode : null @@ -87,16 +114,7 @@ export const usePaste = () => { // Look for image paste data for (const item of items) { if (item.type.startsWith('image/')) { - if (!imageNode) { - // No image node selected: add a new one - const newNode = LiteGraph.createNode('LoadImage') - if (newNode) { - newNode.pos = [canvas.graph_mouse[0], canvas.graph_mouse[1]] - imageNode = graph?.add(newNode) ?? null - } - graph?.change() - } - pasteItemsOnNode(items, imageNode, 'image') + pasteImageNode(canvas as LGraphCanvas, items, imageNode) return } else if (item.type.startsWith('video/')) { if (!videoNode) { diff --git a/src/composables/useProgressBarBackground.ts b/src/composables/useProgressBarBackground.ts new file mode 100644 index 000000000..4ff6f7341 --- /dev/null +++ b/src/composables/useProgressBarBackground.ts @@ -0,0 +1,47 @@ +type ProgressPercent = number | undefined + +const progressBarContainerClass = 'absolute inset-0' +const progressBarBaseClass = + 'pointer-events-none absolute inset-y-0 left-0 h-full transition-[width]' +const progressBarPrimaryClass = `${progressBarBaseClass} bg-interface-panel-job-progress-primary` +const progressBarSecondaryClass = `${progressBarBaseClass} bg-interface-panel-job-progress-secondary` + +function clampPercent(value: number) { + return Math.min(100, Math.max(0, value)) +} + +function normalizeProgressPercent(value: ProgressPercent) { + if (value === undefined || !Number.isFinite(value)) return undefined + + return clampPercent(value) +} + +function hasProgressPercent(value: ProgressPercent) { + return normalizeProgressPercent(value) !== undefined +} + +function hasAnyProgressPercent( + totalPercent: ProgressPercent, + currentPercent: ProgressPercent +) { + return hasProgressPercent(totalPercent) || hasProgressPercent(currentPercent) +} + +function progressPercentStyle(value: ProgressPercent) { + const normalized = normalizeProgressPercent(value) + + if (normalized === undefined) return undefined + + return { width: `${normalized}%` } +} + +export function useProgressBarBackground() { + return { + progressBarContainerClass, + progressBarPrimaryClass, + progressBarSecondaryClass, + hasProgressPercent, + hasAnyProgressPercent, + progressPercentStyle + } +} diff --git a/tests-ui/tests/composables/useServerLogs.test.ts b/src/composables/useServerLogs.test.ts similarity index 100% rename from tests-ui/tests/composables/useServerLogs.test.ts rename to src/composables/useServerLogs.test.ts diff --git a/tests-ui/tests/composables/useTemplateFiltering.test.ts b/src/composables/useTemplateFiltering.test.ts similarity index 62% rename from tests-ui/tests/composables/useTemplateFiltering.test.ts rename to src/composables/useTemplateFiltering.test.ts index 00cc7d856..f6e617cb7 100644 --- a/tests-ui/tests/composables/useTemplateFiltering.test.ts +++ b/src/composables/useTemplateFiltering.test.ts @@ -1,5 +1,7 @@ +import { createPinia, setActivePinia } from 'pinia' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { nextTick, ref } from 'vue' +import type { IFuseOptions } from 'fuse.js' import type { TemplateInfo } from '@/platform/workflow/templates/types/template' @@ -19,23 +21,43 @@ const defaultSettingStore = { set: vi.fn().mockResolvedValue(undefined) } +const defaultRankingStore = { + computeDefaultScore: vi.fn(() => 0), + computePopularScore: vi.fn(() => 0), + getUsageScore: vi.fn(() => 0), + computeFreshness: vi.fn(() => 0.5), + isLoaded: { value: false } +} + vi.mock('@/platform/settings/settingStore', () => ({ useSettingStore: vi.fn(() => defaultSettingStore) })) +vi.mock('@/stores/templateRankingStore', () => ({ + useTemplateRankingStore: vi.fn(() => defaultRankingStore) +})) + vi.mock('@/platform/telemetry', () => ({ useTelemetry: vi.fn(() => ({ trackTemplateFilterChanged: vi.fn() })) })) -const { useTemplateFiltering } = await import( - '@/composables/useTemplateFiltering' -) +const mockGetFuseOptions = vi.hoisted(() => vi.fn()) +vi.mock('@/scripts/api', () => ({ + api: { + getFuseOptions: mockGetFuseOptions + } +})) + +const { useTemplateFiltering } = + await import('@/composables/useTemplateFiltering') describe('useTemplateFiltering', () => { beforeEach(() => { + setActivePinia(createPinia()) vi.clearAllMocks() + mockGetFuseOptions.mockResolvedValue(null) }) afterEach(() => { @@ -259,4 +281,118 @@ describe('useTemplateFiltering', () => { 'beta-pro' ]) }) + + describe('loadFuseOptions', () => { + it('updates fuseOptions when getFuseOptions returns valid options', async () => { + const templates = ref([ + { + name: 'test-template', + description: 'Test template', + mediaType: 'image', + mediaSubtype: 'png' + } + ]) + + const customFuseOptions: IFuseOptions = { + keys: [ + { name: 'name', weight: 0.5 }, + { name: 'description', weight: 0.5 } + ], + threshold: 0.4, + includeScore: true + } + + mockGetFuseOptions.mockResolvedValueOnce(customFuseOptions) + + const { loadFuseOptions, filteredTemplates } = + useTemplateFiltering(templates) + + await loadFuseOptions() + + expect(mockGetFuseOptions).toHaveBeenCalledTimes(1) + expect(filteredTemplates.value).toBeDefined() + }) + + it('does not update fuseOptions when getFuseOptions returns null', async () => { + const templates = ref([ + { + name: 'test-template', + description: 'Test template', + mediaType: 'image', + mediaSubtype: 'png' + } + ]) + + mockGetFuseOptions.mockResolvedValueOnce(null) + + const { loadFuseOptions, filteredTemplates } = + useTemplateFiltering(templates) + + const initialResults = filteredTemplates.value + + await loadFuseOptions() + + expect(mockGetFuseOptions).toHaveBeenCalledTimes(1) + expect(filteredTemplates.value).toEqual(initialResults) + }) + + it('handles errors when getFuseOptions fails', async () => { + const templates = ref([ + { + name: 'test-template', + description: 'Test template', + mediaType: 'image', + mediaSubtype: 'png' + } + ]) + + mockGetFuseOptions.mockRejectedValueOnce(new Error('Network error')) + + const { loadFuseOptions, filteredTemplates } = + useTemplateFiltering(templates) + + const initialResults = filteredTemplates.value + + await expect(loadFuseOptions()).rejects.toThrow('Network error') + expect(filteredTemplates.value).toEqual(initialResults) + }) + + it('recreates Fuse instance when fuseOptions change', async () => { + const templates = ref([ + { + name: 'searchable-template', + description: 'This is a searchable template', + mediaType: 'image', + mediaSubtype: 'png' + }, + { + name: 'another-template', + description: 'Another template', + mediaType: 'image', + mediaSubtype: 'png' + } + ]) + + const { loadFuseOptions, searchQuery, filteredTemplates } = + useTemplateFiltering(templates) + + const customFuseOptions = { + keys: [{ name: 'name', weight: 1.0 }], + threshold: 0.2, + includeScore: true, + includeMatches: true + } + + mockGetFuseOptions.mockResolvedValueOnce(customFuseOptions) + + await loadFuseOptions() + await nextTick() + + searchQuery.value = 'searchable' + await nextTick() + + expect(filteredTemplates.value.length).toBeGreaterThan(0) + expect(mockGetFuseOptions).toHaveBeenCalledTimes(1) + }) + }) }) diff --git a/src/composables/useTemplateFiltering.ts b/src/composables/useTemplateFiltering.ts index c181e2f91..8f6753ab0 100644 --- a/src/composables/useTemplateFiltering.ts +++ b/src/composables/useTemplateFiltering.ts @@ -1,17 +1,35 @@ -import { refDebounced, watchDebounced } from '@vueuse/core' +import { refThrottled, watchDebounced } from '@vueuse/core' import Fuse from 'fuse.js' +import type { IFuseOptions } from 'fuse.js' import { computed, ref, watch } from 'vue' import type { Ref } from 'vue' import { useSettingStore } from '@/platform/settings/settingStore' import { useTelemetry } from '@/platform/telemetry' import type { TemplateInfo } from '@/platform/workflow/templates/types/template' +import { useTemplateRankingStore } from '@/stores/templateRankingStore' import { debounce } from 'es-toolkit/compat' +import { api } from '@/scripts/api' + +// Fuse.js configuration for fuzzy search +const defaultFuseOptions: IFuseOptions = { + keys: [ + { name: 'name', weight: 0.3 }, + { name: 'title', weight: 0.3 }, + { name: 'description', weight: 0.1 }, + { name: 'tags', weight: 0.2 }, + { name: 'models', weight: 0.3 } + ], + threshold: 0.33, + includeScore: true, + includeMatches: true +} export function useTemplateFiltering( templates: Ref | TemplateInfo[] ) { const settingStore = useSettingStore() + const rankingStore = useTemplateRankingStore() const searchQuery = ref('') const selectedModels = ref( @@ -25,32 +43,22 @@ export function useTemplateFiltering( ) const sortBy = ref< | 'default' + | 'recommended' + | 'popular' | 'alphabetical' | 'newest' | 'vram-low-to-high' | 'model-size-low-to-high' >(settingStore.get('Comfy.Templates.SortBy')) + const fuseOptions = ref>(defaultFuseOptions) + const templatesArray = computed(() => { const templateData = 'value' in templates ? templates.value : templates return Array.isArray(templateData) ? templateData : [] }) - // Fuse.js configuration for fuzzy search - const fuseOptions = { - keys: [ - { name: 'name', weight: 0.3 }, - { name: 'title', weight: 0.3 }, - { name: 'description', weight: 0.2 }, - { name: 'tags', weight: 0.1 }, - { name: 'models', weight: 0.1 } - ], - threshold: 0.4, - includeScore: true, - includeMatches: true - } - - const fuse = computed(() => new Fuse(templatesArray.value, fuseOptions)) + const fuse = computed(() => new Fuse(templatesArray.value, fuseOptions.value)) const availableModels = computed(() => { const modelSet = new Set() @@ -76,7 +84,7 @@ export function useTemplateFiltering( return ['ComfyUI', 'External or Remote API'] }) - const debouncedSearchQuery = refDebounced(searchQuery, 50) + const debouncedSearchQuery = refThrottled(searchQuery, 50) const filteredBySearch = computed(() => { if (!debouncedSearchQuery.value.trim()) { @@ -151,10 +159,42 @@ export function useTemplateFiltering( return Number.POSITIVE_INFINITY } + watch( + filteredByRunsOn, + (templates) => { + rankingStore.largestUsageScore = Math.max( + ...templates.map((t) => t.usage || 0) + ) + }, + { immediate: true } + ) + const sortedTemplates = computed(() => { const templates = [...filteredByRunsOn.value] switch (sortBy.value) { + case 'recommended': + // Curated: usage × 0.5 + internal × 0.3 + freshness × 0.2 + return templates.sort((a, b) => { + const scoreA = rankingStore.computeDefaultScore( + a.date, + a.searchRank, + a.usage + ) + const scoreB = rankingStore.computeDefaultScore( + b.date, + b.searchRank, + b.usage + ) + return scoreB - scoreA + }) + case 'popular': + // User-driven: usage × 0.9 + freshness × 0.1 + return templates.sort((a, b) => { + const scoreA = rankingStore.computePopularScore(a.date, a.usage) + const scoreB = rankingStore.computePopularScore(b.date, b.usage) + return scoreB - scoreA + }) case 'alphabetical': return templates.sort((a, b) => { const nameA = a.title || a.name || '' @@ -184,7 +224,7 @@ export function useTemplateFiltering( return vramA - vramB }) case 'model-size-low-to-high': - return templates.sort((a: any, b: any) => { + return templates.sort((a, b) => { const sizeA = typeof a.size === 'number' ? a.size : Number.POSITIVE_INFINITY const sizeB = @@ -194,7 +234,6 @@ export function useTemplateFiltering( }) case 'default': default: - // Keep original order (default order) return templates } }) @@ -206,7 +245,7 @@ export function useTemplateFiltering( selectedModels.value = [] selectedUseCases.value = [] selectedRunsOn.value = [] - sortBy.value = 'newest' + sortBy.value = 'default' } const removeModelFilter = (model: string) => { @@ -237,6 +276,13 @@ export function useTemplateFiltering( }) }, 500) + const loadFuseOptions = async () => { + const fetchedOptions = await api.getFuseOptions() + if (fetchedOptions) { + fuseOptions.value = fetchedOptions + } + } + // Watch for filter changes and track them watch( [searchQuery, selectedModels, selectedUseCases, selectedRunsOn, sortBy], @@ -309,6 +355,7 @@ export function useTemplateFiltering( resetFilters, removeModelFilter, removeUseCaseFilter, - removeRunsOnFilter + removeRunsOnFilter, + loadFuseOptions } } diff --git a/src/composables/useVueNodesMigrationDismissed.ts b/src/composables/useVueNodesMigrationDismissed.ts deleted file mode 100644 index 1d72760fc..000000000 --- a/src/composables/useVueNodesMigrationDismissed.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { createSharedComposable, useLocalStorage } from '@vueuse/core' - -// Browser storage events don't fire in the same tab, so separate -// useLocalStorage() calls create isolated reactive refs. Use shared -// composable to ensure all components use the same ref instance. -export const useVueNodesMigrationDismissed = createSharedComposable(() => - useLocalStorage('comfy.vueNodesMigration.dismissed', false) -) diff --git a/src/composables/useWorkflowActionsMenu.ts b/src/composables/useWorkflowActionsMenu.ts new file mode 100644 index 000000000..41d5bd24b --- /dev/null +++ b/src/composables/useWorkflowActionsMenu.ts @@ -0,0 +1,192 @@ +import type { MenuItem } from 'primevue/menuitem' +import type { ComputedRef, Ref } from 'vue' +import { computed } from 'vue' +import { useI18n } from 'vue-i18n' + +import { useWorkflowService } from '@/platform/workflow/core/services/workflowService' +import type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore' +import { + useWorkflowBookmarkStore, + useWorkflowStore +} from '@/platform/workflow/management/stores/workflowStore' +import { useCommandStore } from '@/stores/commandStore' +import { useSubgraphStore } from '@/stores/subgraphStore' + +interface WorkflowActionsMenuOptions { + /** Whether this is the root workflow level. Defaults to true. */ + isRoot?: boolean + /** Whether to include the delete workflow action. Defaults to true. */ + includeDelete?: boolean + /** Override the workflow to operate on. If not provided, uses activeWorkflow. */ + workflow?: Ref | ComputedRef +} + +export function useWorkflowActionsMenu( + startRename: () => void, + options: WorkflowActionsMenuOptions = {} +) { + const { isRoot = true, includeDelete = true, workflow } = options + const { t } = useI18n() + const workflowStore = useWorkflowStore() + const workflowService = useWorkflowService() + const bookmarkStore = useWorkflowBookmarkStore() + const commandStore = useCommandStore() + const subgraphStore = useSubgraphStore() + + const targetWorkflow = computed( + () => workflow?.value ?? workflowStore.activeWorkflow + ) + + /** Switch to the target workflow tab if it's not already active */ + const ensureWorkflowActive = async (wf: ComfyWorkflow | null) => { + if (!wf || wf === workflowStore.activeWorkflow) return + await workflowService.openWorkflow(wf) + } + + const menuItems = computed(() => { + const workflow = targetWorkflow.value + const isBlueprint = workflow + ? subgraphStore.isSubgraphBlueprint(workflow) + : false + + const items: MenuItem[] = [] + + const addItem = ( + label: string, + icon: string, + command: () => void, + visible = true, + disabled = false, + separator = false + ) => { + if (!visible) return + if (separator) items.push({ separator: true }) + items.push({ label, icon, command, disabled }) + } + + addItem( + t('g.rename'), + 'pi pi-pencil', + async () => { + await ensureWorkflowActive(targetWorkflow.value) + startRename() + }, + true, + isRoot && !workflow?.isPersisted + ) + + addItem( + t('breadcrumbsMenu.duplicate'), + 'pi pi-copy', + async () => { + if (workflow) { + await workflowService.duplicateWorkflow(workflow) + } + }, + isRoot && !isBlueprint + ) + + addItem( + t('menuLabels.Save'), + 'pi pi-save', + async () => { + await ensureWorkflowActive(workflow) + await commandStore.execute('Comfy.SaveWorkflow') + }, + isRoot, + false, + true + ) + + addItem( + t('menuLabels.Save As'), + 'pi pi-save', + async () => { + await ensureWorkflowActive(workflow) + await commandStore.execute('Comfy.SaveWorkflowAs') + }, + isRoot + ) + + addItem( + bookmarkStore.isBookmarked(workflow?.path ?? '') + ? t('tabMenu.removeFromBookmarks') + : t('tabMenu.addToBookmarks'), + 'pi pi-bookmark' + + (bookmarkStore.isBookmarked(workflow?.path ?? '') ? '-fill' : ''), + async () => { + if (workflow?.path) { + await bookmarkStore.toggleBookmarked(workflow.path) + } + }, + isRoot, + workflow?.isTemporary ?? false + ) + + addItem( + t('menuLabels.Export'), + 'pi pi-download', + async () => { + await ensureWorkflowActive(workflow) + await commandStore.execute('Comfy.ExportWorkflow') + }, + isRoot + ) + + addItem( + t('menuLabels.Export (API)'), + 'pi pi-download', + async () => { + await ensureWorkflowActive(workflow) + await commandStore.execute('Comfy.ExportWorkflowAPI') + }, + isRoot + ) + + addItem( + t('breadcrumbsMenu.clearWorkflow'), + 'pi pi-trash', + async () => { + await ensureWorkflowActive(workflow) + await commandStore.execute('Comfy.ClearWorkflow') + }, + true, + false, + true + ) + + addItem( + t('subgraphStore.publish'), + 'pi pi-upload', + async () => { + if (workflow) { + await workflowService.saveWorkflowAs(workflow) + } + }, + isRoot && isBlueprint, + false, + true + ) + + addItem( + isBlueprint + ? t('breadcrumbsMenu.deleteBlueprint') + : t('breadcrumbsMenu.deleteWorkflow'), + 'pi pi-times', + async () => { + if (workflow) { + await workflowService.deleteWorkflow(workflow) + } + }, + isRoot && includeDelete, + false, + true + ) + + return items + }) + + return { + menuItems + } +} diff --git a/src/config/staging.ts b/src/config/staging.ts new file mode 100644 index 000000000..df525f0b2 --- /dev/null +++ b/src/config/staging.ts @@ -0,0 +1,20 @@ +import { computed } from 'vue' + +import { isCloud } from '@/platform/distribution/types' +import { remoteConfig } from '@/platform/remoteConfig/remoteConfig' + +const BUILD_TIME_IS_STAGING = !__USE_PROD_CONFIG__ + +/** + * Returns whether the current environment is staging. + * - Cloud builds use runtime configuration (firebase_config.projectId containing '-dev') + * - OSS / localhost builds fall back to the build-time config determined by __USE_PROD_CONFIG__ + */ +export const isStaging = computed(() => { + if (!isCloud) { + return BUILD_TIME_IS_STAGING + } + + const projectId = remoteConfig.value.firebase_config?.projectId + return projectId?.includes('-dev') ?? BUILD_TIME_IS_STAGING +}) diff --git a/src/constants/serverConfig.ts b/src/constants/serverConfig.ts index 64e655e3d..bd47a0895 100644 --- a/src/constants/serverConfig.ts +++ b/src/constants/serverConfig.ts @@ -389,6 +389,13 @@ export const SERVER_CONFIG_ITEMS: ServerConfig[] = [ type: 'boolean', defaultValue: false }, + { + id: 'enable-manager-legacy-ui', + name: 'Use legacy Manager UI', + tooltip: 'Uses the legacy ComfyUI-Manager UI instead of the new UI.', + type: 'boolean', + defaultValue: false + }, { id: 'disable-all-custom-nodes', name: 'Disable loading all custom nodes.', diff --git a/src/constants/slotColors.ts b/src/constants/slotColors.ts index 60685e38c..63ef1b1ae 100644 --- a/src/constants/slotColors.ts +++ b/src/constants/slotColors.ts @@ -1,5 +1,5 @@ export function getSlotColor(type?: string | number | null): string { if (!type) return '#AAA' const typeStr = String(type).toUpperCase() - return `var(--color-datatype-${typeStr})` + return `var(--color-datatype-${typeStr}, #AAA)` } diff --git a/src/core/graph/subgraph/SubgraphNodeWidget.vue b/src/core/graph/subgraph/SubgraphNodeWidget.vue deleted file mode 100644 index 0051281c2..000000000 --- a/src/core/graph/subgraph/SubgraphNodeWidget.vue +++ /dev/null @@ -1,56 +0,0 @@ - - diff --git a/tests-ui/tests/widgets/proxyWidget.test.ts b/src/core/graph/subgraph/proxyWidget.test.ts similarity index 85% rename from tests-ui/tests/widgets/proxyWidget.test.ts rename to src/core/graph/subgraph/proxyWidget.test.ts index 940ee66f0..5b961ad80 100644 --- a/tests-ui/tests/widgets/proxyWidget.test.ts +++ b/src/core/graph/subgraph/proxyWidget.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test, vi } from 'vitest' import { registerProxyWidgets } from '@/core/graph/subgraph/proxyWidget' +import { promoteWidget } from '@/core/graph/subgraph/proxyWidgetUtils' import { parseProxyWidgets } from '@/core/schemas/proxyWidget' import { LGraphNode } from '@/lib/litegraph/src/litegraph' import type { LGraphCanvas, SubgraphNode } from '@/lib/litegraph/src/litegraph' @@ -8,7 +9,7 @@ import type { LGraphCanvas, SubgraphNode } from '@/lib/litegraph/src/litegraph' import { createTestSubgraph, createTestSubgraphNode -} from '../litegraph/subgraph/fixtures/subgraphHelpers' +} from '@/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers' const canvasEl: Partial = { addEventListener() {} } const canvas: Partial = { canvas: canvasEl as HTMLCanvasElement } @@ -118,4 +119,23 @@ describe('Subgraph proxyWidgets', () => { subgraphNode.widgets[0].computedHeight = 10 expect(subgraphNode.widgets[0].value).toBe('value') }) + test('Prevents duplicate promotion', () => { + const [subgraphNode, innerNodes] = setupSubgraph(1) + innerNodes[0].addWidget('text', 'stringWidget', 'value', () => {}) + + const widget = innerNodes[0].widgets![0] + + // Promote once + promoteWidget(innerNodes[0], widget, [subgraphNode]) + expect(subgraphNode.widgets.length).toBe(1) + expect(subgraphNode.properties.proxyWidgets).toHaveLength(1) + + // Try to promote again - should not create duplicate + promoteWidget(innerNodes[0], widget, [subgraphNode]) + expect(subgraphNode.widgets.length).toBe(1) + expect(subgraphNode.properties.proxyWidgets).toHaveLength(1) + expect(subgraphNode.properties.proxyWidgets).toStrictEqual([ + ['1', 'stringWidget'] + ]) + }) }) diff --git a/src/core/graph/subgraph/proxyWidget.ts b/src/core/graph/subgraph/proxyWidget.ts index ef04bd76b..60f6d456a 100644 --- a/src/core/graph/subgraph/proxyWidget.ts +++ b/src/core/graph/subgraph/proxyWidget.ts @@ -211,11 +211,9 @@ function newProxyFromOverlay(subgraphNode: SubgraphNode, overlay: Overlay) { } return Reflect.get(redirectedTarget, property, redirectedReceiver) }, - set(_t: IBaseWidget, property: string, value: unknown, receiver: object) { + set(_t: IBaseWidget, property: string, value: unknown) { let redirectedTarget: object = backingWidget - let redirectedReceiver = receiver - if (property == 'value') redirectedReceiver = backingWidget - else if (property == 'computedHeight') { + if (property == 'computedHeight') { if (overlay.widgetName.startsWith('$$') && linkedNode) { updatePreviews(linkedNode) } @@ -228,9 +226,8 @@ function newProxyFromOverlay(subgraphNode: SubgraphNode, overlay: Overlay) { } if (Object.prototype.hasOwnProperty.call(overlay, property)) { redirectedTarget = overlay - redirectedReceiver = overlay } - return Reflect.set(redirectedTarget, property, value, redirectedReceiver) + return Reflect.set(redirectedTarget, property, value, redirectedTarget) }, getPrototypeOf() { return Reflect.getPrototypeOf(backingWidget) diff --git a/src/core/graph/subgraph/proxyWidgetUtils.ts b/src/core/graph/subgraph/proxyWidgetUtils.ts index 27961e54d..eafb0f1dd 100644 --- a/src/core/graph/subgraph/proxyWidgetUtils.ts +++ b/src/core/graph/subgraph/proxyWidgetUtils.ts @@ -29,8 +29,13 @@ export function promoteWidget( parents: SubgraphNode[] ) { for (const parent of parents) { + const existingProxyWidgets = getProxyWidgets(parent) + // Prevent duplicate promotion + if (existingProxyWidgets.some(matchesPropertyItem([node, widget]))) { + continue + } const proxyWidgets = [ - ...getProxyWidgets(parent), + ...existingProxyWidgets, widgetItemToProperty([node, widget]) ] parent.properties.proxyWidgets = proxyWidgets diff --git a/src/core/graph/subgraph/useSubgraphNodeDialog.ts b/src/core/graph/subgraph/useSubgraphNodeDialog.ts deleted file mode 100644 index 6004e2850..000000000 --- a/src/core/graph/subgraph/useSubgraphNodeDialog.ts +++ /dev/null @@ -1,27 +0,0 @@ -import SubgraphNode from '@/core/graph/subgraph/SubgraphNode.vue' -import { useDialogStore } from '@/stores/dialogStore' -import type { DialogComponentProps } from '@/stores/dialogStore' - -const key = 'global-subgraph-node-config' - -export function showSubgraphNodeDialog() { - const dialogStore = useDialogStore() - const dialogComponentProps: DialogComponentProps = { - modal: false, - position: 'topright', - pt: { - root: { - class: 'bg-node-component-surface mt-22' - }, - header: { - class: 'h-8 text-xs ml-3' - } - } - } - dialogStore.showDialog({ - title: 'Parameters', - key, - component: SubgraphNode, - dialogComponentProps - }) -} diff --git a/src/core/graph/widgets/dynamicWidgets.test.ts b/src/core/graph/widgets/dynamicWidgets.test.ts new file mode 100644 index 000000000..0252691d1 --- /dev/null +++ b/src/core/graph/widgets/dynamicWidgets.test.ts @@ -0,0 +1,206 @@ +import { setActivePinia } from 'pinia' +import { createTestingPinia } from '@pinia/testing' +import { describe, expect, test } from 'vitest' +import { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph' +import { transformInputSpecV1ToV2 } from '@/schemas/nodeDef/migration' +import type { InputSpec } from '@/schemas/nodeDefSchema' +import { useLitegraphService } from '@/services/litegraphService' +import type { HasInitialMinSize } from '@/services/litegraphService' + +setActivePinia(createTestingPinia()) +type DynamicInputs = ('INT' | 'STRING' | 'IMAGE' | DynamicInputs)[][] + +const { addNodeInput } = useLitegraphService() + +function nextTick() { + return new Promise((r) => requestAnimationFrame(() => r())) +} + +function addDynamicCombo(node: LGraphNode, inputs: DynamicInputs) { + const namePrefix = `${node.widgets?.length ?? 0}` + function getSpec( + inputs: DynamicInputs, + depth: number = 0 + ): { key: string; inputs: object }[] { + return inputs.map((group, groupIndex) => { + const inputs = group.map((input, inputIndex) => [ + `${namePrefix}.${depth}.${inputIndex}`, + Array.isArray(input) + ? ['COMFY_DYNAMICCOMBO_V3', { options: getSpec(input, depth + 1) }] + : [input, {}] + ]) + return { + key: `${groupIndex}`, + inputs: { required: Object.fromEntries(inputs) } + } + }) + } + const inputSpec: Required = [ + 'COMFY_DYNAMICCOMBO_V3', + { options: getSpec(inputs) } + ] + addNodeInput( + node, + transformInputSpecV1ToV2(inputSpec, { name: namePrefix, isOptional: false }) + ) +} +function addAutogrow(node: LGraphNode, template: unknown) { + addNodeInput( + node, + transformInputSpecV1ToV2(['COMFY_AUTOGROW_V3', { template }], { + name: `${node.inputs.length}`, + isOptional: false + }) + ) +} +function connectInput(node: LGraphNode, inputIndex: number, graph: LGraph) { + const node2 = testNode() + node2.addOutput('out', '*') + graph.add(node2) + node2.connect(0, node, inputIndex) +} +function testNode() { + const node: LGraphNode & Partial = new LGraphNode('test') + node.widgets = [] + node._initialMinSize = { width: 1, height: 1 } + node.constructor.nodeData = { + name: 'testnode' + } as typeof node.constructor.nodeData + return node as LGraphNode & Required> +} + +describe('Dynamic Combos', () => { + test('Can add widget on selection', () => { + const node = testNode() + addDynamicCombo(node, [['INT'], ['INT', 'STRING']]) + expect(node.widgets.length).toBe(2) + node.widgets[0].value = '1' + expect(node.widgets.length).toBe(3) + }) + test('Can add nested widgets', () => { + const node = testNode() + addDynamicCombo(node, [['INT'], [[[], ['STRING']]]]) + expect(node.widgets.length).toBe(2) + node.widgets[0].value = '1' + expect(node.widgets.length).toBe(2) + node.widgets[1].value = '1' + expect(node.widgets.length).toBe(3) + }) + test('Can add input', () => { + const node = testNode() + addDynamicCombo(node, [['INT'], ['IMAGE']]) + expect(node.widgets.length).toBe(2) + node.widgets[0].value = '1' + expect(node.widgets.length).toBe(1) + expect(node.inputs.length).toBe(2) + expect(node.inputs[1].type).toBe('IMAGE') + }) + test('Dynamically added inputs are well ordered', () => { + const node = testNode() + addDynamicCombo(node, [['INT'], ['IMAGE']]) + addDynamicCombo(node, [['INT'], ['IMAGE']]) + node.widgets[2].value = '1' + node.widgets[0].value = '1' + expect(node.widgets.length).toBe(2) + expect(node.inputs.length).toBe(4) + expect(node.inputs[1].name).toBe('0.0.0.0') + expect(node.inputs[3].name).toBe('2.2.0.0') + }) +}) +describe('Autogrow', () => { + const inputsSpec = { required: { image: ['IMAGE', {}] } } + test('Can name by prefix', () => { + const graph = new LGraph() + const node = testNode() + graph.add(node) + addAutogrow(node, { input: inputsSpec, prefix: 'test' }) + connectInput(node, 0, graph) + connectInput(node, 1, graph) + connectInput(node, 2, graph) + expect(node.inputs.length).toBe(4) + expect(node.inputs[0].name).toBe('0.test0') + expect(node.inputs[2].name).toBe('0.test2') + }) + test('Can name by list of names', () => { + const graph = new LGraph() + const node = testNode() + graph.add(node) + addAutogrow(node, { input: inputsSpec, names: ['a', 'b', 'c'] }) + connectInput(node, 0, graph) + connectInput(node, 1, graph) + connectInput(node, 2, graph) + expect(node.inputs.length).toBe(3) + expect(node.inputs[0].name).toBe('0.a') + expect(node.inputs[2].name).toBe('0.c') + }) + test('Can add autogrow with min input count', () => { + const node = testNode() + addAutogrow(node, { min: 4, input: inputsSpec }) + expect(node.inputs.length).toBe(4) + }) + test('Adding connections will cause growth up to max', () => { + const graph = new LGraph() + const node = testNode() + graph.add(node) + addAutogrow(node, { min: 1, input: inputsSpec, prefix: 'test', max: 3 }) + expect(node.inputs.length).toBe(1) + + connectInput(node, 0, graph) + expect(node.inputs.length).toBe(2) + connectInput(node, 1, graph) + expect(node.inputs.length).toBe(3) + connectInput(node, 2, graph) + expect(node.inputs.length).toBe(3) + }) + test('Removing connections decreases to min', async () => { + const graph = new LGraph() + const node = testNode() + graph.add(node) + addAutogrow(node, { min: 4, input: inputsSpec, prefix: 'test' }) + connectInput(node, 3, graph) + connectInput(node, 4, graph) + connectInput(node, 5, graph) + expect(node.inputs.length).toBe(7) + + node.disconnectInput(4) + await nextTick() + expect(node.inputs.length).toBe(6) + node.disconnectInput(3) + await nextTick() + expect(node.inputs.length).toBe(5) + + connectInput(node, 0, graph) + expect(node.inputs.length).toBe(5) + node.disconnectInput(0) + await nextTick() + expect(node.inputs.length).toBe(5) + }) + test('Can deserialize a complex node', async () => { + const graph = new LGraph() + const node = testNode() + graph.add(node) + addAutogrow(node, { min: 1, input: inputsSpec, prefix: 'a' }) + addAutogrow(node, { min: 1, input: inputsSpec, prefix: 'b' }) + addNodeInput(node, { name: 'aa', isOptional: false, type: 'IMAGE' }) + + connectInput(node, 0, graph) + connectInput(node, 1, graph) + connectInput(node, 3, graph) + connectInput(node, 4, graph) + + const serialized = graph.serialize() + graph.clear() + graph.configure(serialized) + const newNode = graph.nodes[0]! + + expect(newNode.inputs.map((i) => i.name)).toStrictEqual([ + '0.a0', + '0.a1', + '0.a2', + '1.b0', + '1.b1', + '1.b2', + 'aa' + ]) + }) +}) diff --git a/src/core/graph/widgets/dynamicWidgets.ts b/src/core/graph/widgets/dynamicWidgets.ts index a113881eb..7e3f7fd71 100644 --- a/src/core/graph/widgets/dynamicWidgets.ts +++ b/src/core/graph/widgets/dynamicWidgets.ts @@ -1,11 +1,70 @@ +import { remove } from 'es-toolkit' +import { shallowReactive } from 'vue' + +import { useChainCallback } from '@/composables/functional/useChainCallback' +import type { + ISlotType, + INodeInputSlot, + INodeOutputSlot +} from '@/lib/litegraph/src/interfaces' import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' +import { LiteGraph } from '@/lib/litegraph/src/litegraph' +import type { LLink } from '@/lib/litegraph/src/LLink' +import { commonType } from '@/lib/litegraph/src/utils/type' import { transformInputSpecV1ToV2 } from '@/schemas/nodeDef/migration' import type { ComboInputSpec, InputSpec } from '@/schemas/nodeDefSchema' -import { zDynamicComboInputSpec } from '@/schemas/nodeDefSchema' +import type { InputSpec as InputSpecV2 } from '@/schemas/nodeDef/nodeDefSchemaV2' +import { + zAutogrowOptions, + zDynamicComboInputSpec +} from '@/schemas/nodeDefSchema' import { useLitegraphService } from '@/services/litegraphService' import { app } from '@/scripts/app' import type { ComfyApp } from '@/scripts/app' +const INLINE_INPUTS = false + +type MatchTypeNode = LGraphNode & + Pick, 'onConnectionsChange'> & { + comfyDynamic: { matchType: Record> } + } +type AutogrowNode = LGraphNode & + Pick, 'onConnectionsChange' | 'widgets'> & { + comfyDynamic: { + autogrow: Record< + string, + { + min: number + max: number + inputSpecs: InputSpecV2[] + prefix?: string + names?: string[] + } + > + } + } + +function ensureWidgetForInput(node: LGraphNode, input: INodeInputSlot) { + node.widgets ??= [] + const { widget } = input + if (widget && node.widgets.some((w) => w.name === widget.name)) return + node.widgets.push({ + draw(ctx, _n, _w, y) { + ctx.save() + ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR + ctx.fillText(input.label ?? input.name, 20, y + 15) + ctx.restore() + }, + name: input.name, + options: {}, + serialize: false, + type: 'shim', + y: 0 + }) + input.alwaysVisible = true + input.widget = { name: input.name } +} + function dynamicComboWidget( node: LGraphNode, inputName: string, @@ -28,48 +87,49 @@ function dynamicComboWidget( appArg, widgetName ) - let currentDynamicNames: string[] = [] + function isInGroup(e: { name: string }): boolean { + return e.name.startsWith(inputName + '.') + } const updateWidgets = (value?: string) => { if (!node.widgets) throw new Error('Not Reachable') const newSpec = value ? options[value] : undefined - //TODO: Calculate intersection for widgets that persist across options - //This would potentially allow links to be retained - for (const name of currentDynamicNames) { - const inputIndex = node.inputs.findIndex((input) => input.name === name) - if (inputIndex !== -1) node.removeInput(inputIndex) - const widgetIndex = node.widgets.findIndex( - (widget) => widget.name === name - ) - if (widgetIndex === -1) continue - node.widgets[widgetIndex].value = undefined - node.widgets.splice(widgetIndex, 1) - } - currentDynamicNames = [] + + const removedInputs = remove(node.inputs, isInGroup) + for (const widget of remove(node.widgets, isInGroup)) widget.onRemove?.() + if (!newSpec) return const insertionPoint = node.widgets.findIndex((w) => w === widget) + 1 const startingLength = node.widgets.length - const inputInsertionPoint = - node.inputs.findIndex((i) => i.name === widget.name) + 1 const startingInputLength = node.inputs.length + if (insertionPoint === 0) throw new Error("Dynamic widget doesn't exist on node") - const inputTypes: [Record | undefined, boolean][] = [ - [newSpec.required, false], - [newSpec.optional, true] + const inputTypes: (Record | undefined)[] = [ + newSpec.required, + newSpec.optional ] - for (const [inputType, isOptional] of inputTypes) - for (const name in inputType ?? {}) { - addNodeInput( - node, - transformInputSpecV1ToV2(inputType![name], { - name, - isOptional - }) - ) - currentDynamicNames.push(name) + inputTypes.forEach((inputType, idx) => { + for (const key in inputType ?? {}) { + const name = `${widget.name}.${key}` + const specToAdd = transformInputSpecV1ToV2(inputType![key], { + name, + isOptional: idx !== 0 + }) + specToAdd.display_name = key + addNodeInput(node, specToAdd) + const newInputs = node.inputs + .slice(startingInputLength) + .filter((inp) => inp.name.startsWith(name)) + for (const newInput of newInputs) { + if (INLINE_INPUTS && !newInput.widget) + ensureWidgetForInput(node, newInput) + } } + }) + const inputInsertionPoint = + node.inputs.findIndex((i) => i.name === widget.name) + 1 const addedWidgets = node.widgets.splice(startingLength) node.widgets.splice(insertionPoint, 0, ...addedWidgets) if (inputInsertionPoint === 0) { @@ -81,19 +141,45 @@ function dynamicComboWidget( throw new Error('Failed to find input socket for ' + widget.name) return } - const addedInputs = node - .spliceInputs(startingInputLength) - .map((addedInput) => { + const addedInputs = spliceInputs(node, startingInputLength).map( + (addedInput) => { const existingInput = node.inputs.findIndex( (existingInput) => addedInput.name === existingInput.name ) return existingInput === -1 ? addedInput - : node.spliceInputs(existingInput, 1)[0] - }) + : spliceInputs(node, existingInput, 1)[0] + } + ) //assume existing inputs are in correct order - node.spliceInputs(inputInsertionPoint, 0, ...addedInputs) + spliceInputs(node, inputInsertionPoint, 0, ...addedInputs) + + for (const input of removedInputs) { + const inputIndex = node.inputs.findIndex((inp) => inp.name === input.name) + if (inputIndex === -1) { + node.inputs.push(input) + node.removeInput(node.inputs.length - 1) + } else { + node.inputs[inputIndex].link = input.link + if (!input.link) continue + const link = node.graph?.links?.[input.link] + if (!link) continue + link.target_slot = inputIndex + node.onConnectionsChange?.( + LiteGraph.INPUT, + inputIndex, + true, + link, + node.inputs[inputIndex] + ) + } + } + node.size[1] = node.computeSize([...node.size])[1] + if (!node.graph) return + node._setConcreteSlots() + node.arrange() + app.canvas?.setDirty(true, true) } //A little hacky, but onConfigure won't work. //It fires too late and is overly disruptive @@ -112,3 +198,403 @@ function dynamicComboWidget( } export const dynamicWidgets = { COMFY_DYNAMICCOMBO_V3: dynamicComboWidget } +const dynamicInputs: Record< + string, + (node: LGraphNode, inputSpec: InputSpecV2) => void +> = { + COMFY_AUTOGROW_V3: applyAutogrow, + COMFY_MATCHTYPE_V3: applyMatchType +} + +export function applyDynamicInputs( + node: LGraphNode, + inputSpec: InputSpecV2 +): boolean { + if (!(inputSpec.type in dynamicInputs)) return false + //TODO: move parsing/validation of inputSpec here? + dynamicInputs[inputSpec.type](node, inputSpec) + return true +} +function spliceInputs( + node: LGraphNode, + startIndex: number, + deleteCount = -1, + ...toAdd: INodeInputSlot[] +): INodeInputSlot[] { + if (deleteCount < 0) return node.inputs.splice(startIndex) + const ret = node.inputs.splice(startIndex, deleteCount, ...toAdd) + node.inputs.slice(startIndex).forEach((input, index) => { + const link = input.link && node.graph?.links?.get(input.link) + if (link) link.target_slot = startIndex + index + }) + return ret +} + +function changeOutputType( + node: LGraphNode, + output: INodeOutputSlot, + combinedType: ISlotType +) { + if (output.type === combinedType) return + output.type = combinedType + + //check and potentially remove links + if (!node.graph) return + for (const link_id of output.links ?? []) { + const link = node.graph.links[link_id] + if (!link) continue + const { input, inputNode, subgraphOutput } = link.resolve(node.graph) + const inputType = (input ?? subgraphOutput)?.type + if (!inputType) continue + const keep = LiteGraph.isValidConnection(combinedType, inputType) + if (!keep && subgraphOutput) subgraphOutput.disconnect() + else if (!keep && inputNode) inputNode.disconnectInput(link.target_slot) + if (input && inputNode?.onConnectionsChange) + inputNode.onConnectionsChange( + LiteGraph.INPUT, + link.target_slot, + keep, + link, + input + ) + } +} + +function withComfyMatchType(node: LGraphNode): asserts node is MatchTypeNode { + if (node.comfyDynamic?.matchType) return + node.comfyDynamic ??= {} + node.comfyDynamic.matchType = {} + + const outputGroups = node.constructor.nodeData?.output_matchtypes + node.onConnectionsChange = useChainCallback( + node.onConnectionsChange, + function ( + this: MatchTypeNode, + contype: ISlotType, + slot: number, + iscon: boolean, + linf: LLink | null | undefined + ) { + const input = this.inputs[slot] + if (contype !== LiteGraph.INPUT || !this.graph || !input) return + const [matchKey, matchGroup] = Object.entries( + this.comfyDynamic.matchType + ).find(([, group]) => input.name in group) ?? ['', undefined] + if (!matchGroup) return + if (iscon && linf) { + const { output, subgraphInput } = linf.resolve(this.graph) + const connectingType = (output ?? subgraphInput)?.type + if (connectingType) linf.type = connectingType + } + //NOTE: inputs contains input + const groupInputs: INodeInputSlot[] = node.inputs.filter( + (inp) => inp.name in matchGroup + ) + const connectedTypes = groupInputs.map((inp) => { + if (!inp.link) return '*' + const link = this.graph!.links[inp.link] + if (!link) return '*' + const { output, subgraphInput } = link.resolve(this.graph!) + return (output ?? subgraphInput)?.type ?? '*' + }) + //An input slot can accept a connection that is + // - Compatible with original type + // - Compatible with all other input types + //An output slot can output + // - Only what every input can output + groupInputs.forEach((input, idx) => { + const otherConnected = [ + ...connectedTypes.slice(0, idx), + ...connectedTypes.slice(idx + 1) + ] + const combinedType = commonType( + ...otherConnected, + matchGroup[input.name] + ) + if (!combinedType) throw new Error('invalid connection') + input.type = combinedType + }) + const outputType = commonType(...connectedTypes) + if (!outputType) throw new Error('invalid connection') + this.outputs.forEach((output, idx) => { + if (!(outputGroups?.[idx] == matchKey)) return + changeOutputType(this, output, outputType) + }) + app.canvas?.setDirty(true, true) + } + ) +} + +function applyMatchType(node: LGraphNode, inputSpec: InputSpecV2) { + const { addNodeInput } = useLitegraphService() + const name = inputSpec.name + const { allowed_types, template_id } = ( + inputSpec as InputSpecV2 & { + template: { allowed_types: string; template_id: string } + } + ).template + const typedSpec = { ...inputSpec, type: allowed_types } + addNodeInput(node, typedSpec) + withComfyMatchType(node) + node.comfyDynamic.matchType[template_id] ??= {} + node.comfyDynamic.matchType[template_id][name] = allowed_types + + //TODO: instead apply on output add? + //ensure outputs get updated + const index = node.inputs.length - 1 + requestAnimationFrame(() => { + const input = node.inputs[index] + if (!input) return + node.inputs[index] = shallowReactive(input) + node.onConnectionsChange?.( + LiteGraph.INPUT, + index, + !!input.link, + input.link ? node.graph?.links?.[input.link] : undefined, + input + ) + }) +} + +function autogrowOrdinalToName( + ordinal: number, + key: string, + groupName: string, + node: AutogrowNode +) { + const { + names, + prefix = '', + inputSpecs + } = node.comfyDynamic.autogrow[groupName] + const baseName = names + ? names[ordinal] + : (inputSpecs.length == 1 ? prefix : key) + ordinal + return { name: `${groupName}.${baseName}`, display_name: baseName } +} + +function addAutogrowGroup( + ordinal: number, + groupName: string, + node: AutogrowNode +) { + const { addNodeInput } = useLitegraphService() + const { max, min, inputSpecs } = node.comfyDynamic.autogrow[groupName] + if (ordinal >= max) return + + const namedSpecs = inputSpecs.map((input) => ({ + ...input, + isOptional: ordinal >= (min ?? 0) || input.isOptional, + ...autogrowOrdinalToName(ordinal, input.name, groupName, node) + })) + + const newInputs = namedSpecs.map((namedSpec) => { + addNodeInput(node, namedSpec) + const input = spliceInputs(node, node.inputs.length - 1, 1)[0] + if (inputSpecs.length !== 1 || (INLINE_INPUTS && !input.widget)) + ensureWidgetForInput(node, input) + return input + }) + + for (const newInput of newInputs) { + for (const existingInput of remove( + node.inputs, + (inp) => inp.name === newInput.name + )) { + //NOTE: link.target_slot is updated on spliceInputs call + newInput.link ??= existingInput.link + } + } + + const targetName = autogrowOrdinalToName( + ordinal - 1, + inputSpecs.at(-1)!.name, + groupName, + node + ).name + const lastIndex = node.inputs.findLastIndex((inp) => + inp.name.startsWith(targetName) + ) + const insertionIndex = lastIndex === -1 ? node.inputs.length : lastIndex + 1 + spliceInputs(node, insertionIndex, 0, ...newInputs) + app.canvas?.setDirty(true, true) +} + +const ORDINAL_REGEX = /\d+$/ +function resolveAutogrowOrdinal( + inputName: string, + groupName: string, + node: AutogrowNode +): number | undefined { + //TODO preslice groupname? + const name = inputName.slice(groupName.length + 1) + const { names } = node.comfyDynamic.autogrow[groupName] + if (names) { + const ordinal = names.findIndex((s) => s === name) + return ordinal === -1 ? undefined : ordinal + } + const match = name.match(ORDINAL_REGEX) + if (!match) return undefined + const ordinal = parseInt(match[0]) + return ordinal !== ordinal ? undefined : ordinal +} +function autogrowInputConnected(index: number, node: AutogrowNode) { + const input = node.inputs[index] + const groupName = input.name.slice(0, input.name.lastIndexOf('.')) + const lastInput = node.inputs.findLast((inp) => + inp.name.startsWith(groupName + '.') + ) + const ordinal = resolveAutogrowOrdinal(input.name, groupName, node) + if ( + !lastInput || + ordinal == undefined || + (ordinal !== resolveAutogrowOrdinal(lastInput.name, groupName, node) && + !app.configuringGraph) + ) + return + addAutogrowGroup(ordinal + 1, groupName, node) +} +function autogrowInputDisconnected(index: number, node: AutogrowNode) { + const input = node.inputs[index] + if (!input) return + const groupName = input.name.slice(0, input.name.lastIndexOf('.')) + const { min = 1, inputSpecs } = node.comfyDynamic.autogrow[groupName] + const ordinal = resolveAutogrowOrdinal(input.name, groupName, node) + if (ordinal == undefined || ordinal + 1 < min) return + + //resolve all inputs in group + const groupInputs = node.inputs.filter( + (inp) => + inp.name.startsWith(groupName + '.') && + inp.name.lastIndexOf('.') === groupName.length + ) + const stride = inputSpecs.length + if (stride + index >= node.inputs.length) return + if (groupInputs.length % stride !== 0) { + console.error('Failed to group multi-input autogrow inputs') + return + } + app.canvas?.setDirty(true, true) + //groupBy would be nice here, but may not be supported + for (let column = 0; column < stride; column++) { + for ( + let bubbleOrdinal = ordinal * stride + column; + bubbleOrdinal + stride < groupInputs.length; + bubbleOrdinal += stride + ) { + const curInput = groupInputs[bubbleOrdinal] + curInput.link = groupInputs[bubbleOrdinal + stride].link + if (!curInput.link) continue + const link = node.graph?.links[curInput.link] + if (!link) continue + const curIndex = node.inputs.findIndex((inp) => inp === curInput) + if (curIndex === -1) throw new Error('missing input') + link.target_slot = curIndex + node.onConnectionsChange?.( + LiteGraph.INPUT, + curIndex, + true, + link, + curInput + ) + } + const lastInput = groupInputs.at(column - stride) + if (!lastInput) continue + lastInput.link = null + node.onConnectionsChange?.( + LiteGraph.INPUT, + node.inputs.length + column - stride, + false, + null, + lastInput + ) + } + const removalChecks = groupInputs.slice((min - 1) * stride) + let i + for (i = removalChecks.length - stride; i >= 0; i -= stride) { + if (removalChecks.slice(i, i + stride).some((inp) => inp.link)) break + } + const toRemove = removalChecks.slice(i + stride * 2) + remove(node.inputs, (inp) => toRemove.includes(inp)) + for (const input of toRemove) { + const widgetName = input?.widget?.name + if (!widgetName) continue + for (const widget of remove(node.widgets, (w) => w.name === widgetName)) + widget.onRemove?.() + } + node.size[1] = node.computeSize([...node.size])[1] +} + +function withComfyAutogrow(node: LGraphNode): asserts node is AutogrowNode { + if (node.comfyDynamic?.autogrow) return + node.comfyDynamic ??= {} + node.comfyDynamic.autogrow = {} + + let pendingConnection: number | undefined + let swappingConnection = false + + const originalOnConnectInput = node.onConnectInput + node.onConnectInput = function (slot: number, ...args) { + pendingConnection = slot + requestAnimationFrame(() => (pendingConnection = undefined)) + return originalOnConnectInput?.apply(this, [slot, ...args]) ?? true + } + + node.onConnectionsChange = useChainCallback( + node.onConnectionsChange, + function ( + this: AutogrowNode, + contype: ISlotType, + slot: number, + iscon: boolean, + linf: LLink | null | undefined + ) { + const input = this.inputs[slot] + if (contype !== LiteGraph.INPUT || !input) return + //Return if input isn't known autogrow + const key = input.name.slice(0, input.name.lastIndexOf('.')) + const autogrowGroup = this.comfyDynamic.autogrow[key] + if (!autogrowGroup) return + if (app.configuringGraph && input.widget) + ensureWidgetForInput(node, input) + if (iscon && linf) { + if (swappingConnection || !linf) return + autogrowInputConnected(slot, this) + } else { + if (pendingConnection === slot) { + swappingConnection = true + requestAnimationFrame(() => (swappingConnection = false)) + return + } + requestAnimationFrame(() => autogrowInputDisconnected(slot, this)) + } + } + ) +} +function applyAutogrow(node: LGraphNode, inputSpecV2: InputSpecV2) { + withComfyAutogrow(node) + + const parseResult = zAutogrowOptions.safeParse(inputSpecV2) + if (!parseResult.success) throw new Error('invalid Autogrow spec') + const inputSpec = parseResult.data + const { input, min = 1, names, prefix, max = 100 } = inputSpec.template + + const inputTypes: (Record | undefined)[] = [ + input.required, + input.optional + ] + const inputsV2 = inputTypes.flatMap((inputType, index) => + Object.entries(inputType ?? {}).map(([name, v]) => + transformInputSpecV1ToV2(v, { name, isOptional: index === 1 }) + ) + ) + node.comfyDynamic.autogrow[inputSpecV2.name] = { + names, + min, + max: names?.length ?? max, + prefix, + inputSpecs: inputsV2 + } + for (let i = 0; i === 0 || i < min; i++) + addAutogrowGroup(i, inputSpecV2.name, node) +} diff --git a/src/extensions/core/clipspace.ts b/src/extensions/core/clipspace.ts index bc644d8d7..2fe7378de 100644 --- a/src/extensions/core/clipspace.ts +++ b/src/extensions/core/clipspace.ts @@ -47,8 +47,9 @@ export class ClipspaceDialog extends ComfyDialog { if (ClipspaceDialog.instance) { const self = ClipspaceDialog.instance // allow reconstruct controls when copying from non-image to image content. + const imgSettings = self.createImgSettings() const children = $el('div.comfy-modal-content', [ - self.createImgSettings(), + ...(imgSettings ? [imgSettings] : []), ...self.createButtons() ]) @@ -103,7 +104,7 @@ export class ClipspaceDialog extends ComfyDialog { return buttons } - createImgSettings() { + createImgSettings(): HTMLTableElement | null { if (ComfyApp.clipspace?.imgs) { const combo_items = [] const imgs = ComfyApp.clipspace.imgs @@ -167,14 +168,14 @@ export class ClipspaceDialog extends ComfyDialog { return $el('table', {}, [row1, row2, row3]) } else { - return [] + return null } } - createImgPreview() { + createImgPreview(): HTMLImageElement | null { if (ComfyApp.clipspace?.imgs) { return $el('img', { id: 'clipspace_preview', ondragstart: () => false }) - } else return [] + } else return null } override show() { diff --git a/src/extensions/core/cloudFeedbackTopbarButton.ts b/src/extensions/core/cloudFeedbackTopbarButton.ts index 144506d7e..b19f057a7 100644 --- a/src/extensions/core/cloudFeedbackTopbarButton.ts +++ b/src/extensions/core/cloudFeedbackTopbarButton.ts @@ -1,10 +1,17 @@ import { t } from '@/i18n' +import { getDistribution, ZENDESK_FIELDS } from '@/platform/support/config' import { useExtensionService } from '@/services/extensionService' import type { ActionBarButton } from '@/types/comfy' -// Zendesk feedback URL - update this with the actual URL -const ZENDESK_FEEDBACK_URL = - 'https://support.comfy.org/hc/en-us/requests/new?ticket_form_id=43066738713236' +const ZENDESK_BASE_URL = 'https://support.comfy.org/hc/en-us/requests/new' +const ZENDESK_FEEDBACK_FORM_ID = '43066738713236' + +const distribution = getDistribution() +const params = new URLSearchParams({ + ticket_form_id: ZENDESK_FEEDBACK_FORM_ID, + [ZENDESK_FIELDS.DISTRIBUTION]: distribution +}) +const feedbackUrl = `${ZENDESK_BASE_URL}?${params.toString()}` const buttons: ActionBarButton[] = [ { @@ -12,7 +19,7 @@ const buttons: ActionBarButton[] = [ label: t('actionbar.feedback'), tooltip: t('actionbar.feedbackTooltip'), onClick: () => { - window.open(ZENDESK_FEEDBACK_URL, '_blank', 'noopener,noreferrer') + window.open(feedbackUrl, '_blank', 'noopener,noreferrer') } } ] diff --git a/src/extensions/core/cloudRemoteConfig.ts b/src/extensions/core/cloudRemoteConfig.ts index a40ac401b..6d775389e 100644 --- a/src/extensions/core/cloudRemoteConfig.ts +++ b/src/extensions/core/cloudRemoteConfig.ts @@ -1,4 +1,8 @@ -import { loadRemoteConfig } from '@/platform/remoteConfig/remoteConfig' +import { watchDebounced } from '@vueuse/core' + +import { useCurrentUser } from '@/composables/auth/useCurrentUser' +import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription' +import { refreshRemoteConfig } from '@/platform/remoteConfig/refreshRemoteConfig' import { useExtensionService } from '@/services/extensionService' /** @@ -9,7 +13,19 @@ useExtensionService().registerExtension({ name: 'Comfy.Cloud.RemoteConfig', setup: async () => { - // Poll for config updates every 30 seconds - setInterval(() => void loadRemoteConfig(), 30000) + const { isLoggedIn } = useCurrentUser() + const { isActiveSubscription } = useSubscription() + + watchDebounced( + [isLoggedIn, isActiveSubscription], + () => { + if (!isLoggedIn.value) return + void refreshRemoteConfig() + }, + { debounce: 256, immediate: true } + ) + + // Poll for config updates every 10 minutes (with auth) + setInterval(() => void refreshRemoteConfig(), 600_000) } }) diff --git a/tests-ui/tests/extensions/contextMenuExtensionName.test.ts b/src/extensions/core/contextMenuFilter.name.test.ts similarity index 83% rename from tests-ui/tests/extensions/contextMenuExtensionName.test.ts rename to src/extensions/core/contextMenuFilter.name.test.ts index 1fb0b5fd5..2ee4f6a21 100644 --- a/tests-ui/tests/extensions/contextMenuExtensionName.test.ts +++ b/src/extensions/core/contextMenuFilter.name.test.ts @@ -1,6 +1,10 @@ import { describe, expect, it, vi } from 'vitest' import { legacyMenuCompat } from '@/lib/litegraph/src/contextMenuCompat' +import type { + IContextMenuValue, + LGraphNode +} from '@/lib/litegraph/src/litegraph' import { LGraphCanvas } from '@/lib/litegraph/src/litegraph' /** @@ -18,11 +22,12 @@ describe('Context Menu Extension Name in Warnings', () => { // Extension monkey-patches the method const original = LGraphCanvas.prototype.getCanvasMenuOptions - LGraphCanvas.prototype.getCanvasMenuOptions = function (...args: any[]) { - const items = (original as any).apply(this, args) - items.push({ content: 'My Custom Menu Item', callback: () => {} }) - return items - } + LGraphCanvas.prototype.getCanvasMenuOptions = + function (): (IContextMenuValue | null)[] { + const items = original.call(this) + items.push({ content: 'My Custom Menu Item', callback: () => {} }) + return items + } // Clear extension (happens after setup completes) legacyMenuCompat.setCurrentExtension(null) @@ -49,8 +54,8 @@ describe('Context Menu Extension Name in Warnings', () => { // Extension monkey-patches the method const original = LGraphCanvas.prototype.getNodeMenuOptions - LGraphCanvas.prototype.getNodeMenuOptions = function (...args: any[]) { - const items = (original as any).apply(this, args) + LGraphCanvas.prototype.getNodeMenuOptions = function (node: LGraphNode) { + const items = original.call(this, node) items.push({ content: 'My Node Menu Item', callback: () => {} }) return items } diff --git a/tests-ui/tests/extensions/contextMenuExtension.test.ts b/src/extensions/core/contextMenuFilter.test.ts similarity index 92% rename from tests-ui/tests/extensions/contextMenuExtension.test.ts rename to src/extensions/core/contextMenuFilter.test.ts index 5cd9d3664..72a2d7604 100644 --- a/tests-ui/tests/extensions/contextMenuExtension.test.ts +++ b/src/extensions/core/contextMenuFilter.test.ts @@ -7,6 +7,10 @@ import type { LGraphCanvas, LGraphNode } from '@/lib/litegraph/src/litegraph' import { useExtensionService } from '@/services/extensionService' import { useExtensionStore } from '@/stores/extensionStore' import type { ComfyExtension } from '@/types/comfy' +import { + createMockCanvas, + createMockLGraphNode +} from '@/utils/__tests__/litegraphTestUtils' describe('Context Menu Extension API', () => { let mockCanvas: LGraphCanvas @@ -35,7 +39,7 @@ describe('Context Menu Extension API', () => { // Mock extensions const createCanvasMenuExtension = ( name: string, - items: IContextMenuValue[] + items: (IContextMenuValue | null)[] ): ComfyExtension => ({ name, getCanvasMenuItems: () => items @@ -54,16 +58,16 @@ describe('Context Menu Extension API', () => { extensionStore = useExtensionStore() extensionService = useExtensionService() - mockCanvas = { + mockCanvas = createMockCanvas({ graph_mouse: [100, 100], selectedItems: new Set() - } as unknown as LGraphCanvas + }) - mockNode = { + mockNode = createMockLGraphNode({ id: 1, type: 'TestNode', pos: [0, 0] - } as unknown as LGraphNode + }) }) describe('collectCanvasMenuItems', () => { @@ -79,7 +83,7 @@ describe('Context Menu Extension API', () => { const items: IContextMenuValue[] = extensionService .invokeExtensions('getCanvasMenuItems', mockCanvas) - .flat() + .flat() as IContextMenuValue[] expect(items).toHaveLength(3) expect(items[0]).toMatchObject({ content: 'Canvas Item 1' }) @@ -99,7 +103,7 @@ describe('Context Menu Extension API', () => { ] } }, - null as unknown as IContextMenuValue, + null, { content: 'After Separator', callback: () => {} } ]) @@ -107,7 +111,7 @@ describe('Context Menu Extension API', () => { const items: IContextMenuValue[] = extensionService .invokeExtensions('getCanvasMenuItems', mockCanvas) - .flat() + .flat() as IContextMenuValue[] expect(items).toHaveLength(3) expect(items[0].content).toBe('Menu with Submenu') @@ -129,7 +133,7 @@ describe('Context Menu Extension API', () => { const items: IContextMenuValue[] = extensionService .invokeExtensions('getCanvasMenuItems', mockCanvas) - .flat() + .flat() as IContextMenuValue[] expect(items).toHaveLength(1) expect(items[0].content).toBe('Canvas Item 1') @@ -146,11 +150,11 @@ describe('Context Menu Extension API', () => { // Collect items multiple times (simulating repeated menu opens) const items1: IContextMenuValue[] = extensionService .invokeExtensions('getCanvasMenuItems', mockCanvas) - .flat() + .flat() as IContextMenuValue[] const items2: IContextMenuValue[] = extensionService .invokeExtensions('getCanvasMenuItems', mockCanvas) - .flat() + .flat() as IContextMenuValue[] // Both collections should have the same items (no duplication) expect(items1).toHaveLength(2) @@ -180,7 +184,7 @@ describe('Context Menu Extension API', () => { const items: IContextMenuValue[] = extensionService .invokeExtensions('getNodeMenuItems', mockNode) - .flat() + .flat() as IContextMenuValue[] expect(items).toHaveLength(3) expect(items[0]).toMatchObject({ content: 'Node Item 1' }) @@ -205,7 +209,7 @@ describe('Context Menu Extension API', () => { const items: IContextMenuValue[] = extensionService .invokeExtensions('getNodeMenuItems', mockNode) - .flat() + .flat() as IContextMenuValue[] expect(items[0].content).toBe('Node Menu with Submenu') expect(items[0].submenu?.options).toHaveLength(2) @@ -222,7 +226,7 @@ describe('Context Menu Extension API', () => { const items: IContextMenuValue[] = extensionService .invokeExtensions('getNodeMenuItems', mockNode) - .flat() + .flat() as IContextMenuValue[] expect(items).toHaveLength(1) expect(items[0].content).toBe('Node Item 1') diff --git a/src/extensions/core/customCombo.ts b/src/extensions/core/customCombo.ts new file mode 100644 index 000000000..58d41eb62 --- /dev/null +++ b/src/extensions/core/customCombo.ts @@ -0,0 +1,126 @@ +import { shallowReactive } from 'vue' + +import { useChainCallback } from '@/composables/functional/useChainCallback' +import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events' +import { LGraphNode } from '@/lib/litegraph/src/LGraphNode' +import { LLink } from '@/lib/litegraph/src/litegraph' +import { app } from '@/scripts/app' + +function applyToGraph(this: LGraphNode, extraLinks: LLink[] = []) { + if (!this.outputs[0].links?.length || !this.graph) return + + const links = [ + ...this.outputs[0].links.map((l) => this.graph!.links[l]), + ...extraLinks + ] + let v = this.widgets?.[0].value + // For each output link copy our value over the original widget value + for (const linkInfo of links) { + const node = this.graph?.getNodeById(linkInfo.target_id) + const input = node?.inputs[linkInfo.target_slot] + if (!input) { + console.warn('Unable to resolve node or input for link', linkInfo) + continue + } + + const widgetName = input.widget?.name + if (!widgetName) { + console.warn('Invalid widget or widget name', input.widget) + continue + } + + const widget = node.widgets?.find((w) => w.name === widgetName) + if (!widget) { + console.warn(`Unable to find widget "${widgetName}" on node [${node.id}]`) + continue + } + + widget.value = v + widget.callback?.( + widget.value, + app.canvas, + node, + app.canvas.graph_mouse, + {} as CanvasPointerEvent + ) + } +} + +function onNodeCreated(this: LGraphNode) { + this.applyToGraph = useChainCallback(this.applyToGraph, applyToGraph) + + const comboWidget = this.widgets![0] + const values = shallowReactive([]) + comboWidget.options.values = values + + const updateCombo = () => { + values.splice( + 0, + values.length, + ...this.widgets!.filter( + (w) => w.name.startsWith('option') && w.value + ).map((w) => `${w.value}`) + ) + if (app.configuringGraph) return + if (values.includes(`${comboWidget.value}`)) return + comboWidget.value = values[0] ?? '' + comboWidget.callback?.(comboWidget.value) + } + comboWidget.callback = useChainCallback(comboWidget.callback, () => + this.applyToGraph!() + ) + + function addOption(node: LGraphNode) { + if (!node.widgets) return + const newCount = node.widgets.length - 1 + node.addWidget('string', `option${newCount}`, '', () => {}) + const widget = node.widgets.at(-1) + if (!widget) return + + let value = '' + Object.defineProperty(widget, 'value', { + get() { + return value + }, + set(v) { + value = v + updateCombo() + if (!node.widgets) return + const lastWidget = node.widgets.at(-1) + if (lastWidget === this) { + if (v) addOption(node) + return + } + if (v || node.widgets.at(-2) !== this || lastWidget?.value) return + node.widgets.pop() + node.computeSize(node.size) + this.callback(v) + } + }) + } + const widgets = this.widgets! + widgets.push({ + name: 'index', + type: 'hidden', + get value() { + return widgets.slice(2).findIndex((w) => w.value === comboWidget.value) + }, + set value(_) {}, + draw: () => undefined, + computeSize: () => [0, -4], + options: { hidden: true }, + y: 0 + }) + addOption(this) +} + +app.registerExtension({ + name: 'Comfy.CustomCombo', + beforeRegisterNodeDef(nodeType, nodeData) { + if (nodeData?.name !== 'CustomCombo') return + nodeType.prototype.onNodeCreated = useChainCallback( + nodeType.prototype.onNodeCreated, + onNodeCreated + ) + } +}) diff --git a/tests-ui/tests/dynamicPrompts.test.ts b/src/extensions/core/dynamicPrompts.test.ts similarity index 100% rename from tests-ui/tests/dynamicPrompts.test.ts rename to src/extensions/core/dynamicPrompts.test.ts diff --git a/src/extensions/core/groupNode.ts b/src/extensions/core/groupNode.ts index 5b4146c2d..db543c30a 100644 --- a/src/extensions/core/groupNode.ts +++ b/src/extensions/core/groupNode.ts @@ -1,21 +1,23 @@ import { PREFIX, SEPARATOR } from '@/constants/groupNodeConstants' import { t } from '@/i18n' -import { type NodeId } from '@/lib/litegraph/src/LGraphNode' +import type { GroupNodeWorkflowData } from '@/lib/litegraph/src/LGraph' +import type { SerialisedLLinkArray } from '@/lib/litegraph/src/LLink' +import type { NodeId } from '@/lib/litegraph/src/LGraphNode' import type { IContextMenuValue } from '@/lib/litegraph/src/interfaces' import { type ExecutableLGraphNode, type ExecutionId, LGraphNode, + type LGraphNodeConstructor, LiteGraph, SubgraphNode } from '@/lib/litegraph/src/litegraph' import { useToastStore } from '@/platform/updates/common/toastStore' import { - type ComfyLink, type ComfyNode, type ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema' -import type { ComfyNodeDef } from '@/schemas/nodeDefSchema' +import type { ComfyNodeDef, InputSpec } from '@/schemas/nodeDefSchema' import { useDialogService } from '@/services/dialogService' import { useExecutionStore } from '@/stores/executionStore' import { useNodeDefStore } from '@/stores/nodeDefStore' @@ -30,10 +32,55 @@ import { app } from '../../scripts/app' import { ManageGroupDialog } from './groupNodeManage' import { mergeIfValid } from './widgetInputs' -type GroupNodeWorkflowData = { - external: ComfyLink[] - links: ComfyLink[] - nodes: ComfyNode[] +type GroupNodeLink = SerialisedLLinkArray +type LinksFromMap = Record> +type LinksToMap = Record> +type ExternalFromMap = Record> + +interface GroupNodeInput { + name?: string + type?: string + label?: string + widget?: { name: string } +} + +interface GroupNodeOutput { + name?: string + type?: string + label?: string + widget?: { name: string } + links?: number[] +} + +interface GroupNodeData extends Omit< + GroupNodeWorkflowData['nodes'][number], + 'inputs' | 'outputs' +> { + title?: string + widgets_values?: unknown[] + inputs?: GroupNodeInput[] + outputs?: GroupNodeOutput[] +} + +interface GroupNodeDef { + input: { + required: Record + optional?: Record + } + output: unknown[] + output_name: string[] + output_is_list: boolean[] +} + +interface NodeConfigEntry { + input?: Record + output?: Record +} + +interface SerializedGroupConfig { + nodes: unknown[] + links: GroupNodeLink[] + external?: (number | string)[][] } const Workflow = { @@ -42,13 +89,11 @@ const Workflow = { Registered: 1, InWorkflow: 2 }, - // @ts-expect-error fixme ts strict error - isInUseGroupNode(name) { + isInUseGroupNode(name: string) { const id = `${PREFIX}${SEPARATOR}${name}` // Check if lready registered/in use in this workflow - // @ts-expect-error fixme ts strict error - if (app.graph.extra?.groupNodes?.[name]) { - if (app.graph.nodes.find((n) => n.type === id)) { + if (app.rootGraph.extra?.groupNodes?.[name]) { + if (app.rootGraph.nodes.find((n) => n.type === id)) { return Workflow.InUse.InWorkflow } else { return Workflow.InUse.Registered @@ -57,19 +102,17 @@ const Workflow = { return Workflow.InUse.Free }, storeGroupNode(name: string, data: GroupNodeWorkflowData) { - let extra = app.graph.extra - if (!extra) app.graph.extra = extra = {} + let extra = app.rootGraph.extra + if (!extra) app.rootGraph.extra = extra = {} let groupNodes = extra.groupNodes if (!groupNodes) extra.groupNodes = groupNodes = {} - // @ts-expect-error fixme ts strict error groupNodes[name] = data } } class GroupNodeBuilder { nodes: LGraphNode[] - // @ts-expect-error fixme ts strict error - nodeData: GroupNodeWorkflowData + nodeData!: GroupNodeWorkflowData constructor(nodes: LGraphNode[]) { this.nodes = nodes @@ -118,28 +161,28 @@ class GroupNodeBuilder { sortNodes() { // Gets the builders nodes in graph execution order - const nodesInOrder = app.graph.computeExecutionOrder(false) + const nodesInOrder = app.rootGraph.computeExecutionOrder(false) this.nodes = this.nodes .map((node) => ({ index: nodesInOrder.indexOf(node), node })) - // @ts-expect-error id might be string - .sort((a, b) => a.index - b.index || a.node.id - b.node.id) + .sort( + (a, b) => + a.index - b.index || + String(a.node.id).localeCompare(String(b.node.id)) + ) .map(({ node }) => node) } - getNodeData() { - // @ts-expect-error fixme ts strict error - const storeLinkTypes = (config) => { + getNodeData(): GroupNodeWorkflowData { + const storeLinkTypes = (config: SerializedGroupConfig) => { // Store link types for dynamically typed nodes e.g. reroutes for (const link of config.links) { - const origin = app.graph.getNodeById(link[4]) - // @ts-expect-error fixme ts strict error - const type = origin.outputs[link[1]].type - link.push(type) + const origin = app.rootGraph.getNodeById(link[4] as NodeId) + const type = origin?.outputs?.[Number(link[1])]?.type + if (type !== undefined) link.push(type) } } - // @ts-expect-error fixme ts strict error - const storeExternalLinks = (config) => { + const storeExternalLinks = (config: SerializedGroupConfig) => { // Store any external links to the group in the config so when rebuilding we add extra slots config.external = [] for (let i = 0; i < this.nodes.length; i++) { @@ -151,7 +194,7 @@ class GroupNodeBuilder { let type = output.type if (!output.links?.length) continue for (const l of output.links) { - const link = app.graph.links[l] + const link = app.rootGraph.links[l] if (!link) continue if (type === '*') type = link.type @@ -161,54 +204,50 @@ class GroupNodeBuilder { } } if (hasExternal) { - config.external.push([i, slot, type]) + config.external.push([i, slot, String(type)]) } } } } // Use the built in copyToClipboard function to generate the node data we need - try { - // @ts-expect-error fixme ts strict error - const serialised = serialise(this.nodes, app.canvas?.graph) - const config = JSON.parse(serialised) + const graph = app.canvas?.graph + if (!graph) return { nodes: [], links: [], external: [] } + const serialised = serialise(this.nodes, graph) + const config = JSON.parse(serialised) as SerializedGroupConfig + config.external = [] - storeLinkTypes(config) - storeExternalLinks(config) + storeLinkTypes(config) + storeExternalLinks(config) - return config - } finally { - } + return config as GroupNodeWorkflowData } } export class GroupNodeConfig { name: string - nodeData: any + nodeData: GroupNodeWorkflowData inputCount: number - oldToNewOutputMap: {} - newToOldOutputMap: {} - oldToNewInputMap: {} - oldToNewWidgetMap: {} - newToOldWidgetMap: {} - primitiveDefs: {} - widgetToPrimitive: {} - primitiveToWidget: {} - nodeInputs: {} - outputVisibility: any[] - // @ts-expect-error fixme ts strict error - nodeDef: ComfyNodeDef - // @ts-expect-error fixme ts strict error - inputs: any[] - // @ts-expect-error fixme ts strict error - linksFrom: {} - // @ts-expect-error fixme ts strict error - linksTo: {} - // @ts-expect-error fixme ts strict error - externalFrom: {} + oldToNewOutputMap: Record> + newToOldOutputMap: Record + oldToNewInputMap: Record> + oldToNewWidgetMap: Record> + newToOldWidgetMap: Record + primitiveDefs: Record + widgetToPrimitive: Record> + primitiveToWidget: Record< + number, + { nodeId: number | string | null; inputName: string }[] + > + nodeInputs: Record> + outputVisibility: boolean[] + nodeDef: (ComfyNodeDef & { [GROUP]: GroupNodeConfig }) | undefined + inputs!: unknown[] + linksFrom!: LinksFromMap + linksTo!: LinksToMap + externalFrom!: ExternalFromMap - // @ts-expect-error fixme ts strict error - constructor(name, nodeData) { + constructor(name: string, nodeData: GroupNodeWorkflowData) { this.name = name this.nodeData = nodeData this.getLinks() @@ -231,14 +270,12 @@ export class GroupNodeConfig { output: [], output_name: [], output_is_list: [], - // @ts-expect-error Unused, doesn't exist - output_is_hidden: [], + output_node: false, // This is a lie (to satisfy the interface) name: source + SEPARATOR + this.name, display_name: this.name, category: 'group nodes' + (SEPARATOR + source), input: { required: {} }, description: `Group node combining ${this.nodeData.nodes - // @ts-expect-error fixme ts strict error .map((n) => n.type) .join(', ')}`, python_module: 'custom_nodes.' + this.name, @@ -250,17 +287,16 @@ export class GroupNodeConfig { const seenInputs = {} const seenOutputs = {} for (let i = 0; i < this.nodeData.nodes.length; i++) { - const node = this.nodeData.nodes[i] + const node = this.nodeData.nodes[i] as GroupNodeData node.index = i this.processNode(node, seenInputs, seenOutputs) } for (const p of this.#convertedToProcess) { - // @ts-expect-error fixme ts strict error p() } - // @ts-expect-error fixme ts strict error - this.#convertedToProcess = null + this.#convertedToProcess = [] + if (!this.nodeDef) return await app.registerNodeDef(`${PREFIX}${SEPARATOR}` + this.name, this.nodeDef) useNodeDefStore().addNodeDef(this.nodeDef) } @@ -271,50 +307,57 @@ export class GroupNodeConfig { this.externalFrom = {} // Extract links for easy lookup - for (const l of this.nodeData.links) { - const [sourceNodeId, sourceNodeSlot, targetNodeId, targetNodeSlot] = l + for (const link of this.nodeData.links) { + const [sourceNodeId, sourceNodeSlot, targetNodeId, targetNodeSlot] = link // Skip links outside the copy config - if (sourceNodeId == null) continue + if ( + sourceNodeId == null || + sourceNodeSlot == null || + targetNodeId == null || + targetNodeSlot == null + ) + continue - // @ts-expect-error fixme ts strict error - if (!this.linksFrom[sourceNodeId]) { - // @ts-expect-error fixme ts strict error - this.linksFrom[sourceNodeId] = {} - } - // @ts-expect-error fixme ts strict error - if (!this.linksFrom[sourceNodeId][sourceNodeSlot]) { - // @ts-expect-error fixme ts strict error - this.linksFrom[sourceNodeId][sourceNodeSlot] = [] - } - // @ts-expect-error fixme ts strict error - this.linksFrom[sourceNodeId][sourceNodeSlot].push(l) + const srcId = Number(sourceNodeId) + const srcSlot = Number(sourceNodeSlot) + const tgtId = Number(targetNodeId) + const tgtSlot = Number(targetNodeSlot) - // @ts-expect-error fixme ts strict error - if (!this.linksTo[targetNodeId]) { - // @ts-expect-error fixme ts strict error - this.linksTo[targetNodeId] = {} + if (!this.linksFrom[srcId]) { + this.linksFrom[srcId] = {} } - // @ts-expect-error fixme ts strict error - this.linksTo[targetNodeId][targetNodeSlot] = l + if (!this.linksFrom[srcId][srcSlot]) { + this.linksFrom[srcId][srcSlot] = [] + } + this.linksFrom[srcId][srcSlot].push(link) + + if (!this.linksTo[tgtId]) { + this.linksTo[tgtId] = {} + } + this.linksTo[tgtId][tgtSlot] = link } if (this.nodeData.external) { for (const ext of this.nodeData.external) { - // @ts-expect-error fixme ts strict error - if (!this.externalFrom[ext[0]]) { - // @ts-expect-error fixme ts strict error - this.externalFrom[ext[0]] = { [ext[1]]: ext[2] } + const nodeIdx = Number(ext[0]) + const slotIdx = Number(ext[1]) + const typeVal = ext[2] + if (typeVal == null) continue + if (!this.externalFrom[nodeIdx]) { + this.externalFrom[nodeIdx] = { [slotIdx]: typeVal } } else { - // @ts-expect-error fixme ts strict error - this.externalFrom[ext[0]][ext[1]] = ext[2] + this.externalFrom[nodeIdx][slotIdx] = typeVal } } } } - // @ts-expect-error fixme ts strict error - processNode(node, seenInputs, seenOutputs) { + processNode( + node: GroupNodeData, + seenInputs: Record, + seenOutputs: Record + ) { const def = this.getNodeDef(node) if (!def) return @@ -324,32 +367,45 @@ export class GroupNodeConfig { if (def.output?.length) this.processNodeOutputs(node, seenOutputs, def) } - // @ts-expect-error fixme ts strict error - getNodeDef(node) { - // @ts-expect-error fixme ts strict error - const def = globalDefs[node.type] - if (def) return def + getNodeDef( + node: GroupNodeData | GroupNodeWorkflowData['nodes'][number] + ): GroupNodeDef | ComfyNodeDef | null | undefined { + if (node.type) { + const def = globalDefs[node.type] + if (def) return def + } - // @ts-expect-error fixme ts strict error - const linksFrom = this.linksFrom[node.index] + const nodeIndex = node.index + if (nodeIndex == null) return undefined + + const linksFrom = this.linksFrom[nodeIndex] if (node.type === 'PrimitiveNode') { // Skip as its not linked if (!linksFrom) return - let type = linksFrom['0'][0][5] + let type: string | number | null = linksFrom[0]?.[0]?.[5] ?? null if (type === 'COMBO') { // Use the array items - const source = node.outputs[0].widget.name - const fromTypeName = this.nodeData.nodes[linksFrom['0'][0][2]].type - // @ts-expect-error fixme ts strict error - const fromType = globalDefs[fromTypeName] - const input = - fromType.input.required[source] ?? fromType.input.optional[source] - type = input[0] + const output = node.outputs?.[0] as GroupNodeOutput | undefined + const source = output?.widget?.name + const nodeIdx = linksFrom[0]?.[0]?.[2] + if (source && nodeIdx != null) { + const fromTypeName = this.nodeData.nodes[Number(nodeIdx)]?.type + if (fromTypeName) { + const fromType = globalDefs[fromTypeName] + const input = + fromType?.input?.required?.[source] ?? + fromType?.input?.optional?.[source] + const inputType = input?.[0] + type = + typeof inputType === 'string' || typeof inputType === 'number' + ? inputType + : null + } + } } - // @ts-expect-error fixme ts strict error - const def = (this.primitiveDefs[node.index] = { + const def = (this.primitiveDefs[nodeIndex] = { input: { required: { value: [type, {}] @@ -361,66 +417,85 @@ export class GroupNodeConfig { }) return def } else if (node.type === 'Reroute') { - // @ts-expect-error fixme ts strict error - const linksTo = this.linksTo[node.index] - // @ts-expect-error fixme ts strict error - if (linksTo && linksFrom && !this.externalFrom[node.index]?.[0]) { + const linksTo = this.linksTo[nodeIndex] + if (linksTo && linksFrom && !this.externalFrom[nodeIndex]?.[0]) { // Being used internally return null } - let config = {} + let config: Record = {} let rerouteType = '*' if (linksFrom) { - for (const [, , id, slot] of linksFrom['0']) { - const node = this.nodeData.nodes[id] - const input = node.inputs[slot] - if (rerouteType === '*') { + const links = linksFrom[0] ?? [] + for (const link of links) { + const id = link[2] + const slot = link[3] + if (id == null || slot == null) continue + const targetNode = this.nodeData.nodes[Number(id)] + const input = targetNode?.inputs?.[Number(slot)] as + | GroupNodeInput + | undefined + if (input?.type && rerouteType === '*') { rerouteType = input.type } - if (input.widget) { - // @ts-expect-error fixme ts strict error - const targetDef = globalDefs[node.type] + if (input?.widget && targetNode?.type) { + const targetDef = globalDefs[targetNode.type] const targetWidget = - targetDef.input.required[input.widget.name] ?? - targetDef.input.optional[input.widget.name] + targetDef?.input?.required?.[input.widget.name] ?? + targetDef?.input?.optional?.[input.widget.name] - const widget = [targetWidget[0], config] - const res = mergeIfValid( - { - // @ts-expect-error fixme ts strict error - widget - }, - targetWidget, - false, - null, - widget - ) - config = res?.customConfig ?? config + if (targetWidget) { + const widgetSpec = [targetWidget[0], config] as Parameters< + typeof mergeIfValid + >[4] + const res = mergeIfValid( + { widget: widgetSpec } as unknown as Parameters< + typeof mergeIfValid + >[0], + targetWidget, + false, + undefined, + widgetSpec + ) + config = (res?.customConfig as Record) ?? config + } } } } else if (linksTo) { - const [id, slot] = linksTo['0'] - rerouteType = this.nodeData.nodes[id].outputs[slot].type + const link = linksTo[0] + if (link) { + const id = link[0] + const slot = link[1] + if (id != null && slot != null) { + const outputType = + this.nodeData.nodes[Number(id)]?.outputs?.[Number(slot)] + if ( + outputType && + typeof outputType === 'object' && + 'type' in outputType + ) { + rerouteType = String((outputType as GroupNodeOutput).type ?? '*') + } + } + } } else { // Reroute used as a pipe for (const l of this.nodeData.links) { if (l[2] === node.index) { - rerouteType = l[5] + const linkType = l[5] + if (linkType != null) rerouteType = String(linkType) break } } if (rerouteType === '*') { // Check for an external link - // @ts-expect-error fixme ts strict error - const t = this.externalFrom[node.index]?.[0] + const t = this.externalFrom[nodeIndex]?.[0] if (t) { - rerouteType = t + rerouteType = String(t) } } } - // @ts-expect-error config.forceInput = true return { input: { @@ -442,12 +517,19 @@ export class GroupNodeConfig { ) } - // @ts-expect-error fixme ts strict error - getInputConfig(node, inputName, seenInputs, config, extra?) { - const customConfig = this.nodeData.config?.[node.index]?.input?.[inputName] + getInputConfig( + node: GroupNodeData, + inputName: string, + seenInputs: Record, + config: unknown[], + extra?: Record + ) { + const nodeConfig = this.nodeData.config?.[node.index ?? -1] as + | NodeConfigEntry + | undefined + const customConfig = nodeConfig?.input?.[inputName] let name = customConfig?.name ?? - // @ts-expect-error fixme ts strict error node.inputs?.find((inp) => inp.name === inputName)?.label ?? inputName let key = name @@ -468,36 +550,55 @@ export class GroupNodeConfig { } if (config[0] === 'IMAGEUPLOAD') { if (!extra) extra = {} - extra.widget = - // @ts-expect-error fixme ts strict error - this.oldToNewWidgetMap[node.index]?.[config[1]?.widget ?? 'image'] ?? - 'image' + const nodeIndex = node.index ?? -1 + const configOptions = + typeof config[1] === 'object' && config[1] !== null ? config[1] : {} + const widgetKey = + 'widget' in configOptions && typeof configOptions.widget === 'string' + ? configOptions.widget + : 'image' + extra.widget = this.oldToNewWidgetMap[nodeIndex]?.[widgetKey] ?? 'image' } if (extra) { - config = [config[0], { ...config[1], ...extra }] + const configObj = + typeof config[1] === 'object' && config[1] ? config[1] : {} + config = [config[0], { ...configObj, ...extra }] } return { name, config, customConfig } } - // @ts-expect-error fixme ts strict error - processWidgetInputs(inputs, node, inputNames, seenInputs) { - const slots = [] - const converted = new Map() - // @ts-expect-error fixme ts strict error - const widgetMap = (this.oldToNewWidgetMap[node.index] = {}) + processWidgetInputs( + inputs: Record, + node: GroupNodeData, + inputNames: string[], + seenInputs: Record + ) { + const slots: string[] = [] + const converted = new Map() + const nodeIndex = node.index ?? -1 + const widgetMap: Record = (this.oldToNewWidgetMap[ + nodeIndex + ] = {}) for (const inputName of inputNames) { - if (useWidgetStore().inputIsWidget(inputs[inputName])) { - const convertedIndex = node.inputs?.findIndex( - // @ts-expect-error fixme ts strict error - (inp) => inp.name === inputName && inp.widget?.name === inputName - ) + const inputSpec = inputs[inputName] + const isValidSpec = + Array.isArray(inputSpec) && + inputSpec.length >= 1 && + typeof inputSpec[0] === 'string' + if ( + isValidSpec && + useWidgetStore().inputIsWidget(inputSpec as InputSpec) + ) { + const convertedIndex = + node.inputs?.findIndex( + (inp) => inp.name === inputName && inp.widget?.name === inputName + ) ?? -1 if (convertedIndex > -1) { // This widget has been converted to a widget // We need to store this in the correct position so link ids line up converted.set(convertedIndex, inputName) - // @ts-expect-error fixme ts strict error widgetMap[inputName] = null } else { // Normal widget @@ -505,13 +606,13 @@ export class GroupNodeConfig { node, inputName, seenInputs, - inputs[inputName] + inputs[inputName] as unknown[] ) - // @ts-expect-error fixme ts strict error - this.nodeDef.input.required[name] = config - // @ts-expect-error fixme ts strict error + if (this.nodeDef?.input?.required) { + // @ts-expect-error legacy dynamic input assignment + this.nodeDef.input.required[name] = config + } widgetMap[inputName] = name - // @ts-expect-error fixme ts strict error this.newToOldWidgetMap[name] = { node, inputName } } } else { @@ -522,61 +623,78 @@ export class GroupNodeConfig { return { converted, slots } } - // @ts-expect-error fixme ts strict error - checkPrimitiveConnection(link, inputName, inputs) { - const sourceNode = this.nodeData.nodes[link[0]] - if (sourceNode.type === 'PrimitiveNode') { + checkPrimitiveConnection( + link: GroupNodeLink, + inputName: string, + inputs: Record + ) { + const linkSourceIdx = link[0] + if (linkSourceIdx == null) return + const sourceNode = this.nodeData.nodes[Number(linkSourceIdx)] + if (sourceNode?.type === 'PrimitiveNode') { // Merge link configurations - const [sourceNodeId, _, targetNodeId, __] = link - // @ts-expect-error fixme ts strict error + const sourceNodeId = Number(link[0]) + const targetNodeId = Number(link[2]) const primitiveDef = this.primitiveDefs[sourceNodeId] + if (!primitiveDef) return const targetWidget = inputs[inputName] - const primitiveConfig = primitiveDef.input.required.value + const primitiveConfig = primitiveDef.input.required.value as [ + unknown, + Record + ] const output = { widget: primitiveConfig } const config = mergeIfValid( - // @ts-expect-error invalid slot type + // @ts-expect-error slot type mismatch - legacy API output, targetWidget, false, - null, + undefined, primitiveConfig ) + const inputConfig = inputs[inputName]?.[1] primitiveConfig[1] = - (config?.customConfig ?? inputs[inputName][1]) - ? { ...inputs[inputName][1] } + (config?.customConfig ?? inputConfig) + ? { ...(typeof inputConfig === 'object' ? inputConfig : {}) } : {} - // @ts-expect-error fixme ts strict error - let name = this.oldToNewWidgetMap[sourceNodeId]['value'] - name = name.substr(0, name.length - 6) - primitiveConfig[1].control_after_generate = true - primitiveConfig[1].control_prefix = name + const widgetName = this.oldToNewWidgetMap[sourceNodeId]?.['value'] + if (widgetName) { + const name = widgetName.substring(0, widgetName.length - 6) + primitiveConfig[1].control_after_generate = true + primitiveConfig[1].control_prefix = name + } - // @ts-expect-error fixme ts strict error let toPrimitive = this.widgetToPrimitive[targetNodeId] if (!toPrimitive) { - // @ts-expect-error fixme ts strict error toPrimitive = this.widgetToPrimitive[targetNodeId] = {} } - if (toPrimitive[inputName]) { - toPrimitive[inputName].push(sourceNodeId) + const existing = toPrimitive[inputName] + if (Array.isArray(existing)) { + existing.push(sourceNodeId) + } else if (typeof existing === 'number') { + toPrimitive[inputName] = [existing, sourceNodeId] + } else { + toPrimitive[inputName] = sourceNodeId } - toPrimitive[inputName] = sourceNodeId - // @ts-expect-error fixme ts strict error let toWidget = this.primitiveToWidget[sourceNodeId] if (!toWidget) { - // @ts-expect-error fixme ts strict error toWidget = this.primitiveToWidget[sourceNodeId] = [] } toWidget.push({ nodeId: targetNodeId, inputName }) } } - // @ts-expect-error fixme ts strict error - processInputSlots(inputs, node, slots, linksTo, inputMap, seenInputs) { - // @ts-expect-error fixme ts strict error - this.nodeInputs[node.index] = {} + processInputSlots( + inputs: Record, + node: GroupNodeData, + slots: string[], + linksTo: Record, + inputMap: Record, + seenInputs: Record + ) { + const nodeIdx = node.index ?? -1 + this.nodeInputs[nodeIdx] = {} for (let i = 0; i < slots.length; i++) { const inputName = slots[i] if (linksTo[i]) { @@ -592,31 +710,25 @@ export class GroupNodeConfig { inputs[inputName] ) - // @ts-expect-error fixme ts strict error - this.nodeInputs[node.index][inputName] = name + this.nodeInputs[nodeIdx][inputName] = name if (customConfig?.visible === false) continue - // @ts-expect-error fixme ts strict error - this.nodeDef.input.required[name] = config + if (this.nodeDef?.input?.required) { + // @ts-expect-error legacy dynamic input assignment + this.nodeDef.input.required[name] = config + } inputMap[i] = this.inputCount++ } } processConvertedWidgets( - // @ts-expect-error fixme ts strict error - inputs, - // @ts-expect-error fixme ts strict error - node, - // @ts-expect-error fixme ts strict error - slots, - // @ts-expect-error fixme ts strict error - converted, - // @ts-expect-error fixme ts strict error - linksTo, - // @ts-expect-error fixme ts strict error - inputMap, - // @ts-expect-error fixme ts strict error - seenInputs + inputs: Record, + node: GroupNodeData, + slots: string[], + converted: Map, + linksTo: Record, + inputMap: Record, + seenInputs: Record ) { // Add converted widgets sorted into their index order (ordered as they were converted) so link ids match up const convertedSlots = [...converted.keys()] @@ -624,11 +736,12 @@ export class GroupNodeConfig { .map((k) => converted.get(k)) for (let i = 0; i < convertedSlots.length; i++) { const inputName = convertedSlots[i] + if (!inputName) continue if (linksTo[slots.length + i]) { this.checkPrimitiveConnection( linksTo[slots.length + i], inputName, - inputs + inputs as Record ) // This input is linked so we can skip it continue @@ -638,34 +751,35 @@ export class GroupNodeConfig { node, inputName, seenInputs, - inputs[inputName], + inputs[inputName] as unknown[], { defaultInput: true } ) - // @ts-expect-error fixme ts strict error - this.nodeDef.input.required[name] = config - // @ts-expect-error fixme ts strict error + if (this.nodeDef?.input?.required) { + // @ts-expect-error legacy dynamic input assignment + this.nodeDef.input.required[name] = config + } this.newToOldWidgetMap[name] = { node, inputName } - // @ts-expect-error fixme ts strict error - if (!this.oldToNewWidgetMap[node.index]) { - // @ts-expect-error fixme ts strict error - this.oldToNewWidgetMap[node.index] = {} + const nodeIndex = node.index ?? -1 + if (!this.oldToNewWidgetMap[nodeIndex]) { + this.oldToNewWidgetMap[nodeIndex] = {} } - // @ts-expect-error fixme ts strict error - this.oldToNewWidgetMap[node.index][inputName] = name + this.oldToNewWidgetMap[nodeIndex][inputName] = name inputMap[slots.length + i] = this.inputCount++ } } - #convertedToProcess = [] - // @ts-expect-error fixme ts strict error - processNodeInputs(node, seenInputs, inputs) { - // @ts-expect-error fixme ts strict error - const inputMapping = [] + #convertedToProcess: (() => void)[] = [] + processNodeInputs( + node: GroupNodeData, + seenInputs: Record, + inputs: Record + ) { + const inputMapping: unknown[] = [] const inputNames = Object.keys(inputs) if (!inputNames.length) return @@ -676,14 +790,20 @@ export class GroupNodeConfig { inputNames, seenInputs ) - // @ts-expect-error fixme ts strict error - const linksTo = this.linksTo[node.index] ?? {} - // @ts-expect-error fixme ts strict error - const inputMap = (this.oldToNewInputMap[node.index] = {}) - this.processInputSlots(inputs, node, slots, linksTo, inputMap, seenInputs) + const nodeIndex = node.index ?? -1 + const linksTo = this.linksTo[nodeIndex] ?? {} + const inputMap: Record = (this.oldToNewInputMap[nodeIndex] = + {}) + this.processInputSlots( + inputs as unknown as Record, + node, + slots, + linksTo, + inputMap, + seenInputs + ) // Converted inputs have to be processed after all other nodes as they'll be at the end of the list - // @ts-expect-error fixme ts strict error this.#convertedToProcess.push(() => this.processConvertedWidgets( inputs, @@ -696,79 +816,91 @@ export class GroupNodeConfig { ) ) - // @ts-expect-error fixme ts strict error return inputMapping } - // @ts-expect-error fixme ts strict error - processNodeOutputs(node, seenOutputs, def) { - // @ts-expect-error fixme ts strict error - const oldToNew = (this.oldToNewOutputMap[node.index] = {}) + processNodeOutputs( + node: GroupNodeData, + seenOutputs: Record, + def: GroupNodeDef | ComfyNodeDef + ) { + const nodeIndex = node.index ?? -1 + const oldToNew: Record = (this.oldToNewOutputMap[ + nodeIndex + ] = {}) + const defOutput = def.output ?? [] // Add outputs - for (let outputId = 0; outputId < def.output.length; outputId++) { - // @ts-expect-error fixme ts strict error - const linksFrom = this.linksFrom[node.index] + for (let outputId = 0; outputId < defOutput.length; outputId++) { + const linksFrom = this.linksFrom[nodeIndex] // If this output is linked internally we flag it to hide const hasLink = - // @ts-expect-error fixme ts strict error - linksFrom?.[outputId] && !this.externalFrom[node.index]?.[outputId] - const customConfig = - this.nodeData.config?.[node.index]?.output?.[outputId] + linksFrom?.[outputId] && !this.externalFrom[nodeIndex]?.[outputId] + const outputConfig = this.nodeData.config?.[node.index ?? -1] as + | NodeConfigEntry + | undefined + const customConfig = outputConfig?.output?.[outputId] const visible = customConfig?.visible ?? !hasLink this.outputVisibility.push(visible) if (!visible) { continue } - // @ts-expect-error fixme ts strict error - oldToNew[outputId] = this.nodeDef.output.length - // @ts-expect-error fixme ts strict error - this.newToOldOutputMap[this.nodeDef.output.length] = { - node, - slot: outputId + if (this.nodeDef?.output) { + oldToNew[outputId] = this.nodeDef.output.length + this.newToOldOutputMap[this.nodeDef.output.length] = { + node, + slot: outputId + } + // @ts-expect-error legacy dynamic output type assignment + this.nodeDef.output.push(defOutput[outputId]) + this.nodeDef.output_is_list?.push( + def.output_is_list?.[outputId] ?? false + ) } - // @ts-expect-error fixme ts strict error - this.nodeDef.output.push(def.output[outputId]) - // @ts-expect-error fixme ts strict error - this.nodeDef.output_is_list.push(def.output_is_list[outputId]) - let label = customConfig?.name + let label: string | undefined = customConfig?.name if (!label) { - label = def.output_name?.[outputId] ?? def.output[outputId] - // @ts-expect-error fixme ts strict error - const output = node.outputs.find((o) => o.name === label) + const outputVal = defOutput[outputId] + label = + def.output_name?.[outputId] ?? + (typeof outputVal === 'string' ? outputVal : undefined) + const output = node.outputs?.find((o) => o.name === label) if (output?.label) { label = output.label } } - let name = label + let name: string = String(label ?? `output_${outputId}`) if (name in seenOutputs) { const prefix = `${node.title ?? node.type} ` - name = `${prefix}${label}` + name = `${prefix}${label ?? outputId}` if (name in seenOutputs) { - name = `${prefix}${node.index} ${label}` + name = `${prefix}${node.index} ${label ?? outputId}` } } seenOutputs[name] = 1 - // @ts-expect-error fixme ts strict error - this.nodeDef.output_name.push(name) + this.nodeDef?.output_name?.push(name) } } - // @ts-expect-error fixme ts strict error - static async registerFromWorkflow(groupNodes, missingNodeTypes) { + static async registerFromWorkflow( + groupNodes: Record, + missingNodeTypes: ( + | string + | { type: string; hint?: string; action?: unknown } + )[] + ) { for (const g in groupNodes) { const groupData = groupNodes[g] let hasMissing = false for (const n of groupData.nodes) { // Find missing node types - if (!(n.type in LiteGraph.registered_node_types)) { + if (!n.type || !(n.type in LiteGraph.registered_node_types)) { missingNodeTypes.push({ - type: n.type, + type: n.type ?? 'unknown', hint: ` (In group node '${PREFIX}${SEPARATOR}${g}')` }) @@ -776,12 +908,12 @@ export class GroupNodeConfig { type: `${PREFIX}${SEPARATOR}` + g, action: { text: 'Remove from workflow', - // @ts-expect-error fixme ts strict error - callback: (e) => { + callback: (e: MouseEvent) => { delete groupNodes[g] - e.target.textContent = 'Removed' - e.target.style.pointerEvents = 'none' - e.target.style.opacity = 0.7 + const target = e.target as HTMLElement + target.textContent = 'Removed' + target.style.pointerEvents = 'none' + target.style.opacity = '0.7' } } }) @@ -800,12 +932,12 @@ export class GroupNodeConfig { export class GroupNodeHandler { node: LGraphNode - groupData: any - innerNodes: any + groupData: GroupNodeConfig + innerNodes: LGraphNode[] | null = null constructor(node: LGraphNode) { this.node = node - this.groupData = node.constructor?.nodeData?.[GROUP] + this.groupData = node.constructor?.nodeData?.[GROUP] as GroupNodeConfig this.node.setInnerNodes = (innerNodes) => { this.innerNodes = innerNodes @@ -820,60 +952,63 @@ export class GroupNodeHandler { for (const w of innerNode.widgets ?? []) { if (w.type === 'converted-widget') { + // @ts-expect-error legacy widget property for converted widgets w.serializeValue = w.origSerializeValue } } innerNode.index = innerNodeIndex - // @ts-expect-error fixme ts strict error - innerNode.getInputNode = (slot) => { + innerNode.getInputNode = (slot: number) => { // Check if this input is internal or external - const externalSlot = - this.groupData.oldToNewInputMap[innerNode.index]?.[slot] + const nodeIdx = innerNode.index ?? 0 + const externalSlot = this.groupData.oldToNewInputMap[nodeIdx]?.[slot] if (externalSlot != null) { return this.node.getInputNode(externalSlot) } // Internal link - const innerLink = this.groupData.linksTo[innerNode.index]?.[slot] + const innerLink = this.groupData.linksTo[nodeIdx]?.[slot] if (!innerLink) return null - const inputNode = innerNodes[innerLink[0]] + const linkSrcIdx = innerLink[0] + if (linkSrcIdx == null) return null + const inputNode = innerNodes[Number(linkSrcIdx)] // Primitives will already apply their values if (inputNode.type === 'PrimitiveNode') return null return inputNode } - // @ts-expect-error fixme ts strict error - innerNode.getInputLink = (slot) => { - const externalSlot = - this.groupData.oldToNewInputMap[innerNode.index]?.[slot] + // @ts-expect-error returns partial link object, not full LLink + innerNode.getInputLink = (slot: number) => { + const nodeIdx = innerNode.index ?? 0 + const externalSlot = this.groupData.oldToNewInputMap[nodeIdx]?.[slot] if (externalSlot != null) { // The inner node is connected via the group node inputs const linkId = this.node.inputs[externalSlot].link - // @ts-expect-error fixme ts strict error - let link = app.graph.links[linkId] + if (linkId == null) return null + const existingLink = app.rootGraph.links[linkId] + if (!existingLink) return null // Use the outer link, but update the target to the inner node - link = { - ...link, + return { + ...existingLink, target_id: innerNode.id, target_slot: +slot } - return link } - let link = this.groupData.linksTo[innerNode.index]?.[slot] - if (!link) return null + const innerLink = this.groupData.linksTo[nodeIdx]?.[slot] + if (!innerLink) return null + const linkSrcIdx = innerLink[0] + if (linkSrcIdx == null) return null // Use the inner link, but update the origin node to be inner node id - link = { - origin_id: innerNodes[link[0]].id, - origin_slot: link[1], + return { + origin_id: innerNodes[Number(linkSrcIdx)].id, + origin_slot: innerLink[1], target_id: innerNode.id, target_slot: +slot } - return link } } } @@ -883,7 +1018,9 @@ export class GroupNodeHandler { // @ts-expect-error Can this be removed? Or replaced with: LLink.create(link.asSerialisable()) link = { ...link } const output = this.groupData.newToOldOutputMap[link.origin_slot] - let innerNode = this.innerNodes[output.node.index] + if (!output || !this.innerNodes) return null + const nodeIdx = output.node.index ?? 0 + let innerNode: LGraphNode | null = this.innerNodes[nodeIdx] let l while (innerNode?.type === 'Reroute') { l = innerNode.getInputLink(0) @@ -894,7 +1031,11 @@ export class GroupNodeHandler { return null } - if (l && GroupNodeHandler.isGroupNode(innerNode)) { + if ( + l && + GroupNodeHandler.isGroupNode(innerNode) && + innerNode.updateLink + ) { return innerNode.updateLink(l) } @@ -918,20 +1059,19 @@ export class GroupNodeHandler { visited.add(this.node) if (!this.innerNodes) { - // @ts-expect-error fixme ts strict error - this.node.setInnerNodes( - // @ts-expect-error fixme ts strict error - this.groupData.nodeData.nodes.map((n, i) => { + const createdNodes = this.groupData.nodeData.nodes + .map((n, i) => { + if (!n.type) return null const innerNode = LiteGraph.createNode(n.type) - // @ts-expect-error fixme ts strict error + if (!innerNode) return null + // @ts-expect-error legacy node data format used for configure innerNode.configure(n) - // @ts-expect-error fixme ts strict error innerNode.id = `${this.node.id}:${i}` - // @ts-expect-error fixme ts strict error innerNode.graph = this.node.graph return innerNode }) - ) + .filter((n): n is LGraphNode => n !== null) + this.node.setInnerNodes?.(createdNodes) } this.updateInnerWidgets() @@ -943,11 +1083,12 @@ export class GroupNodeHandler { subgraphNodePath.at(-1) ) ?? undefined) as SubgraphNode | undefined - for (const node of this.innerNodes) { + for (const node of this.innerNodes ?? []) { node.graph ??= this.node.graph // Create minimal DTOs rather than cloning the node const currentId = String(node.id) + // @ts-expect-error temporary id reassignment for DTO creation node.id = currentId.split(':').at(-1) const aVeryRealNode = new ExecutableGroupNodeChildDTO( node, @@ -963,95 +1104,87 @@ export class GroupNodeHandler { return nodes } - // @ts-expect-error fixme ts strict error + // @ts-expect-error recreate returns null if creation fails this.node.recreate = async () => { const id = this.node.id const sz = this.node.size - // @ts-expect-error fixme ts strict error - const nodes = this.node.convertToNodes() + const nodes = ( + this.node as LGraphNode & { convertToNodes?: () => LGraphNode[] } + ).convertToNodes?.() + if (!nodes) return null const groupNode = LiteGraph.createNode(this.node.type) - // @ts-expect-error fixme ts strict error + if (!groupNode) return null groupNode.id = id // Reuse the existing nodes for this instance - // @ts-expect-error fixme ts strict error - groupNode.setInnerNodes(nodes) - // @ts-expect-error fixme ts strict error - groupNode[GROUP].populateWidgets() - // @ts-expect-error fixme ts strict error - app.graph.add(groupNode) - // @ts-expect-error fixme ts strict error - groupNode.setSize([ - // @ts-expect-error fixme ts strict error + groupNode.setInnerNodes?.(nodes) + const handler = GroupNodeHandler.getHandler(groupNode) + handler?.populateWidgets() + app.rootGraph.add(groupNode) + groupNode.setSize?.([ Math.max(groupNode.size[0], sz[0]), - // @ts-expect-error fixme ts strict error Math.max(groupNode.size[1], sz[1]) ]) // Remove all converted nodes and relink them const builder = new GroupNodeBuilder(nodes) const nodeData = builder.getNodeData() - // @ts-expect-error fixme ts strict error - groupNode[GROUP].groupData.nodeData.links = nodeData.links - // @ts-expect-error fixme ts strict error - groupNode[GROUP].replaceNodes(nodes) + if (handler) { + handler.groupData.nodeData.links = nodeData.links + handler.replaceNodes(nodes) + } return groupNode } - - // @ts-expect-error fixme ts strict error - this.node.convertToNodes = () => { + ;( + this.node as LGraphNode & { convertToNodes: () => LGraphNode[] } + ).convertToNodes = () => { const addInnerNodes = () => { // Clone the node data so we dont mutate it for other nodes const c = { ...this.groupData.nodeData } c.nodes = [...c.nodes] - // @ts-expect-error fixme ts strict error - const innerNodes = this.node.getInnerNodes() - let ids = [] + // @ts-expect-error getInnerNodes called without args in legacy conversion code + const innerNodes = this.node.getInnerNodes?.() + const ids: (string | number)[] = [] for (let i = 0; i < c.nodes.length; i++) { - let id = innerNodes?.[i]?.id + let id: string | number | undefined = innerNodes?.[i]?.id // Use existing IDs if they are set on the inner nodes - // @ts-expect-error id can be string or number - if (id == null || isNaN(id)) { - // @ts-expect-error fixme ts strict error + if (id == null || (typeof id === 'number' && isNaN(id))) { id = undefined } else { ids.push(id) } + // @ts-expect-error adding id to node copy for serialization c.nodes[i] = { ...c.nodes[i], id } } deserialiseAndCreate(JSON.stringify(c), app.canvas) const [x, y] = this.node.pos - let top - let left + let top: number | undefined + let left: number | undefined // Configure nodes with current widget data const selectedIds = ids.length ? ids : Object.keys(app.canvas.selected_nodes) - const newNodes = [] + const newNodes: LGraphNode[] = [] for (let i = 0; i < selectedIds.length; i++) { const id = selectedIds[i] - const newNode = app.graph.getNodeById(id) - const innerNode = innerNodes[i] + const newNode = app.rootGraph.getNodeById(id) + const innerNode = innerNodes?.[i] + if (!newNode) continue newNodes.push(newNode) - // @ts-expect-error fixme ts strict error if (left == null || newNode.pos[0] < left) { - // @ts-expect-error fixme ts strict error left = newNode.pos[0] } - // @ts-expect-error fixme ts strict error if (top == null || newNode.pos[1] < top) { - // @ts-expect-error fixme ts strict error top = newNode.pos[1] } - // @ts-expect-error fixme ts strict error - if (!newNode.widgets) continue + if (!newNode.widgets || !innerNode) continue - // @ts-expect-error fixme ts strict error - const map = this.groupData.oldToNewWidgetMap[innerNode.index] + // @ts-expect-error index property access on ExecutableLGraphNode + const map = this.groupData.oldToNewWidgetMap[innerNode.index ?? 0] if (map) { const widgets = Object.keys(map) @@ -1059,37 +1192,32 @@ export class GroupNodeHandler { const newName = map[oldName] if (!newName) continue - // @ts-expect-error fixme ts strict error - const widgetIndex = this.node.widgets.findIndex( - (w) => w.name === newName - ) + const widgetIndex = + this.node.widgets?.findIndex((w) => w.name === newName) ?? -1 if (widgetIndex === -1) continue // Populate the main and any linked widgets if (innerNode.type === 'PrimitiveNode') { - // @ts-expect-error fixme ts strict error for (let i = 0; i < newNode.widgets.length; i++) { - // @ts-expect-error fixme ts strict error - newNode.widgets[i].value = - // @ts-expect-error fixme ts strict error - this.node.widgets[widgetIndex + i].value + const srcWidget = this.node.widgets?.[widgetIndex + i] + if (srcWidget) { + newNode.widgets[i].value = srcWidget.value + } } } else { - // @ts-expect-error fixme ts strict error - const outerWidget = this.node.widgets[widgetIndex] - // @ts-expect-error fixme ts strict error + const outerWidget = this.node.widgets?.[widgetIndex] const newWidget = newNode.widgets.find( (w) => w.name === oldName ) - if (!newWidget) continue + if (!newWidget || !outerWidget) continue newWidget.value = outerWidget.value - // @ts-expect-error fixme ts strict error - for (let w = 0; w < outerWidget.linkedWidgets?.length; w++) { - // @ts-expect-error fixme ts strict error - newWidget.linkedWidgets[w].value = - // @ts-expect-error fixme ts strict error - outerWidget.linkedWidgets[w].value + const linkedWidgets = outerWidget.linkedWidgets ?? [] + for (let w = 0; w < linkedWidgets.length; w++) { + const newLinked = newWidget.linkedWidgets?.[w] + if (newLinked && linkedWidgets[w]) { + newLinked.value = linkedWidgets[w].value + } } } } @@ -1098,38 +1226,34 @@ export class GroupNodeHandler { // Shift each node for (const newNode of newNodes) { - // @ts-expect-error fixme ts strict error - newNode.pos[0] -= left - x - // @ts-expect-error fixme ts strict error - newNode.pos[1] -= top - y + newNode.pos[0] -= (left ?? 0) - x + newNode.pos[1] -= (top ?? 0) - y } return { newNodes, selectedIds } } - // @ts-expect-error fixme ts strict error - const reconnectInputs = (selectedIds) => { + const reconnectInputs = (selectedIds: (string | number)[]) => { for (const innerNodeIndex in this.groupData.oldToNewInputMap) { - const id = selectedIds[innerNodeIndex] - const newNode = app.graph.getNodeById(id) - const map = this.groupData.oldToNewInputMap[innerNodeIndex] + const id = selectedIds[Number(innerNodeIndex)] + const newNode = app.rootGraph.getNodeById(id) + if (!newNode) continue + const map = this.groupData.oldToNewInputMap[Number(innerNodeIndex)] for (const innerInputId in map) { - const groupSlotId = map[innerInputId] + const groupSlotId = map[Number(innerInputId)] if (groupSlotId == null) continue const slot = node.inputs[groupSlotId] if (slot.link == null) continue - const link = app.graph.links[slot.link] + const link = app.rootGraph.links[slot.link] if (!link) continue // connect this node output to the input of another node - const originNode = app.graph.getNodeById(link.origin_id) - // @ts-expect-error fixme ts strict error - originNode.connect(link.origin_slot, newNode, +innerInputId) + const originNode = app.rootGraph.getNodeById(link.origin_id) + originNode?.connect(link.origin_slot, newNode, +innerInputId) } } } - // @ts-expect-error fixme ts strict error - const reconnectOutputs = (selectedIds) => { + const reconnectOutputs = (selectedIds: (string | number)[]) => { for ( let groupOutputId = 0; groupOutputId < node.outputs?.length; @@ -1140,11 +1264,16 @@ export class GroupNodeHandler { const links = [...output.links] for (const l of links) { const slot = this.groupData.newToOldOutputMap[groupOutputId] - const link = app.graph.links[l] - const targetNode = app.graph.getNodeById(link.target_id) - const newNode = app.graph.getNodeById(selectedIds[slot.node.index]) - // @ts-expect-error fixme ts strict error - newNode.connect(slot.slot, targetNode, link.target_slot) + if (!slot) continue + const link = app.rootGraph.links[l] + if (!link) continue + const targetNode = app.rootGraph.getNodeById(link.target_id) + const newNode = app.rootGraph.getNodeById( + selectedIds[slot.node.index ?? 0] + ) + if (targetNode) { + newNode?.connect(slot.slot, targetNode, link.target_slot) + } } } } @@ -1155,7 +1284,7 @@ export class GroupNodeHandler { const { newNodes, selectedIds } = addInnerNodes() reconnectInputs(selectedIds) reconnectOutputs(selectedIds) - app.graph.remove(this.node) + app.rootGraph.remove(this.node) return newNodes } finally { @@ -1164,10 +1293,9 @@ export class GroupNodeHandler { } const getExtraMenuOptions = this.node.getExtraMenuOptions - // @ts-expect-error Should pass patched return value getExtraMenuOptions - this.node.getExtraMenuOptions = function (_, options) { - // @ts-expect-error fixme ts strict error - getExtraMenuOptions?.apply(this, arguments) + const handlerNode = this.node + this.node.getExtraMenuOptions = function (_canvas, options) { + getExtraMenuOptions?.call(this, _canvas, options) let optionIndex = options.findIndex((o) => o?.content === 'Outputs') if (optionIndex === -1) optionIndex = options.length @@ -1178,10 +1306,14 @@ export class GroupNodeHandler { null, { content: 'Convert to nodes', - // @ts-expect-error - callback: () => { - // @ts-expect-error fixme ts strict error - return this.convertToNodes() + // @ts-expect-error async callback not expected by legacy menu API + callback: async () => { + const convertFn = ( + handlerNode as LGraphNode & { + convertToNodes?: () => LGraphNode[] + } + ).convertToNodes + return convertFn?.() } }, { @@ -1189,13 +1321,15 @@ export class GroupNodeHandler { callback: () => manageGroupNodes(this.type) } ) + // Return empty array to satisfy type signature without triggering + // LGraphCanvas concatenation (which only happens when length > 0) + return [] } // Draw custom collapse icon to identity this as a group const onDrawTitleBox = this.node.onDrawTitleBox - this.node.onDrawTitleBox = function (ctx, height) { - // @ts-expect-error fixme ts strict error - onDrawTitleBox?.apply(this, arguments) + this.node.onDrawTitleBox = function (ctx, height, size, scale) { + onDrawTitleBox?.call(this, ctx, height, size, scale) const fill = ctx.fillStyle ctx.beginPath() @@ -1217,17 +1351,19 @@ export class GroupNodeHandler { // Draw progress label const onDrawForeground = node.onDrawForeground const groupData = this.groupData.nodeData - node.onDrawForeground = function (ctx) { - // @ts-expect-error fixme ts strict error - onDrawForeground?.apply?.(this, arguments) + node.onDrawForeground = function (ctx, canvas, canvasElement) { + onDrawForeground?.call(this, ctx, canvas, canvasElement) const progressState = useExecutionStore().nodeProgressStates[this.id] if ( progressState && progressState.state === 'running' && this.runningInternalNodeId !== null ) { - // @ts-expect-error fixme ts strict error - const n = groupData.nodes[this.runningInternalNodeId] + const nodeIdx = + typeof this.runningInternalNodeId === 'number' + ? this.runningInternalNodeId + : parseInt(String(this.runningInternalNodeId), 10) + const n = groupData.nodes[nodeIdx] as { title?: string; type?: string } if (!n) return const message = `Running ${n.title || n.type} (${this.runningInternalNodeId}/${groupData.nodes.length})` ctx.save() @@ -1253,26 +1389,28 @@ export class GroupNodeHandler { // Flag this node as needing to be reset const onExecutionStart = this.node.onExecutionStart this.node.onExecutionStart = function () { - // @ts-expect-error fixme ts strict error - this.resetExecution = true - // @ts-expect-error fixme ts strict error - return onExecutionStart?.apply(this, arguments) + ;(this as LGraphNode & { resetExecution?: boolean }).resetExecution = true + return onExecutionStart?.call(this) } - const self = this const onNodeCreated = this.node.onNodeCreated + const handlerGroupData = this.groupData this.node.onNodeCreated = function () { if (!this.widgets) { return } - const config = self.groupData.nodeData.config + const config = handlerGroupData.nodeData.config as + | Record + | undefined if (config) { for (const n in config) { const inputs = config[n]?.input + if (!inputs) continue for (const w in inputs) { - if (inputs[w].visible !== false) continue - const widgetName = self.groupData.oldToNewWidgetMap[n][w] - const widget = this.widgets.find((w) => w.name === widgetName) + if (inputs[w]?.visible !== false) continue + const widgetName = + handlerGroupData.oldToNewWidgetMap[Number(n)]?.[w] + const widget = this.widgets.find((wg) => wg.name === widgetName) if (widget) { widget.type = 'hidden' widget.computeSize = () => [0, -4] @@ -1281,91 +1419,88 @@ export class GroupNodeHandler { } } - // @ts-expect-error fixme ts strict error - return onNodeCreated?.apply(this, arguments) + return onNodeCreated?.call(this) } - // @ts-expect-error fixme ts strict error - function handleEvent(type, getId, getEvent) { - // @ts-expect-error fixme ts strict error - const handler = ({ detail }) => { + type EventDetail = { display_node?: string; node?: string } | string + const handleEvent = ( + type: string, + getId: (detail: EventDetail) => string | undefined, + getEvent: ( + detail: EventDetail, + id: string, + node: LGraphNode + ) => EventDetail + ) => { + const handler = ({ detail }: CustomEvent) => { const id = getId(detail) if (!id) return - const node = app.graph.getNodeById(id) - if (node) return + const existingNode = app.rootGraph.getNodeById(id) + if (existingNode) return - // @ts-expect-error fixme ts strict error - const innerNodeIndex = this.innerNodes?.findIndex((n) => n.id == id) + const innerNodeIndex = + this.innerNodes?.findIndex((n) => n.id == id) ?? -1 if (innerNodeIndex > -1) { - // @ts-expect-error fixme ts strict error - this.node.runningInternalNodeId = innerNodeIndex + ;( + this.node as LGraphNode & { runningInternalNodeId?: number } + ).runningInternalNodeId = innerNodeIndex api.dispatchCustomEvent( - type, - // @ts-expect-error fixme ts strict error - getEvent(detail, `${this.node.id}`, this.node) + type as 'executing', + getEvent(detail, `${this.node.id}`, this.node) as string ) } } - api.addEventListener(type, handler) + api.addEventListener( + type as 'executing' | 'executed', + handler as EventListener + ) return handler } - const executing = handleEvent.call( - this, + const executing = handleEvent( 'executing', - // @ts-expect-error fixme ts strict error - (d) => d, - // @ts-expect-error fixme ts strict error - (_, id) => id + (d) => (typeof d === 'string' ? d : undefined), + (_d, id) => id ) - const executed = handleEvent.call( - this, + const executed = handleEvent( 'executed', - // @ts-expect-error fixme ts strict error - (d) => d?.display_node || d?.node, - // @ts-expect-error fixme ts strict error + (d) => (typeof d === 'object' ? d?.display_node || d?.node : undefined), (d, id, node) => ({ - ...d, + ...(typeof d === 'object' ? d : {}), node: id, display_node: id, - merge: !node.resetExecution + merge: !(node as LGraphNode & { resetExecution?: boolean }) + .resetExecution }) ) const onRemoved = node.onRemoved this.node.onRemoved = function () { - // @ts-expect-error fixme ts strict error - onRemoved?.apply(this, arguments) - // api.removeEventListener('progress_state', progress_state) - api.removeEventListener('executing', executing) - api.removeEventListener('executed', executed) + onRemoved?.call(this) + api.removeEventListener('executing', executing as EventListener) + api.removeEventListener('executed', executed as EventListener) } this.node.refreshComboInNode = (defs) => { // Update combo widget options for (const widgetName in this.groupData.newToOldWidgetMap) { - // @ts-expect-error fixme ts strict error - const widget = this.node.widgets.find((w) => w.name === widgetName) + const widget = this.node.widgets?.find((w) => w.name === widgetName) if (widget?.type === 'combo') { const old = this.groupData.newToOldWidgetMap[widgetName] + if (!old.node.type) continue const def = defs[old.node.type] const input = def?.input?.required?.[old.inputName] ?? def?.input?.optional?.[old.inputName] if (!input) continue - widget.options.values = input[0] + widget.options.values = input[0] as unknown[] - if ( - old.inputName !== 'image' && - // @ts-expect-error Widget values - !widget.options.values.includes(widget.value) - ) { - // @ts-expect-error fixme ts strict error - widget.value = widget.options.values[0] - // @ts-expect-error fixme ts strict error - widget.callback(widget.value) + const values = widget.options.values as unknown[] + if (old.inputName !== 'image' && !values.includes(widget.value)) { + widget.value = values[0] as typeof widget.value + widget.callback?.(widget.value) } } } @@ -1373,22 +1508,30 @@ export class GroupNodeHandler { } updateInnerWidgets() { + if (!this.innerNodes) return for (const newWidgetName in this.groupData.newToOldWidgetMap) { - // @ts-expect-error fixme ts strict error - const newWidget = this.node.widgets.find((w) => w.name === newWidgetName) + const newWidget = this.node.widgets?.find((w) => w.name === newWidgetName) if (!newWidget) continue const newValue = newWidget.value const old = this.groupData.newToOldWidgetMap[newWidgetName] - let innerNode = this.innerNodes[old.node.index] + const nodeIdx = old.node.index ?? 0 + const innerNode = this.innerNodes[nodeIdx] + if (!innerNode) continue if (innerNode.type === 'PrimitiveNode') { + // @ts-expect-error primitiveValue is a custom property on PrimitiveNode innerNode.primitiveValue = newValue - const primitiveLinked = this.groupData.primitiveToWidget[old.node.index] + const primitiveLinked = this.groupData.primitiveToWidget[nodeIdx] for (const linked of primitiveLinked ?? []) { - const node = this.innerNodes[linked.nodeId] - // @ts-expect-error fixme ts strict error - const widget = node.widgets.find((w) => w.name === linked.inputName) + const linkedNodeId = + typeof linked.nodeId === 'number' + ? linked.nodeId + : Number(linked.nodeId) + const linkedNode = this.innerNodes[linkedNodeId] + const widget = linkedNode?.widgets?.find( + (w) => w.name === linked.inputName + ) if (widget) { widget.value = newValue @@ -1396,15 +1539,17 @@ export class GroupNodeHandler { } continue } else if (innerNode.type === 'Reroute') { - const rerouteLinks = this.groupData.linksFrom[old.node.index] + const rerouteLinks = this.groupData.linksFrom[nodeIdx] if (rerouteLinks) { - for (const [_, , targetNodeId, targetSlot] of rerouteLinks['0']) { - const node = this.innerNodes[targetNodeId] - const input = node.inputs[targetSlot] - if (input.widget) { - const widget = node.widgets?.find( - // @ts-expect-error fixme ts strict error - (w) => w.name === input.widget.name + for (const [, , targetNodeId, targetSlot] of rerouteLinks[0] ?? []) { + if (targetNodeId == null || targetSlot == null) continue + const targetNode = this.innerNodes[Number(targetNodeId)] + if (!targetNode) continue + const input = targetNode.inputs?.[Number(targetSlot)] + if (input?.widget) { + const widgetName = input.widget.name + const widget = targetNode.widgets?.find( + (w) => w.name === widgetName ) if (widget) { widget.value = newValue @@ -1414,7 +1559,6 @@ export class GroupNodeHandler { } } - // @ts-expect-error fixme ts strict error const widget = innerNode.widgets?.find((w) => w.name === old.inputName) if (widget) { widget.value = newValue @@ -1422,57 +1566,73 @@ export class GroupNodeHandler { } } - // @ts-expect-error fixme ts strict error - populatePrimitive(_node, nodeId, oldName) { + populatePrimitive( + _node: GroupNodeData, + nodeId: number, + oldName: string + ): boolean { // Converted widget, populate primitive if linked const primitiveId = this.groupData.widgetToPrimitive[nodeId]?.[oldName] - if (primitiveId == null) return + if (primitiveId == null) return false const targetWidgetName = - this.groupData.oldToNewWidgetMap[primitiveId]['value'] - // @ts-expect-error fixme ts strict error - const targetWidgetIndex = this.node.widgets.findIndex( - (w) => w.name === targetWidgetName - ) - if (targetWidgetIndex > -1) { - const primitiveNode = this.innerNodes[primitiveId] + this.groupData.oldToNewWidgetMap[ + Array.isArray(primitiveId) ? primitiveId[0] : primitiveId + ]?.['value'] + if (!targetWidgetName) return false + const targetWidgetIndex = + this.node.widgets?.findIndex((w) => w.name === targetWidgetName) ?? -1 + if (targetWidgetIndex > -1 && this.innerNodes) { + const primIdx = Array.isArray(primitiveId) ? primitiveId[0] : primitiveId + const primitiveNode = this.innerNodes[primIdx] + if (!primitiveNode?.widgets) return true let len = primitiveNode.widgets.length if ( len - 1 !== - // @ts-expect-error fixme ts strict error - this.node.widgets[targetWidgetIndex].linkedWidgets?.length + (this.node.widgets?.[targetWidgetIndex]?.linkedWidgets?.length ?? 0) ) { // Fallback handling for if some reason the primitive has a different number of widgets // we dont want to overwrite random widgets, better to leave blank len = 1 } for (let i = 0; i < len; i++) { - // @ts-expect-error fixme ts strict error - this.node.widgets[targetWidgetIndex + i].value = - primitiveNode.widgets[i].value + const targetWidget = this.node.widgets?.[targetWidgetIndex + i] + const srcWidget = primitiveNode.widgets[i] + if (targetWidget && srcWidget) { + targetWidget.value = srcWidget.value + } } } return true } - // @ts-expect-error fixme ts strict error - populateReroute(node, nodeId, map) { + populateReroute( + node: GroupNodeData, + nodeId: number, + map: Record + ) { if (node.type !== 'Reroute') return const link = this.groupData.linksFrom[nodeId]?.[0]?.[0] if (!link) return - const [, , targetNodeId, targetNodeSlot] = link - const targetNode = this.groupData.nodeData.nodes[targetNodeId] - const inputs = targetNode.inputs - const targetWidget = inputs?.[targetNodeSlot]?.widget + const targetNodeId = link[2] + const targetNodeSlot = link[3] + if (targetNodeId == null || targetNodeSlot == null) return + const targetNode = this.groupData.nodeData.nodes[Number(targetNodeId)] as + | GroupNodeData + | undefined + const inputs = targetNode?.inputs + const targetWidget = (inputs as GroupNodeInput[] | undefined)?.[ + Number(targetNodeSlot) + ]?.widget if (!targetWidget) return - const offset = inputs.length - (targetNode.widgets_values?.length ?? 0) - const v = targetNode.widgets_values?.[targetNodeSlot - offset] + const offset = + (inputs?.length ?? 0) - (targetNode?.widgets_values?.length ?? 0) + const v = targetNode?.widgets_values?.[Number(targetNodeSlot) - offset] if (v == null) return const widgetName = Object.values(map)[0] - // @ts-expect-error fixme ts strict error - const widget = this.node.widgets.find((w) => w.name === widgetName) + const widget = this.node.widgets?.find((w) => w.name === widgetName) if (widget) { widget.value = v } @@ -1486,7 +1646,7 @@ export class GroupNodeHandler { nodeId < this.groupData.nodeData.nodes.length; nodeId++ ) { - const node = this.groupData.nodeData.nodes[nodeId] + const node = this.groupData.nodeData.nodes[nodeId] as GroupNodeData const map = this.groupData.oldToNewWidgetMap[nodeId] ?? {} const widgets = Object.keys(map) @@ -1510,8 +1670,7 @@ export class GroupNodeHandler { widgetIndex === -1 ) { // Find the inner widget and shift by the number of linked widgets as they will have been removed too - const innerWidget = this.innerNodes[nodeId].widgets?.find( - // @ts-expect-error fixme ts strict error + const innerWidget = this.innerNodes?.[nodeId]?.widgets?.find( (w) => w.name === oldName ) linkedShift += innerWidget?.linkedWidgets?.length ?? 0 @@ -1521,20 +1680,22 @@ export class GroupNodeHandler { } // Populate the main and any linked widget - mainWidget.value = node.widgets_values[i + linkedShift] - // @ts-expect-error fixme ts strict error - for (let w = 0; w < mainWidget.linkedWidgets?.length; w++) { - this.node.widgets[widgetIndex + w + 1].value = - node.widgets_values[i + ++linkedShift] + mainWidget.value = node.widgets_values?.[ + i + linkedShift + ] as typeof mainWidget.value + const linkedWidgets = mainWidget.linkedWidgets ?? [] + for (let w = 0; w < linkedWidgets.length; w++) { + this.node.widgets[widgetIndex + w + 1].value = node.widgets_values?.[ + i + ++linkedShift + ] as typeof mainWidget.value } } } } - // @ts-expect-error fixme ts strict error - replaceNodes(nodes) { - let top - let left + replaceNodes(nodes: LGraphNode[]) { + let top: number | undefined + let left: number | undefined for (let i = 0; i < nodes.length; i++) { const node = nodes[i] @@ -1546,18 +1707,17 @@ export class GroupNodeHandler { } this.linkOutputs(node, i) - app.graph.remove(node) + app.rootGraph.remove(node) // Set internal ID to what is expected after workflow is reloaded node.id = `${this.node.id}:${i}` } this.linkInputs() - this.node.pos = [left, top] + this.node.pos = [left ?? 0, top ?? 0] } - // @ts-expect-error fixme ts strict error - linkOutputs(originalNode, nodeId) { + linkOutputs(originalNode: LGraphNode, nodeId: number) { if (!originalNode.outputs) return for (const output of originalNode.outputs) { @@ -1565,14 +1725,13 @@ export class GroupNodeHandler { // Clone the links as they'll be changed if we reconnect const links = [...output.links] for (const l of links) { - const link = app.graph.links[l] + const link = app.rootGraph.links[l] if (!link) continue - const targetNode = app.graph.getNodeById(link.target_id) + const targetNode = app.rootGraph.getNodeById(link.target_id) const newSlot = this.groupData.oldToNewOutputMap[nodeId]?.[link.origin_slot] - if (newSlot != null) { - // @ts-expect-error fixme ts strict error + if (newSlot != null && targetNode) { this.node.connect(newSlot, targetNode, link.target_slot) } } @@ -1582,20 +1741,58 @@ export class GroupNodeHandler { linkInputs() { for (const link of this.groupData.nodeData.links ?? []) { const [, originSlot, targetId, targetSlot, actualOriginId] = link - const originNode = app.graph.getNodeById(actualOriginId) + if (actualOriginId == null || typeof actualOriginId === 'object') continue + const originNode = app.rootGraph.getNodeById(actualOriginId) if (!originNode) continue // this node is in the group - originNode.connect( - originSlot, - // @ts-expect-error Valid - uses deprecated interface. Required check: if (graph.getNodeById(this.node.id) !== this.node) report() - this.node.id, - this.groupData.oldToNewInputMap[targetId][targetSlot] - ) + if (targetId == null || targetSlot == null) continue + const mappedSlot = + this.groupData.oldToNewInputMap[Number(targetId)]?.[Number(targetSlot)] + if (mappedSlot == null) continue + if (typeof originSlot === 'number' || typeof originSlot === 'string') { + originNode.connect( + originSlot, + // @ts-expect-error Valid - uses deprecated interface (node ID instead of node reference) + this.node.id, + mappedSlot + ) + } } } - // @ts-expect-error fixme ts strict error - static getGroupData(node) { - return (node.nodeData ?? node.constructor?.nodeData)?.[GROUP] + static getGroupData( + node: LGraphNodeConstructor + ): GroupNodeConfig | undefined + static getGroupData(node: LGraphNode): GroupNodeConfig | undefined + static getGroupData( + node: LGraphNode | LGraphNodeConstructor + ): GroupNodeConfig | undefined { + // Check if this is a constructor (function) or an instance + if (typeof node === 'function') { + // Constructor case - access nodeData directly + const nodeData = (node as LGraphNodeConstructor & { nodeData?: unknown }) + .nodeData as Record | undefined + return nodeData?.[GROUP] + } + // Instance case - check instance property first, then constructor + const instanceData = (node as LGraphNode & { nodeData?: unknown }) + .nodeData as Record | undefined + if (instanceData?.[GROUP]) return instanceData[GROUP] + const ctorData = ( + node.constructor as LGraphNodeConstructor & { nodeData?: unknown } + )?.nodeData as Record | undefined + return ctorData?.[GROUP] + } + + static getHandler(node: LGraphNode): GroupNodeHandler | undefined { + // @ts-expect-error GROUP symbol indexing on LGraphNode + let handler = node[GROUP] as GroupNodeHandler | undefined + // Handler may not be set yet if nodeCreated async hook hasn't run + // Create it synchronously if needed + if (!handler && GroupNodeHandler.isGroupNode(node)) { + handler = new GroupNodeHandler(node) + ;(node as LGraphNode & { [GROUP]: GroupNodeHandler })[GROUP] = handler + } + return handler } static isGroupNode(node: LGraphNode) { @@ -1615,17 +1812,15 @@ export class GroupNodeHandler { await config.registerType() const groupNode = LiteGraph.createNode(`${PREFIX}${SEPARATOR}${name}`) + if (!groupNode) return // Reuse the existing nodes for this instance - // @ts-expect-error fixme ts strict error - groupNode.setInnerNodes(builder.nodes) - // @ts-expect-error fixme ts strict error - groupNode[GROUP].populateWidgets() - // @ts-expect-error fixme ts strict error - app.graph.add(groupNode) + groupNode.setInnerNodes?.(builder.nodes) + const handler = GroupNodeHandler.getHandler(groupNode) + handler?.populateWidgets() + app.rootGraph.add(groupNode) // Remove all converted nodes and relink them - // @ts-expect-error fixme ts strict error - groupNode[GROUP].replaceNodes(builder.nodes) + handler?.replaceNodes(builder.nodes) return groupNode } } @@ -1684,8 +1879,24 @@ function manageGroupNodes(type?: string) { } const id = 'Comfy.GroupNode' -// @ts-expect-error fixme ts strict error -let globalDefs + +/** + * Global node definitions cache, populated and mutated by extension callbacks. + * + * **Initialization**: Set by `addCustomNodeDefs` during extension initialization. + * This callback runs early in the app lifecycle, before any code that reads + * `globalDefs` is executed. + * + * **Mutation**: `refreshComboInNodes` merges updated definitions into this object + * when combo options are refreshed (e.g., after model files change). + * + * **Usage Notes**: + * - Functions reading `globalDefs` (e.g., `getNodeDef`, `checkPrimitiveConnection`) + * must only be called after `addCustomNodeDefs` has run. + * - Not thread-safe; assumes single-threaded JS execution model. + * - The object reference is stable after initialization; only contents are mutated. + */ +let globalDefs: Record const ext: ComfyExtension = { name: id, commands: [ @@ -1738,8 +1949,8 @@ const ext: ComfyExtension = { items.push({ content: `Convert to Group Node (Deprecated)`, disabled: !convertEnabled, - // @ts-expect-error fixme ts strict error - async callback - callback: () => convertSelectedNodesToGroupNode() + // @ts-expect-error async callback - legacy menu API doesn't expect Promise + callback: async () => convertSelectedNodesToGroupNode() }) const groups = canvas.graph?.extra?.groupNodes @@ -1765,8 +1976,8 @@ const ext: ComfyExtension = { { content: `Convert to Group Node (Deprecated)`, disabled: !convertEnabled, - // @ts-expect-error fixme ts strict error - async callback - callback: () => convertSelectedNodesToGroupNode() + // @ts-expect-error async callback - legacy menu API doesn't expect Promise + callback: async () => convertSelectedNodesToGroupNode() } ] }, @@ -1774,7 +1985,9 @@ const ext: ComfyExtension = { graphData: ComfyWorkflowJSON, missingNodeTypes: string[] ) { - const nodes = graphData?.extra?.groupNodes + const nodes = graphData?.extra?.groupNodes as + | Record + | undefined if (nodes) { replaceLegacySeparators(graphData.nodes) await GroupNodeConfig.registerFromWorkflow(nodes, missingNodeTypes) @@ -1786,25 +1999,24 @@ const ext: ComfyExtension = { }, nodeCreated(node) { if (GroupNodeHandler.isGroupNode(node)) { - // @ts-expect-error fixme ts strict error - node[GROUP] = new GroupNodeHandler(node) + ;(node as LGraphNode & { [GROUP]: GroupNodeHandler })[GROUP] = + new GroupNodeHandler(node) // Ensure group nodes pasted from other workflows are stored - // @ts-expect-error fixme ts strict error - if (node.title && node[GROUP]?.groupData?.nodeData) { - // @ts-expect-error fixme ts strict error - Workflow.storeGroupNode(node.title, node[GROUP].groupData.nodeData) + const handler = GroupNodeHandler.getHandler(node) + if (node.title && handler?.groupData?.nodeData) { + Workflow.storeGroupNode(node.title, handler.groupData.nodeData) } } }, - // @ts-expect-error fixme ts strict error - async refreshComboInNodes(defs) { + async refreshComboInNodes(defs: Record) { // Re-register group nodes so new ones are created with the correct options - // @ts-expect-error fixme ts strict error Object.assign(globalDefs, defs) - const nodes = app.graph.extra?.groupNodes + const nodes = app.rootGraph.extra?.groupNodes as + | Record + | undefined if (nodes) { - await GroupNodeConfig.registerFromWorkflow(nodes, {}) + await GroupNodeConfig.registerFromWorkflow(nodes, []) } } } diff --git a/src/extensions/core/groupNodeManage.css b/src/extensions/core/groupNodeManage.css index b72e5606b..932167ab3 100644 --- a/src/extensions/core/groupNodeManage.css +++ b/src/extensions/core/groupNodeManage.css @@ -2,7 +2,6 @@ background: var(--bg-color); color: var(--fg-color); padding: 0; - font-family: Arial, Helvetica, sans-serif; border-color: black; margin: 20vh auto; max-height: 60vh; diff --git a/src/extensions/core/groupNodeManage.ts b/src/extensions/core/groupNodeManage.ts index 8e52ffccc..38bddb129 100644 --- a/src/extensions/core/groupNodeManage.ts +++ b/src/extensions/core/groupNodeManage.ts @@ -1,9 +1,11 @@ import { PREFIX, SEPARATOR } from '@/constants/groupNodeConstants' -import { - type LGraphNode, - type LGraphNodeConstructor, - LiteGraph +import type { + GroupNodeConfigEntry, + GroupNodeWorkflowData, + LGraphNode, + LGraphNodeConstructor } from '@/lib/litegraph/src/litegraph' +import { LiteGraph } from '@/lib/litegraph/src/litegraph' import { useToastStore } from '@/platform/updates/common/toastStore' import { type ComfyApp, app } from '../../scripts/app' @@ -15,18 +17,20 @@ import './groupNodeManage.css' const ORDER: symbol = Symbol() -// @ts-expect-error fixme ts strict error -function merge(target, source) { - if (typeof target === 'object' && typeof source === 'object') { - for (const key in source) { - const sv = source[key] - if (typeof sv === 'object') { - let tv = target[key] - if (!tv) tv = target[key] = {} - merge(tv, source[key]) - } else { - target[key] = sv +function merge( + target: Record, + source: Record +): Record { + for (const key in source) { + const sv = source[key] + if (typeof sv === 'object' && sv !== null) { + let tv = target[key] as Record | undefined + if (!tv) { + tv = target[key] = {} } + merge(tv, sv as Record) + } else { + target[key] = sv } } @@ -34,8 +38,7 @@ function merge(target, source) { } export class ManageGroupDialog extends ComfyDialog { - // @ts-expect-error fixme ts strict error - tabs: Record< + tabs!: Record< 'Inputs' | 'Outputs' | 'Widgets', { tab: HTMLAnchorElement; page: HTMLElement } > @@ -52,31 +55,26 @@ export class ManageGroupDialog extends ComfyDialog { > > > = {} - // @ts-expect-error fixme ts strict error - nodeItems: any[] + nodeItems!: HTMLLIElement[] app: ComfyApp - // @ts-expect-error fixme ts strict error - groupNodeType: LGraphNodeConstructor - groupNodeDef: any - groupData: any + groupNodeType!: LGraphNodeConstructor + groupData!: GroupNodeConfig - // @ts-expect-error fixme ts strict error - innerNodesList: HTMLUListElement - // @ts-expect-error fixme ts strict error - widgetsPage: HTMLElement - // @ts-expect-error fixme ts strict error - inputsPage: HTMLElement - // @ts-expect-error fixme ts strict error - outputsPage: HTMLElement - draggable: any + innerNodesList!: HTMLUListElement + widgetsPage!: HTMLElement + inputsPage!: HTMLElement + outputsPage!: HTMLElement + draggable: DraggableList | undefined - get selectedNodeInnerIndex() { - // @ts-expect-error fixme ts strict error - return +this.nodeItems[this.selectedNodeIndex].dataset.nodeindex + get selectedNodeInnerIndex(): number { + const index = this.selectedNodeIndex + if (index == null) throw new Error('No node selected') + const item = this.nodeItems[index] + if (!item?.dataset.nodeindex) throw new Error('Invalid node item') + return +item.dataset.nodeindex } - // @ts-expect-error fixme ts strict error - constructor(app) { + constructor(app: ComfyApp) { super() this.app = app this.element = $el('dialog.comfy-group-manage', { @@ -84,19 +82,15 @@ export class ManageGroupDialog extends ComfyDialog { }) as HTMLDialogElement } - // @ts-expect-error fixme ts strict error - changeTab(tab) { + changeTab(tab: keyof ManageGroupDialog['tabs']): void { this.tabs[this.selectedTab].tab.classList.remove('active') this.tabs[this.selectedTab].page.classList.remove('active') - // @ts-expect-error fixme ts strict error this.tabs[tab].tab.classList.add('active') - // @ts-expect-error fixme ts strict error this.tabs[tab].page.classList.add('active') this.selectedTab = tab } - // @ts-expect-error fixme ts strict error - changeNode(index, force?) { + changeNode(index: number, force?: boolean): void { if (!force && this.selectedNodeIndex === index) return if (this.selectedNodeIndex != null) { @@ -122,43 +116,41 @@ export class ManageGroupDialog extends ComfyDialog { this.groupNodeType = LiteGraph.registered_node_types[ `${PREFIX}${SEPARATOR}` + this.selectedGroup ] as unknown as LGraphNodeConstructor - this.groupNodeDef = this.groupNodeType.nodeData - this.groupData = GroupNodeHandler.getGroupData(this.groupNodeType) + this.groupData = GroupNodeHandler.getGroupData(this.groupNodeType)! } - // @ts-expect-error fixme ts strict error - changeGroup(group, reset = true) { + changeGroup(group: string, reset = true): void { this.selectedGroup = group this.getGroupData() const nodes = this.groupData.nodeData.nodes - // @ts-expect-error fixme ts strict error - this.nodeItems = nodes.map((n, i) => - $el( - 'li.draggable-item', - { - dataset: { - nodeindex: n.index + '' - }, - onclick: () => { - this.changeNode(i) - } - }, - [ - $el('span.drag-handle'), - $el( - 'div', - { - textContent: n.title ?? n.type + this.nodeItems = nodes.map( + (n, i) => + $el( + 'li.draggable-item', + { + dataset: { + nodeindex: n.index + '' }, - n.title - ? $el('span', { - textContent: n.type - }) - : [] - ) - ] - ) + onclick: () => { + this.changeNode(i) + } + }, + [ + $el('span.drag-handle'), + $el( + 'div', + { + textContent: n.title ?? n.type + }, + n.title + ? $el('span', { + textContent: n.type + }) + : [] + ) + ] + ) as HTMLLIElement ) this.innerNodesList.replaceChildren(...this.nodeItems) @@ -167,47 +159,46 @@ export class ManageGroupDialog extends ComfyDialog { this.selectedNodeIndex = null this.changeNode(0) } else { - const items = this.draggable.getAllItems() - // @ts-expect-error fixme ts strict error - let index = items.findIndex((item) => item.classList.contains('selected')) - if (index === -1) index = this.selectedNodeIndex + const items = this.draggable!.getAllItems() + let index = items.findIndex((item: Element) => + item.classList.contains('selected') + ) + if (index === -1) index = this.selectedNodeIndex! this.changeNode(index, true) } const ordered = [...nodes] this.draggable?.dispose() this.draggable = new DraggableList(this.innerNodesList, 'li') - this.draggable.addEventListener( - 'dragend', - // @ts-expect-error fixme ts strict error - ({ detail: { oldPosition, newPosition } }) => { - if (oldPosition === newPosition) return - ordered.splice(newPosition, 0, ordered.splice(oldPosition, 1)[0]) - for (let i = 0; i < ordered.length; i++) { - this.storeModification({ - nodeIndex: ordered[i].index, - section: ORDER, - prop: 'order', - value: i - }) - } + this.draggable.addEventListener('dragend', (e: Event) => { + const { oldPosition, newPosition } = (e as CustomEvent).detail + if (oldPosition === newPosition) return + ordered.splice(newPosition, 0, ordered.splice(oldPosition, 1)[0]) + for (let i = 0; i < ordered.length; i++) { + this.storeModification({ + nodeIndex: ordered[i].index, + section: ORDER, + prop: 'order', + value: i + }) } - ) + }) } storeModification(props: { nodeIndex?: number - section: symbol + section: string | symbol prop: string - value: any + value: unknown }) { const { nodeIndex, section, prop, value } = props - // @ts-expect-error fixme ts strict error - const groupMod = (this.modifications[this.selectedGroup] ??= {}) - const nodesMod = (groupMod.nodes ??= {}) + const groupKey = this.selectedGroup! + const groupMod = (this.modifications[groupKey] ??= {}) + const nodesMod = ((groupMod as Record).nodes ??= + {}) as Record>> const nodeMod = (nodesMod[nodeIndex ?? this.selectedNodeInnerIndex] ??= {}) const typeMod = (nodeMod[section] ??= {}) - if (typeof value === 'object') { + if (typeof value === 'object' && value !== null) { const objMod = (typeMod[prop] ??= {}) Object.assign(objMod, value) } else { @@ -215,35 +206,45 @@ export class ManageGroupDialog extends ComfyDialog { } } - // @ts-expect-error fixme ts strict error - getEditElement(section, prop, value, placeholder, checked, checkable = true) { - if (value === placeholder) value = '' + getEditElement( + section: string, + prop: string | number, + value: unknown, + placeholder: string, + checked: boolean, + checkable = true + ): HTMLDivElement { + let displayValue = value === placeholder ? '' : value - const mods = - // @ts-expect-error fixme ts strict error - this.modifications[this.selectedGroup]?.nodes?.[ - this.selectedNodeInnerIndex - ]?.[section]?.[prop] - if (mods) { - if (mods.name != null) { - value = mods.name + const groupKey = this.selectedGroup! + const mods = ( + this.modifications[groupKey] as Record | undefined + )?.nodes as + | Record< + number, + Record> + > + | undefined + const modEntry = mods?.[this.selectedNodeInnerIndex]?.[section]?.[prop] + if (modEntry) { + if (modEntry.name != null) { + displayValue = modEntry.name } - if (mods.visible != null) { - checked = mods.visible + if (modEntry.visible != null) { + checked = modEntry.visible } } return $el('div', [ $el('input', { - value, + value: displayValue as string, placeholder, type: 'text', - // @ts-expect-error fixme ts strict error - onchange: (e) => { + onchange: (e: Event) => { this.storeModification({ section, - prop, - value: { name: e.target.value } + prop: String(prop), + value: { name: (e.target as HTMLInputElement).value } }) } }), @@ -252,25 +253,23 @@ export class ManageGroupDialog extends ComfyDialog { type: 'checkbox', checked, disabled: !checkable, - // @ts-expect-error fixme ts strict error - onchange: (e) => { + onchange: (e: Event) => { this.storeModification({ section, - prop, - value: { visible: !!e.target.checked } + prop: String(prop), + value: { visible: !!(e.target as HTMLInputElement).checked } }) } }) ]) - ]) + ]) as HTMLDivElement } buildWidgetsPage() { const widgets = this.groupData.oldToNewWidgetMap[this.selectedNodeInnerIndex] const items = Object.keys(widgets ?? {}) - // @ts-expect-error fixme ts strict error - const type = app.graph.extra.groupNodes[this.selectedGroup] + const type = app.rootGraph.extra.groupNodes![this.selectedGroup!]! const config = type.config?.[this.selectedNodeInnerIndex]?.input this.widgetsPage.replaceChildren( ...items.map((oldName) => { @@ -289,28 +288,25 @@ export class ManageGroupDialog extends ComfyDialog { buildInputsPage() { const inputs = this.groupData.nodeInputs[this.selectedNodeInnerIndex] const items = Object.keys(inputs ?? {}) - // @ts-expect-error fixme ts strict error - const type = app.graph.extra.groupNodes[this.selectedGroup] + const type = app.rootGraph.extra.groupNodes![this.selectedGroup!]! const config = type.config?.[this.selectedNodeInnerIndex]?.input - this.inputsPage.replaceChildren( - // @ts-expect-error fixme ts strict error - ...items - .map((oldName) => { - let value = inputs[oldName] - if (!value) { - return - } + const elements = items + .map((oldName) => { + const value = inputs[oldName] + if (!value) { + return null + } - return this.getEditElement( - 'input', - oldName, - value, - oldName, - config?.[oldName]?.visible !== false - ) - }) - .filter(Boolean) - ) + return this.getEditElement( + 'input', + oldName, + value, + oldName, + config?.[oldName]?.visible !== false + ) + }) + .filter((el): el is HTMLDivElement => el !== null) + this.inputsPage.replaceChildren(...elements) return !!items.length } @@ -323,39 +319,36 @@ export class ManageGroupDialog extends ComfyDialog { const groupOutputs = this.groupData.oldToNewOutputMap[this.selectedNodeInnerIndex] - // @ts-expect-error fixme ts strict error - const type = app.graph.extra.groupNodes[this.selectedGroup] + const type = app.rootGraph.extra.groupNodes![this.selectedGroup!]! const config = type.config?.[this.selectedNodeInnerIndex]?.output const node = this.groupData.nodeData.nodes[this.selectedNodeInnerIndex] const checkable = node.type !== 'PrimitiveNode' - this.outputsPage.replaceChildren( - ...outputs - // @ts-expect-error fixme ts strict error - .map((type, slot) => { - const groupOutputIndex = groupOutputs?.[slot] - const oldName = innerNodeDef.output_name?.[slot] ?? type - let value = config?.[slot]?.name - const visible = config?.[slot]?.visible || groupOutputIndex != null - if (!value || value === oldName) { - value = '' - } - return this.getEditElement( - 'output', - slot, - value, - oldName, - visible, - checkable - ) - }) - .filter(Boolean) - ) + const elements = outputs.map((outputType: unknown, slot: number) => { + const groupOutputIndex = groupOutputs?.[slot] + const oldName = innerNodeDef?.output_name?.[slot] ?? String(outputType) + let value = config?.[slot]?.name + const visible = config?.[slot]?.visible || groupOutputIndex != null + if (!value || value === oldName) { + value = '' + } + return this.getEditElement( + 'output', + slot, + value, + oldName, + visible, + checkable + ) + }) + this.outputsPage.replaceChildren(...elements) return !!outputs.length } - // @ts-expect-error fixme ts strict error - show(type?) { - const groupNodes = Object.keys(app.graph.extra?.groupNodes ?? {}).sort( + override show(groupNodeType?: string | HTMLElement | HTMLElement[]): void { + // Extract string type - this method repurposes the show signature + const nodeType = + typeof groupNodeType === 'string' ? groupNodeType : undefined + const groupNodes = Object.keys(app.rootGraph.extra?.groupNodes ?? {}).sort( (a, b) => a.localeCompare(b) ) @@ -371,24 +364,27 @@ export class ManageGroupDialog extends ComfyDialog { this.outputsPage ]) - this.tabs = [ + type TabName = 'Inputs' | 'Widgets' | 'Outputs' + const tabEntries: [TabName, HTMLElement][] = [ ['Inputs', this.inputsPage], ['Widgets', this.widgetsPage], ['Outputs', this.outputsPage] - // @ts-expect-error fixme ts strict error - ].reduce((p, [name, page]: [string, HTMLElement]) => { - // @ts-expect-error fixme ts strict error - p[name] = { - tab: $el('a', { - onclick: () => { - this.changeTab(name) - }, - textContent: name - }), - page - } - return p - }, {}) as any + ] + this.tabs = tabEntries.reduce( + (p, [name, page]) => { + p[name] = { + tab: $el('a', { + onclick: () => { + this.changeTab(name) + }, + textContent: name + }) as HTMLAnchorElement, + page + } + return p + }, + {} as ManageGroupDialog['tabs'] + ) const outer = $el('div.comfy-group-manage-outer', [ $el('header', [ @@ -396,15 +392,14 @@ export class ManageGroupDialog extends ComfyDialog { $el( 'select', { - // @ts-expect-error fixme ts strict error - onchange: (e) => { - this.changeGroup(e.target.value) + onchange: (e: Event) => { + this.changeGroup((e.target as HTMLSelectElement).value) } }, groupNodes.map((g) => $el('option', { textContent: g, - selected: `${PREFIX}${SEPARATOR}${g}` === type, + selected: `${PREFIX}${SEPARATOR}${g}` === nodeType, value: g }) ) @@ -425,7 +420,7 @@ export class ManageGroupDialog extends ComfyDialog { 'button.comfy-btn', { onclick: () => { - const node = app.graph.nodes.find( + const node = app.rootGraph.nodes.find( (n) => n.type === `${PREFIX}${SEPARATOR}` + this.selectedGroup ) if (node) { @@ -439,8 +434,7 @@ export class ManageGroupDialog extends ComfyDialog { `Are you sure you want to remove the node: "${this.selectedGroup}"` ) ) { - // @ts-expect-error fixme ts strict error - delete app.graph.extra.groupNodes[this.selectedGroup] + delete app.rootGraph.extra.groupNodes![this.selectedGroup!] LiteGraph.unregisterNodeType( `${PREFIX}${SEPARATOR}` + this.selectedGroup ) @@ -454,90 +448,106 @@ export class ManageGroupDialog extends ComfyDialog { 'button.comfy-btn', { onclick: async () => { - let nodesByType - let recreateNodes = [] - const types = {} + type NodesByType = Record + let nodesByType: NodesByType | undefined + const recreateNodes: LGraphNode[] = [] + const types: Record = {} for (const g in this.modifications) { - // @ts-expect-error fixme ts strict error - const type = app.graph.extra.groupNodes[g] - let config = (type.config ??= {}) + const groupNodeData = app.rootGraph.extra.groupNodes![g]! + let config = (groupNodeData.config ??= {}) - let nodeMods = this.modifications[g]?.nodes + type NodeMods = Record< + string, + Record> + > + let nodeMods = this.modifications[g]?.nodes as + | NodeMods + | undefined if (nodeMods) { const keys = Object.keys(nodeMods) - // @ts-expect-error fixme ts strict error - if (nodeMods[keys[0]][ORDER]) { + if (nodeMods[keys[0]]?.[ORDER]) { // If any node is reordered, they will all need sequencing - const orderedNodes = [] - const orderedMods = {} - const orderedConfig = {} + const orderedNodes: GroupNodeWorkflowData['nodes'] = [] + const orderedMods: NodeMods = {} + const orderedConfig: Record = + {} for (const n of keys) { - // @ts-expect-error fixme ts strict error - const order = nodeMods[n][ORDER].order - orderedNodes[order] = type.nodes[+n] - // @ts-expect-error fixme ts strict error + const order = (nodeMods[n][ORDER] as { order: number }) + .order + orderedNodes[order] = groupNodeData.nodes[+n] orderedMods[order] = nodeMods[n] orderedNodes[order].index = order } // Rewrite links - for (const l of type.links) { - if (l[0] != null) l[0] = type.nodes[l[0]].index - if (l[2] != null) l[2] = type.nodes[l[2]].index + const nodesLen = groupNodeData.nodes.length + for (const l of groupNodeData.links) { + const srcIdx = l[0] as number + const dstIdx = l[2] as number + if (srcIdx != null && srcIdx < nodesLen) + l[0] = groupNodeData.nodes[srcIdx].index! + if (dstIdx != null && dstIdx < nodesLen) + l[2] = groupNodeData.nodes[dstIdx].index! } // Rewrite externals - if (type.external) { - for (const ext of type.external) { - ext[0] = type.nodes[ext[0]] + if (groupNodeData.external) { + for (const ext of groupNodeData.external) { + const extIdx = ext[0] as number + if (extIdx != null && extIdx < nodesLen) { + ext[0] = groupNodeData.nodes[extIdx].index! + } } } // Rewrite modifications for (const id of keys) { - if (config[id]) { - // @ts-expect-error fixme ts strict error - orderedConfig[type.nodes[id].index] = config[id] + if (config[+id]) { + orderedConfig[groupNodeData.nodes[+id].index!] = + config[+id] } - delete config[id] + delete config[+id] } - type.nodes = orderedNodes + groupNodeData.nodes = orderedNodes nodeMods = orderedMods - type.config = config = orderedConfig + groupNodeData.config = config = orderedConfig } - merge(config, nodeMods) + merge( + config as Record, + nodeMods as Record + ) } - // @ts-expect-error fixme ts strict error - types[g] = type + types[g] = groupNodeData if (!nodesByType) { - nodesByType = app.graph.nodes.reduce((p, n) => { - // @ts-expect-error fixme ts strict error - p[n.type] ??= [] - // @ts-expect-error fixme ts strict error - p[n.type].push(n) - return p - }, {}) + nodesByType = app.rootGraph.nodes.reduce( + (p, n) => { + const nodeType = n.type ?? '' + p[nodeType] ??= [] + p[nodeType].push(n) + return p + }, + {} + ) } - // @ts-expect-error fixme ts strict error - const nodes = nodesByType[`${PREFIX}${SEPARATOR}` + g] - if (nodes) recreateNodes.push(...nodes) + const groupTypeNodes = nodesByType[`${PREFIX}${SEPARATOR}` + g] + if (groupTypeNodes) recreateNodes.push(...groupTypeNodes) } - await GroupNodeConfig.registerFromWorkflow(types, {}) + await GroupNodeConfig.registerFromWorkflow(types, []) for (const node of recreateNodes) { - node.recreate() + node.recreate?.() } this.modifications = {} - this.app.graph.setDirtyCanvas(true, true) - this.changeGroup(this.selectedGroup, false) + this.app.canvas.setDirty(true, true) + this.changeGroup(this.selectedGroup!, false) } }, 'Save' @@ -552,8 +562,8 @@ export class ManageGroupDialog extends ComfyDialog { this.element.replaceChildren(outer) this.changeGroup( - type - ? (groupNodes.find((g) => `${PREFIX}${SEPARATOR}${g}` === type) ?? + nodeType + ? (groupNodes.find((g) => `${PREFIX}${SEPARATOR}${g}` === nodeType) ?? groupNodes[0]) : groupNodes[0] ) diff --git a/src/extensions/core/imageCompare.ts b/src/extensions/core/imageCompare.ts new file mode 100644 index 000000000..4821ec062 --- /dev/null +++ b/src/extensions/core/imageCompare.ts @@ -0,0 +1,48 @@ +import type { NodeOutputWith } from '@/schemas/apiSchema' +import { api } from '@/scripts/api' +import { app } from '@/scripts/app' +import { useExtensionService } from '@/services/extensionService' + +type ImageCompareOutput = NodeOutputWith<{ + a_images?: Record[] + b_images?: Record[] +}> + +useExtensionService().registerExtension({ + name: 'Comfy.ImageCompare', + + async nodeCreated(node) { + if (node.constructor.comfyClass !== 'ImageCompare') return + + const [oldWidth, oldHeight] = node.size + node.setSize([Math.max(oldWidth, 400), Math.max(oldHeight, 350)]) + + const onExecuted = node.onExecuted + + node.onExecuted = function (output: ImageCompareOutput) { + onExecuted?.call(this, output) + + const { a_images: aImages, b_images: bImages } = output + const rand = app.getRandParam() + + const beforeUrl = + aImages && aImages.length > 0 + ? api.apiURL(`/view?${new URLSearchParams(aImages[0])}${rand}`) + : '' + const afterUrl = + bImages && bImages.length > 0 + ? api.apiURL(`/view?${new URLSearchParams(bImages[0])}${rand}`) + : '' + + const widget = node.widgets?.find((w) => w.type === 'imagecompare') + + if (widget) { + widget.value = { + before: beforeUrl, + after: afterUrl + } + widget.callback?.(widget.value) + } + } + } +}) diff --git a/src/extensions/core/imageCrop.ts b/src/extensions/core/imageCrop.ts new file mode 100644 index 000000000..38cf6692a --- /dev/null +++ b/src/extensions/core/imageCrop.ts @@ -0,0 +1,12 @@ +import { useExtensionService } from '@/services/extensionService' + +useExtensionService().registerExtension({ + name: 'Comfy.ImageCrop', + + async nodeCreated(node) { + if (node.constructor.comfyClass !== 'ImageCrop') return + + const [oldWidth, oldHeight] = node.size + node.setSize([Math.max(oldWidth, 300), Math.max(oldHeight, 450)]) + } +}) diff --git a/src/extensions/core/index.ts b/src/extensions/core/index.ts index 4171dce89..367f7eb92 100644 --- a/src/extensions/core/index.ts +++ b/src/extensions/core/index.ts @@ -1,16 +1,18 @@ -import { isCloud } from '@/platform/distribution/types' +import { isCloud, isNightly } from '@/platform/distribution/types' import './clipspace' import './contextMenuFilter' +import './customCombo' import './dynamicPrompts' import './editAttention' import './electronAdapter' import './groupNode' import './groupNodeManage' import './groupOptions' +import './imageCompare' +import './imageCrop' import './load3d' import './maskeditor' -import './matchType' import './nodeTemplates' import './noteNode' import './previewAny' @@ -30,9 +32,18 @@ if (isCloud) { await import('./cloudRemoteConfig') await import('./cloudBadges') await import('./cloudSessionCookie') - await import('./cloudFeedbackTopbarButton') if (window.__CONFIG__?.subscription_required) { await import('./cloudSubscription') } } + +// Feedback button for cloud and nightly builds +if (isCloud || isNightly) { + await import('./cloudFeedbackTopbarButton') +} + +// Nightly-only extensions +if (isNightly && !isCloud) { + await import('./nightlyBadges') +} diff --git a/src/extensions/core/load3d.ts b/src/extensions/core/load3d.ts index c142cb272..455776744 100644 --- a/src/extensions/core/load3d.ts +++ b/src/extensions/core/load3d.ts @@ -4,6 +4,10 @@ import Load3D from '@/components/load3d/Load3D.vue' import Load3DViewerContent from '@/components/load3d/Load3dViewerContent.vue' import { nodeToLoad3dMap, useLoad3d } from '@/composables/useLoad3d' import { createExportMenuItems } from '@/extensions/core/load3d/exportMenuHelper' +import type { + CameraConfig, + CameraState +} from '@/extensions/core/load3d/interfaces' import Load3DConfiguration from '@/extensions/core/load3d/Load3DConfiguration' import Load3dUtils from '@/extensions/core/load3d/Load3dUtils' import { t } from '@/i18n' @@ -11,7 +15,12 @@ import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' import type { IContextMenuValue } from '@/lib/litegraph/src/interfaces' import type { IStringWidget } from '@/lib/litegraph/src/types/widgets' import { useToastStore } from '@/platform/updates/common/toastStore' -import { type CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2' +import type { NodeOutputWith } from '@/schemas/apiSchema' + +type Load3dPreviewOutput = NodeOutputWith<{ + result?: [string?, CameraState?, string?] +}> +import type { CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2' import { api } from '@/scripts/api' import { ComfyApp, app } from '@/scripts/app' import { ComponentWidgetImpl, addWidget } from '@/scripts/domWidget' @@ -32,12 +41,12 @@ const inputSpecPreview3D: CustomInputSpec = { isPreview: true } -async function handleModelUpload(files: FileList, node: any) { +async function handleModelUpload(files: FileList, node: LGraphNode) { if (!files?.length) return - const modelWidget = node.widgets?.find( - (w: any) => w.name === 'model_file' - ) as IStringWidget + const modelWidget = node.widgets?.find((w) => w.name === 'model_file') as + | IStringWidget + | undefined try { const resourceFolder = (node.properties['Resource Folder'] as string) || '' @@ -81,7 +90,7 @@ async function handleModelUpload(files: FileList, node: any) { } } -async function handleResourcesUpload(files: FileList, node: any) { +async function handleResourcesUpload(files: FileList, node: LGraphNode) { if (!files?.length) return try { @@ -198,6 +207,17 @@ useExtensionService().registerExtension({ type: 'boolean', defaultValue: false, experimental: true + }, + { + id: 'Comfy.Load3D.PLYEngine', + category: ['3D', 'PLY', 'PLY Engine'], + name: 'PLY Engine', + tooltip: + 'Select the engine for loading PLY files. "threejs" uses the native Three.js PLYLoader (best for mesh PLY files). "fastply" uses an optimized loader for ASCII point cloud PLY files. "sparkjs" uses Spark.js for 3D Gaussian Splatting PLY files.', + type: 'combo', + options: ['threejs', 'fastply', 'sparkjs'], + defaultValue: 'threejs', + experimental: true } ], commands: [ @@ -238,7 +258,10 @@ useExtensionService().registerExtension({ getCustomWidgets() { return { LOAD_3D(node) { - const fileInput = createFileInput('.gltf,.glb,.obj,.fbx,.stl', false) + const fileInput = createFileInput( + '.gltf,.glb,.obj,.fbx,.stl,.ply,.spz,.splat,.ksplat', + false + ) node.properties['Resource Folder'] = '' @@ -301,6 +324,8 @@ useExtensionService().registerExtension({ const load3d = useLoad3dService().getLoad3d(node) if (!load3d) return [] + if (load3d.isSplatModel()) return [] + return createExportMenuItems(load3d) }, @@ -314,7 +339,9 @@ useExtensionService().registerExtension({ await nextTick() useLoad3d(node).waitForLoad3d((load3d) => { - const cameraConfig = node.properties['Camera Config'] as any + const cameraConfig = node.properties['Camera Config'] as + | CameraConfig + | undefined const cameraState = cameraConfig?.state const config = new Load3DConfiguration(load3d, node.properties) @@ -341,7 +368,9 @@ useExtensionService().registerExtension({ return null } - const cameraConfig = (node.properties['Camera Config'] as any) || { + const cameraConfig: CameraConfig = (node.properties[ + 'Camera Config' + ] as CameraConfig | undefined) || { cameraType: currentLoad3d.getCurrentCameraType(), fov: currentLoad3d.cameraManager.perspectiveCamera.fov } @@ -372,7 +401,8 @@ useExtensionService().registerExtension({ mask: `threed/${dataMask.name} [temp]`, normal: `threed/${dataNormal.name} [temp]`, camera_info: - (node.properties['Camera Config'] as any)?.state || null, + (node.properties['Camera Config'] as CameraConfig | undefined) + ?.state || null, recording: '' } @@ -409,6 +439,8 @@ useExtensionService().registerExtension({ const load3d = useLoad3dService().getLoad3d(node) if (!load3d) return [] + if (load3d.isSplatModel()) return [] + return createExportMenuItems(load3d) }, @@ -454,7 +486,9 @@ useExtensionService().registerExtension({ if (lastTimeModelFile) { modelWidget.value = lastTimeModelFile - const cameraConfig = node.properties['Camera Config'] as any + const cameraConfig = node.properties['Camera Config'] as + | CameraConfig + | undefined const cameraState = cameraConfig?.state const settings = { @@ -466,10 +500,11 @@ useExtensionService().registerExtension({ config.configure(settings) } - node.onExecuted = function (message: any) { - onExecuted?.apply(this, arguments as any) + node.onExecuted = function (output: Load3dPreviewOutput) { + onExecuted?.call(this, output) - let filePath = message.result[0] + const result = output.result + const filePath = result?.[0] if (!filePath) { const msg = t('toastMessages.unableToGetModelFilePath') @@ -477,10 +512,10 @@ useExtensionService().registerExtension({ useToastStore().addAlert(msg) } - let cameraState = message.result[1] - let bgImagePath = message.result[2] + const cameraState = result?.[1] + const bgImagePath = result?.[2] - modelWidget.value = filePath.replaceAll('\\', '/') + modelWidget.value = filePath?.replaceAll('\\', '/') node.properties['Last Time Model File'] = modelWidget.value diff --git a/src/extensions/core/load3d/AnimationManager.ts b/src/extensions/core/load3d/AnimationManager.ts index a451da8cd..c6dc3428b 100644 --- a/src/extensions/core/load3d/AnimationManager.ts +++ b/src/extensions/core/load3d/AnimationManager.ts @@ -1,9 +1,10 @@ import * as THREE from 'three' +import type { GLTF } from 'three/examples/jsm/loaders/GLTFLoader' -import { - type AnimationItem, - type AnimationManagerInterface, - type EventManagerInterface +import type { + AnimationItem, + AnimationManagerInterface, + EventManagerInterface } from '@/extensions/core/load3d/interfaces' export class AnimationManager implements AnimationManagerInterface { @@ -38,7 +39,10 @@ export class AnimationManager implements AnimationManagerInterface { this.eventManager.emitEvent('animationListChange', []) } - setupModelAnimations(model: THREE.Object3D, originalModel: any): void { + setupModelAnimations( + model: THREE.Object3D, + originalModel: THREE.Object3D | THREE.BufferGeometry | GLTF | null + ): void { if (this.currentAnimation) { this.currentAnimation.stopAllAction() this.animationActions = [] @@ -125,6 +129,13 @@ export class AnimationManager implements AnimationManagerInterface { } this.animationActions = [action] + + // Emit initial progress to set duration + this.eventManager.emitEvent('animationProgressChange', { + progress: 0, + currentTime: 0, + duration: clip.duration + }) } toggleAnimation(play?: boolean): void { @@ -150,8 +161,58 @@ export class AnimationManager implements AnimationManagerInterface { update(delta: number): void { if (this.currentAnimation && this.isAnimationPlaying) { this.currentAnimation.update(delta) + + if (this.animationActions.length > 0) { + const action = this.animationActions[0] + const clip = action.getClip() + const progress = (action.time / clip.duration) * 100 + this.eventManager.emitEvent('animationProgressChange', { + progress, + currentTime: action.time, + duration: clip.duration + }) + } } } + getAnimationTime(): number { + if (this.animationActions.length === 0) return 0 + return this.animationActions[0].time + } + + getAnimationDuration(): number { + if (this.animationActions.length === 0) return 0 + return this.animationActions[0].getClip().duration + } + + setAnimationTime(time: number): void { + if (this.animationActions.length === 0) return + const duration = this.getAnimationDuration() + const clampedTime = Math.max(0, Math.min(time, duration)) + + // Temporarily unpause to allow time update, then restore + const wasPaused = this.animationActions.map((action) => action.paused) + this.animationActions.forEach((action) => { + action.paused = false + action.time = clampedTime + }) + + if (this.currentAnimation) { + this.currentAnimation.setTime(clampedTime) + this.currentAnimation.update(0) + } + + // Restore paused state + this.animationActions.forEach((action, i) => { + action.paused = wasPaused[i] + }) + + this.eventManager.emitEvent('animationProgressChange', { + progress: (clampedTime / duration) * 100, + currentTime: clampedTime, + duration + }) + } + reset(): void {} } diff --git a/src/extensions/core/load3d/CameraManager.ts b/src/extensions/core/load3d/CameraManager.ts index c87d38483..c92190b80 100644 --- a/src/extensions/core/load3d/CameraManager.ts +++ b/src/extensions/core/load3d/CameraManager.ts @@ -13,8 +13,6 @@ export class CameraManager implements CameraManagerInterface { orthographicCamera: THREE.OrthographicCamera activeCamera: THREE.Camera - // @ts-expect-error unused variable - private renderer: THREE.WebGLRenderer private eventManager: EventManagerInterface private controls: OrbitControls | null = null @@ -42,10 +40,9 @@ export class CameraManager implements CameraManagerInterface { } constructor( - renderer: THREE.WebGLRenderer, + _renderer: THREE.WebGLRenderer, eventManager: EventManagerInterface ) { - this.renderer = renderer this.eventManager = eventManager this.perspectiveCamera = new THREE.PerspectiveCamera( diff --git a/src/extensions/core/load3d/EventManager.ts b/src/extensions/core/load3d/EventManager.ts index 64b87eb9b..3a0a747e9 100644 --- a/src/extensions/core/load3d/EventManager.ts +++ b/src/extensions/core/load3d/EventManager.ts @@ -1,16 +1,16 @@ import { type EventCallback, type EventManagerInterface } from './interfaces' export class EventManager implements EventManagerInterface { - private listeners: { [key: string]: EventCallback[] } = {} + private listeners: Record = {} - addEventListener(event: string, callback: EventCallback): void { + addEventListener(event: string, callback: EventCallback): void { if (!this.listeners[event]) { this.listeners[event] = [] } - this.listeners[event].push(callback) + this.listeners[event].push(callback as EventCallback) } - removeEventListener(event: string, callback: EventCallback): void { + removeEventListener(event: string, callback: EventCallback): void { if (this.listeners[event]) { this.listeners[event] = this.listeners[event].filter( (cb) => cb !== callback @@ -18,7 +18,7 @@ export class EventManager implements EventManagerInterface { } } - emitEvent(event: string, data?: any): void { + emitEvent(event: string, data: T): void { if (this.listeners[event]) { this.listeners[event].forEach((callback) => callback(data)) } diff --git a/src/extensions/core/load3d/Load3DConfiguration.ts b/src/extensions/core/load3d/Load3DConfiguration.ts index 0510a9c82..d3e6351fd 100644 --- a/src/extensions/core/load3d/Load3DConfiguration.ts +++ b/src/extensions/core/load3d/Load3DConfiguration.ts @@ -156,8 +156,9 @@ class Load3DConfiguration { return { upDirection: 'original', - materialMode: 'original' - } as ModelConfig + materialMode: 'original', + showSkeleton: false + } } private applySceneConfig(config: SceneConfig, bgImagePath?: string) { diff --git a/src/extensions/core/load3d/Load3d.ts b/src/extensions/core/load3d/Load3d.ts index c759acc1f..60690e4f8 100644 --- a/src/extensions/core/load3d/Load3d.ts +++ b/src/extensions/core/load3d/Load3d.ts @@ -14,6 +14,7 @@ import { ViewHelperManager } from './ViewHelperManager' import { type CameraState, type CaptureResult, + type EventCallback, type Load3DOptions, type MaterialMode, type UpDirection @@ -43,8 +44,8 @@ class Load3d { STATUS_MOUSE_ON_VIEWER: boolean INITIAL_RENDER_DONE: boolean = false - targetWidth: number = 512 - targetHeight: number = 512 + targetWidth: number = 0 + targetHeight: number = 0 targetAspectRatio: number = 1 isViewerMode: boolean = false @@ -72,7 +73,13 @@ class Load3d { this.renderer.setClearColor(0x282828) this.renderer.autoClear = false this.renderer.outputColorSpace = THREE.SRGBColorSpace - this.renderer.domElement.classList.add('flex', '!h-full', '!w-full') + this.renderer.domElement.classList.add( + 'absolute', + 'inset-0', + 'h-full', + 'w-full', + 'outline-none' + ) container.appendChild(this.renderer.domElement) this.eventManager = new EventManager() @@ -386,7 +393,8 @@ class Load3d { this.STATUS_MOUSE_ON_SCENE || this.STATUS_MOUSE_ON_VIEWER || this.isRecording() || - !this.INITIAL_RENDER_DONE + !this.INITIAL_RENDER_DONE || + this.animationManager.isAnimationPlaying ) } @@ -571,6 +579,14 @@ class Load3d { this.loadingPromise = null } + isSplatModel(): boolean { + return this.modelManager.containsSplatMesh() + } + + isPlyModel(): boolean { + return this.modelManager.originalModel instanceof THREE.BufferGeometry + } + clearModel(): void { this.animationManager.dispose() this.modelManager.clearModel() @@ -595,11 +611,11 @@ class Load3d { this.forceRender() } - addEventListener(event: string, callback: (data?: any) => void): void { + addEventListener(event: string, callback: EventCallback): void { this.eventManager.addEventListener(event, callback) } - removeEventListener(event: string, callback: (data?: any) => void): void { + removeEventListener(event: string, callback: EventCallback): void { this.eventManager.removeEventListener(event, callback) } @@ -609,7 +625,7 @@ class Load3d { } handleResize(): void { - const parentElement = this.renderer?.domElement + const parentElement = this.renderer?.domElement?.parentElement if (!parentElement) { console.warn('Parent element not found') @@ -712,6 +728,86 @@ class Load3d { return this.animationManager.animationClips.length > 0 } + public hasSkeleton(): boolean { + return this.modelManager.hasSkeleton() + } + + public setShowSkeleton(show: boolean): void { + this.modelManager.setShowSkeleton(show) + this.forceRender() + } + + public getShowSkeleton(): boolean { + return this.modelManager.showSkeleton + } + + public getAnimationTime(): number { + return this.animationManager.getAnimationTime() + } + + public getAnimationDuration(): number { + return this.animationManager.getAnimationDuration() + } + + public setAnimationTime(time: number): void { + this.animationManager.setAnimationTime(time) + this.forceRender() + } + + public async captureThumbnail( + width: number = 256, + height: number = 256 + ): Promise { + if (!this.modelManager.currentModel) { + throw new Error('No model loaded for thumbnail capture') + } + + const savedState = this.cameraManager.getCameraState() + const savedCameraType = this.cameraManager.getCurrentCameraType() + const savedGridVisible = this.sceneManager.gridHelper.visible + + try { + this.sceneManager.gridHelper.visible = false + + if (savedCameraType !== 'perspective') { + this.cameraManager.toggleCamera('perspective') + } + + const box = new THREE.Box3().setFromObject(this.modelManager.currentModel) + const size = box.getSize(new THREE.Vector3()) + const center = box.getCenter(new THREE.Vector3()) + + const maxDim = Math.max(size.x, size.y, size.z) + const distance = maxDim * 1.5 + + const cameraPosition = new THREE.Vector3( + center.x - distance * 0.8, + center.y + distance * 0.4, + center.z + distance * 0.3 + ) + + this.cameraManager.perspectiveCamera.position.copy(cameraPosition) + this.cameraManager.perspectiveCamera.lookAt(center) + this.cameraManager.perspectiveCamera.updateProjectionMatrix() + + if (this.controlsManager.controls) { + this.controlsManager.controls.target.copy(center) + this.controlsManager.controls.update() + } + + const result = await this.sceneManager.captureScene(width, height) + return result.scene + } finally { + this.sceneManager.gridHelper.visible = savedGridVisible + + if (savedCameraType !== 'perspective') { + this.cameraManager.toggleCamera(savedCameraType) + } + this.cameraManager.setCameraState(savedState) + this.controlsManager.controls?.update() + } + } + public remove(): void { if (this.contextMenuAbortController) { this.contextMenuAbortController.abort() diff --git a/src/extensions/core/load3d/Load3dUtils.ts b/src/extensions/core/load3d/Load3dUtils.ts index 4a63e6b5a..ba7c36e55 100644 --- a/src/extensions/core/load3d/Load3dUtils.ts +++ b/src/extensions/core/load3d/Load3dUtils.ts @@ -1,9 +1,34 @@ +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( + load3d: Load3d, + modelPath: string, + folderType: 'input' | 'output' + ): Promise { + const [subfolder, filename] = this.splitFilePath(modelPath) + const thumbnailFilename = this.getThumbnailFilename(filename) + + const exists = await this.fileExists( + subfolder, + thumbnailFilename, + folderType + ) + if (exists) return + + const imageData = await load3d.captureThumbnail(256, 256) + await this.uploadThumbnail( + imageData, + subfolder, + thumbnailFilename, + folderType + ) + } + static async uploadTempImage( imageData: string, prefix: string, @@ -34,9 +59,26 @@ class Load3dUtils { return await resp.json() } + static readonly MAX_UPLOAD_SIZE_MB = 100 + static async uploadFile(file: File, subfolder: string) { let uploadPath + const fileSizeMB = file.size / 1024 / 1024 + if (fileSizeMB > this.MAX_UPLOAD_SIZE_MB) { + const message = t('toastMessages.fileTooLarge', { + size: fileSizeMB.toFixed(1), + maxSize: this.MAX_UPLOAD_SIZE_MB + }) + console.warn( + '[Load3D] uploadFile: file too large', + fileSizeMB.toFixed(2), + 'MB' + ) + useToastStore().addAlert(message) + return undefined + } + try { const body = new FormData() body.append('image', file) @@ -61,7 +103,7 @@ class Load3dUtils { useToastStore().addAlert(resp.status + ' - ' + resp.statusText) } } catch (error) { - console.error('Upload error:', error) + console.error('[Load3D] uploadFile: exception', error) useToastStore().addAlert( error instanceof Error ? error.message @@ -105,6 +147,46 @@ class Load3dUtils { await Promise.all(uploadPromises) } + + static getThumbnailFilename(modelFilename: string): string { + return `${modelFilename}.png` + } + + static async fileExists( + subfolder: string, + filename: string, + type: string = 'input' + ): Promise { + try { + const url = api.apiURL(this.getResourceURL(subfolder, filename, type)) + const response = await fetch(url, { method: 'HEAD' }) + return response.ok + } catch { + return false + } + } + + static async uploadThumbnail( + imageData: string, + subfolder: string, + filename: string, + type: string = 'input' + ): Promise { + const blob = await fetch(imageData).then((r) => r.blob()) + const file = new File([blob], filename, { type: 'image/png' }) + + const body = new FormData() + body.append('image', file) + body.append('subfolder', subfolder) + body.append('type', type) + + const resp = await api.fetchApi('/upload/image', { + method: 'POST', + body + }) + + return resp.status === 200 + } } export default Load3dUtils diff --git a/src/extensions/core/load3d/LoaderManager.ts b/src/extensions/core/load3d/LoaderManager.ts index 2c753e6fc..3a4db5a3d 100644 --- a/src/extensions/core/load3d/LoaderManager.ts +++ b/src/extensions/core/load3d/LoaderManager.ts @@ -1,28 +1,40 @@ +import { SplatMesh } from '@sparkjsdev/spark' import * as THREE from 'three' import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader' import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader' -import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader' +import { PLYLoader } from 'three/examples/jsm/loaders/PLYLoader' import { STLLoader } from 'three/examples/jsm/loaders/STLLoader' +import { MtlObjBridge, OBJLoader2Parallel } from 'wwobjloader2' +// Use pre-bundled worker module (has all dependencies included) +// The unbundled 'wwobjloader2/worker' has ES imports that fail in production builds +import OBJLoader2WorkerUrl from 'wwobjloader2/bundle/worker/module?url' import { t } from '@/i18n' +import { useSettingStore } from '@/platform/settings/settingStore' import { useToastStore } from '@/platform/updates/common/toastStore' +import { api } from '@/scripts/api' +import { isPLYAsciiFormat } from '@/scripts/metadata/ply' import { type EventManagerInterface, type LoaderManagerInterface, type ModelManagerInterface } from './interfaces' +import { FastPLYLoader } from './loader/FastPLYLoader' export class LoaderManager implements LoaderManagerInterface { gltfLoader: GLTFLoader - objLoader: OBJLoader + objLoader: OBJLoader2Parallel mtlLoader: MTLLoader fbxLoader: FBXLoader stlLoader: STLLoader + plyLoader: PLYLoader + fastPlyLoader: FastPLYLoader private modelManager: ModelManagerInterface private eventManager: EventManagerInterface + private currentLoadId: number = 0 constructor( modelManager: ModelManagerInterface, @@ -32,10 +44,17 @@ export class LoaderManager implements LoaderManagerInterface { this.eventManager = eventManager this.gltfLoader = new GLTFLoader() - this.objLoader = new OBJLoader() + this.objLoader = new OBJLoader2Parallel() + // Set worker URL for Vite compatibility + this.objLoader.setWorkerUrl( + true, + new URL(OBJLoader2WorkerUrl, import.meta.url) + ) this.mtlLoader = new MTLLoader() this.fbxLoader = new FBXLoader() this.stlLoader = new STLLoader() + this.plyLoader = new PLYLoader() + this.fastPlyLoader = new FastPLYLoader() } init(): void {} @@ -43,6 +62,8 @@ export class LoaderManager implements LoaderManagerInterface { dispose(): void {} async loadModel(url: string, originalFileName?: string): Promise { + const loadId = ++this.currentLoadId + try { this.eventManager.emitEvent('modelLoadingStart', null) @@ -72,7 +93,11 @@ export class LoaderManager implements LoaderManagerInterface { return } - let model = await this.loadModelInternal(url, fileExtension) + const model = await this.loadModelInternal(url, fileExtension) + + if (loadId !== this.currentLoadId) { + return + } if (model) { await this.modelManager.setupModel(model) @@ -80,9 +105,11 @@ export class LoaderManager implements LoaderManagerInterface { this.eventManager.emitEvent('modelLoadingEnd', null) } catch (error) { - this.eventManager.emitEvent('modelLoadingEnd', null) - console.error('Error loading model:', error) - useToastStore().addAlert(t('toastMessages.errorLoadingModel')) + if (loadId === this.currentLoadId) { + this.eventManager.emitEvent('modelLoadingEnd', null) + console.error('Error loading model:', error) + useToastStore().addAlert(t('toastMessages.errorLoadingModel')) + } } } @@ -141,6 +168,10 @@ export class LoaderManager implements LoaderManagerInterface { fbxModel.traverse((child) => { if (child instanceof THREE.Mesh) { this.modelManager.originalMaterials.set(child, child.material) + + if (child instanceof THREE.SkinnedMesh) { + child.frustumCulled = false + } } }) break @@ -154,7 +185,9 @@ export class LoaderManager implements LoaderManagerInterface { const materials = await this.mtlLoader.loadAsync(mtlFileName) materials.preload() - this.objLoader.setMaterials(materials) + const materialsFromMtl = + MtlObjBridge.addMaterialsFromMtlLoader(materials) + this.objLoader.setMaterials(materialsFromMtl) } catch (e) { console.log( 'No MTL file found or error loading it, continuing without materials' @@ -162,8 +195,10 @@ export class LoaderManager implements LoaderManagerInterface { } } - this.objLoader.setPath(path) - model = await this.objLoader.loadAsync(filename) + // OBJLoader2Parallel uses Web Worker for parsing (non-blocking) + const objUrl = path + encodeURIComponent(filename) + model = await this.objLoader.loadAsync(objUrl) + model.traverse((child) => { if (child instanceof THREE.Mesh) { this.modelManager.originalMaterials.set(child, child.material) @@ -174,7 +209,6 @@ export class LoaderManager implements LoaderManagerInterface { case 'gltf': case 'glb': this.gltfLoader.setPath(path) - const gltf = await this.gltfLoader.loadAsync(filename) this.modelManager.setOriginalModel(gltf) @@ -184,11 +218,139 @@ export class LoaderManager implements LoaderManagerInterface { if (child instanceof THREE.Mesh) { child.geometry.computeVertexNormals() this.modelManager.originalMaterials.set(child, child.material) + + if (child instanceof THREE.SkinnedMesh) { + child.frustumCulled = false + } } }) break + + case 'ply': + model = await this.loadPLY(path, filename) + break + + case 'spz': + case 'splat': + case 'ksplat': + model = await this.loadSplat(path, filename) + break } return model } + + private async fetchModelData(path: string, filename: string) { + const route = + '/' + path.replace(/^api\//, '') + encodeURIComponent(filename) + const response = await api.fetchApi(route) + if (!response.ok) { + throw new Error(`Failed to fetch model: ${response.status}`) + } + return response.arrayBuffer() + } + + private async loadSplat( + path: string, + filename: string + ): Promise { + const arrayBuffer = await this.fetchModelData(path, filename) + + const splatMesh = new SplatMesh({ fileBytes: arrayBuffer }) + this.modelManager.setOriginalModel(splatMesh) + const splatGroup = new THREE.Group() + splatGroup.add(splatMesh) + return splatGroup + } + + private async loadPLY( + path: string, + filename: string + ): Promise { + const plyEngine = useSettingStore().get('Comfy.Load3D.PLYEngine') as string + + if (plyEngine === 'sparkjs') { + return this.loadSplat(path, filename) + } + + // Use Three.js PLYLoader or FastPLYLoader for point cloud PLY files + const arrayBuffer = await this.fetchModelData(path, filename) + + const isASCII = isPLYAsciiFormat(arrayBuffer) + + let plyGeometry: THREE.BufferGeometry + + if (isASCII && plyEngine === 'fastply') { + plyGeometry = this.fastPlyLoader.parse(arrayBuffer) + } else { + this.plyLoader.setPath(path) + plyGeometry = this.plyLoader.parse(arrayBuffer) + } + + this.modelManager.setOriginalModel(plyGeometry) + plyGeometry.computeVertexNormals() + + const hasVertexColors = plyGeometry.attributes.color !== undefined + const materialMode = this.modelManager.materialMode + + // Use Points rendering for pointCloud mode (better for point clouds) + if (materialMode === 'pointCloud') { + plyGeometry.computeBoundingSphere() + if (plyGeometry.boundingSphere) { + const center = plyGeometry.boundingSphere.center + const radius = plyGeometry.boundingSphere.radius + + plyGeometry.translate(-center.x, -center.y, -center.z) + + if (radius > 0) { + const scale = 1.0 / radius + plyGeometry.scale(scale, scale, scale) + } + } + + const pointMaterial = hasVertexColors + ? new THREE.PointsMaterial({ + size: 0.005, + vertexColors: true, + sizeAttenuation: true + }) + : new THREE.PointsMaterial({ + size: 0.005, + color: 0xcccccc, + sizeAttenuation: true + }) + + const plyPoints = new THREE.Points(plyGeometry, pointMaterial) + this.modelManager.originalMaterials.set( + plyPoints as unknown as THREE.Mesh, + pointMaterial + ) + + const plyGroup = new THREE.Group() + plyGroup.add(plyPoints) + return plyGroup + } + + // Use Mesh rendering for other modes + let plyMaterial: THREE.Material + + if (hasVertexColors) { + plyMaterial = new THREE.MeshStandardMaterial({ + vertexColors: true, + metalness: 0.0, + roughness: 0.5, + side: THREE.DoubleSide + }) + } else { + plyMaterial = this.modelManager.standardMaterial.clone() + plyMaterial.side = THREE.DoubleSide + } + + const plyMesh = new THREE.Mesh(plyGeometry, plyMaterial) + this.modelManager.originalMaterials.set(plyMesh, plyMaterial) + + const plyGroup = new THREE.Group() + plyGroup.add(plyMesh) + return plyGroup + } } diff --git a/src/extensions/core/load3d/SceneManager.ts b/src/extensions/core/load3d/SceneManager.ts index c03a43121..00a3383d6 100644 --- a/src/extensions/core/load3d/SceneManager.ts +++ b/src/extensions/core/load3d/SceneManager.ts @@ -27,13 +27,11 @@ export class SceneManager implements SceneManagerInterface { private renderer: THREE.WebGLRenderer private getActiveCamera: () => THREE.Camera - // @ts-expect-error unused variable - private getControls: () => OrbitControls constructor( renderer: THREE.WebGLRenderer, getActiveCamera: () => THREE.Camera, - getControls: () => OrbitControls, + _getControls: () => OrbitControls, eventManager: EventManagerInterface ) { this.renderer = renderer @@ -41,7 +39,6 @@ export class SceneManager implements SceneManagerInterface { this.scene = new THREE.Scene() this.getActiveCamera = getActiveCamera - this.getControls = getControls this.gridHelper = new THREE.GridHelper(20, 20) this.gridHelper.position.set(0, 0, 0) diff --git a/src/extensions/core/load3d/SceneModelManager.ts b/src/extensions/core/load3d/SceneModelManager.ts index aef82dcd9..a480e4554 100644 --- a/src/extensions/core/load3d/SceneModelManager.ts +++ b/src/extensions/core/load3d/SceneModelManager.ts @@ -1,3 +1,4 @@ +import { SplatMesh } from '@sparkjsdev/spark' import * as THREE from 'three' import { type GLTF } from 'three/examples/jsm/loaders/GLTFLoader' @@ -29,6 +30,8 @@ export class SceneModelManager implements ModelManagerInterface { originalURL: string | null = null appliedTexture: THREE.Texture | null = null textureLoader: THREE.TextureLoader + skeletonHelper: THREE.SkeletonHelper | null = null + showSkeleton: boolean = false private scene: THREE.Scene private renderer: THREE.WebGLRenderer @@ -98,6 +101,145 @@ export class SceneModelManager implements ModelManagerInterface { }) } + private handlePLYModeSwitch(mode: MaterialMode): void { + if (!(this.originalModel instanceof THREE.BufferGeometry)) { + return + } + + const plyGeometry = this.originalModel.clone() + const hasVertexColors = plyGeometry.attributes.color !== undefined + + // Find and remove ALL MainModel instances by name to ensure deletion + const oldMainModels: THREE.Object3D[] = [] + this.scene.traverse((obj) => { + if (obj.name === 'MainModel') { + oldMainModels.push(obj) + } + }) + + // Remove and dispose all found MainModels + oldMainModels.forEach((oldModel) => { + oldModel.traverse((child) => { + if (child instanceof THREE.Mesh || child instanceof THREE.Points) { + child.geometry?.dispose() + if (Array.isArray(child.material)) { + child.material.forEach((m) => m.dispose()) + } else { + child.material?.dispose() + } + } + }) + this.scene.remove(oldModel) + }) + + this.currentModel = null + + let newModel: THREE.Object3D + + if (mode === 'pointCloud') { + // Use Points rendering for point cloud mode + plyGeometry.computeBoundingSphere() + if (plyGeometry.boundingSphere) { + const center = plyGeometry.boundingSphere.center + const radius = plyGeometry.boundingSphere.radius + + plyGeometry.translate(-center.x, -center.y, -center.z) + + if (radius > 0) { + const scale = 1.0 / radius + plyGeometry.scale(scale, scale, scale) + } + } + + const pointMaterial = hasVertexColors + ? new THREE.PointsMaterial({ + size: 0.005, + vertexColors: true, + sizeAttenuation: true + }) + : new THREE.PointsMaterial({ + size: 0.005, + color: 0xcccccc, + sizeAttenuation: true + }) + + const points = new THREE.Points(plyGeometry, pointMaterial) + newModel = new THREE.Group() + newModel.add(points) + } else { + // Use Mesh rendering for other modes + let meshMaterial: THREE.Material = hasVertexColors + ? new THREE.MeshStandardMaterial({ + vertexColors: true, + metalness: 0.0, + roughness: 0.5, + side: THREE.DoubleSide + }) + : this.standardMaterial.clone() + + if ( + !hasVertexColors && + meshMaterial instanceof THREE.MeshStandardMaterial + ) { + meshMaterial.side = THREE.DoubleSide + } + + const mesh = new THREE.Mesh(plyGeometry, meshMaterial) + this.originalMaterials.set(mesh, meshMaterial) + + newModel = new THREE.Group() + newModel.add(mesh) + + // Apply the requested material mode + if (mode === 'normal') { + mesh.material = new THREE.MeshNormalMaterial({ + flatShading: false, + side: THREE.DoubleSide + }) + } else if (mode === 'wireframe') { + mesh.material = new THREE.MeshBasicMaterial({ + color: 0xffffff, + wireframe: true + }) + } + } + + // Double check: remove any remaining MainModel before adding new one + const remainingMainModels: THREE.Object3D[] = [] + this.scene.traverse((obj) => { + if (obj.name === 'MainModel') { + remainingMainModels.push(obj) + } + }) + remainingMainModels.forEach((obj) => this.scene.remove(obj)) + + this.currentModel = newModel + newModel.name = 'MainModel' + + // Setup the new model + if (mode === 'pointCloud') { + this.scene.add(newModel) + } else { + const box = new THREE.Box3().setFromObject(newModel) + const size = box.getSize(new THREE.Vector3()) + const center = box.getCenter(new THREE.Vector3()) + + const maxDim = Math.max(size.x, size.y, size.z) + const targetSize = 5 + const scale = targetSize / maxDim + newModel.scale.multiplyScalar(scale) + + box.setFromObject(newModel) + box.getCenter(center) + box.getSize(size) + + newModel.position.set(-center.x, -box.min.y, -center.z) + this.scene.add(newModel) + } + + this.eventManager.emitEvent('materialModeChange', mode) + } + setMaterialMode(mode: MaterialMode): void { if (!this.currentModel || mode === this.materialMode) { return @@ -105,6 +247,12 @@ export class SceneModelManager implements ModelManagerInterface { this.materialMode = mode + // Handle PLY files specially - they need to be recreated for mode switch + if (this.originalModel instanceof THREE.BufferGeometry) { + this.handlePLYModeSwitch(mode) + return + } + if (mode === 'depth') { this.renderer.outputColorSpace = THREE.LinearSRGBColorSpace } else { @@ -186,6 +334,7 @@ export class SceneModelManager implements ModelManagerInterface { }) break case 'original': + case 'pointCloud': const originalMaterial = this.originalMaterials.get(child) if (originalMaterial) { child.material = originalMaterial @@ -267,17 +416,90 @@ export class SceneModelManager implements ModelManagerInterface { this.appliedTexture = null } + if (this.skeletonHelper) { + this.scene.remove(this.skeletonHelper) + this.skeletonHelper.dispose() + this.skeletonHelper = null + } + this.showSkeleton = false + this.originalMaterials = new WeakMap() } + hasSkeleton(): boolean { + if (!this.currentModel) return false + let found = false + this.currentModel.traverse((child) => { + if (child instanceof THREE.SkinnedMesh && child.skeleton) { + found = true + } + }) + return found + } + + setShowSkeleton(show: boolean): void { + this.showSkeleton = show + + if (show) { + if (!this.skeletonHelper && this.currentModel) { + let rootBone: THREE.Bone | null = null + this.currentModel.traverse((child) => { + if (child instanceof THREE.Bone && !rootBone) { + if (!(child.parent instanceof THREE.Bone)) { + rootBone = child + } + } + }) + + if (rootBone) { + this.skeletonHelper = new THREE.SkeletonHelper(rootBone) + this.scene.add(this.skeletonHelper) + } else { + let skinnedMesh: THREE.SkinnedMesh | null = null + this.currentModel.traverse((child) => { + if (child instanceof THREE.SkinnedMesh && !skinnedMesh) { + skinnedMesh = child + } + }) + + if (skinnedMesh) { + this.skeletonHelper = new THREE.SkeletonHelper(skinnedMesh) + this.scene.add(this.skeletonHelper) + } + } + } else if (this.skeletonHelper) { + this.skeletonHelper.visible = true + } + } else { + if (this.skeletonHelper) { + this.skeletonHelper.visible = false + } + } + + this.eventManager.emitEvent('skeletonVisibilityChange', show) + } + addModelToScene(model: THREE.Object3D): void { this.currentModel = model + model.name = 'MainModel' this.scene.add(this.currentModel) } async setupModel(model: THREE.Object3D): Promise { this.currentModel = model + model.name = 'MainModel' + + // Check if model is or contains a SplatMesh (3D Gaussian Splatting) + const isSplatModel = this.containsSplatMesh(model) + + if (isSplatModel) { + // SplatMesh handles its own rendering, just add to scene + this.scene.add(model) + // Set a default camera distance for splat models + this.setupCamera(new THREE.Vector3(5, 5, 5)) + return + } const box = new THREE.Box3().setFromObject(model) const size = box.getSize(new THREE.Vector3()) @@ -308,6 +530,17 @@ export class SceneModelManager implements ModelManagerInterface { this.setupCamera(size) } + containsSplatMesh(model?: THREE.Object3D | null): boolean { + const target = model ?? this.currentModel + if (!target) return false + if (target instanceof SplatMesh) return true + let found = false + target.traverse((child) => { + if (child instanceof SplatMesh) found = true + }) + return found + } + setOriginalModel(model: THREE.Object3D | THREE.BufferGeometry | GLTF): void { this.originalModel = model } diff --git a/src/extensions/core/load3d/ViewHelperManager.ts b/src/extensions/core/load3d/ViewHelperManager.ts index 380153ed0..f7824f168 100644 --- a/src/extensions/core/load3d/ViewHelperManager.ts +++ b/src/extensions/core/load3d/ViewHelperManager.ts @@ -14,16 +14,13 @@ export class ViewHelperManager implements ViewHelperManagerInterface { private getActiveCamera: () => THREE.Camera private getControls: () => OrbitControls private eventManager: EventManagerInterface - // @ts-expect-error unused variable - private renderer: THREE.WebGLRenderer constructor( - renderer: THREE.WebGLRenderer, + _renderer: THREE.WebGLRenderer, getActiveCamera: () => THREE.Camera, getControls: () => OrbitControls, eventManager: EventManagerInterface ) { - this.renderer = renderer this.getActiveCamera = getActiveCamera this.getControls = getControls this.eventManager = eventManager diff --git a/src/extensions/core/load3d/interfaces.ts b/src/extensions/core/load3d/interfaces.ts index fd2511738..7beb3882a 100644 --- a/src/extensions/core/load3d/interfaces.ts +++ b/src/extensions/core/load3d/interfaces.ts @@ -4,10 +4,15 @@ import { ViewHelper } from 'three/examples/jsm/helpers/ViewHelper' import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader' import { type GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader' -import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader' import { STLLoader } from 'three/examples/jsm/loaders/STLLoader' +import { type OBJLoader2Parallel } from 'wwobjloader2' -export type MaterialMode = 'original' | 'normal' | 'wireframe' | 'depth' +export type MaterialMode = + | 'original' + | 'pointCloud' + | 'normal' + | 'wireframe' + | 'depth' export type UpDirection = 'original' | '-x' | '+x' | '-y' | '+y' | '-z' | '+z' export type CameraType = 'perspective' | 'orthographic' export type BackgroundRenderModeType = 'tiled' | 'panorama' @@ -29,6 +34,7 @@ export interface SceneConfig { export interface ModelConfig { upDirection: UpDirection materialMode: MaterialMode + showSkeleton: boolean } export interface CameraConfig { @@ -41,8 +47,8 @@ export interface LightConfig { intensity: number } -export interface EventCallback { - (data?: any): void +export interface EventCallback { + (data: T): void } export interface Load3DOptions { @@ -122,9 +128,9 @@ export interface ViewHelperManagerInterface extends BaseManager { } export interface EventManagerInterface { - addEventListener(event: string, callback: EventCallback): void - removeEventListener(event: string, callback: EventCallback): void - emitEvent(event: string, data?: any): void + addEventListener(event: string, callback: EventCallback): void + removeEventListener(event: string, callback: EventCallback): void + emitEvent(event: string, data: T): void } export interface AnimationManagerInterface extends BaseManager { @@ -135,12 +141,18 @@ export interface AnimationManagerInterface extends BaseManager { isAnimationPlaying: boolean animationSpeed: number - setupModelAnimations(model: THREE.Object3D, originalModel: any): void + setupModelAnimations( + model: THREE.Object3D, + originalModel: THREE.Object3D | THREE.BufferGeometry | GLTF | null + ): void updateAnimationList(): void setAnimationSpeed(speed: number): void updateSelectedAnimation(index: number): void toggleAnimation(play?: boolean): void update(delta: number): void + getAnimationTime(): number + getAnimationDuration(): number + setAnimationTime(time: number): void } export interface ModelManagerInterface { @@ -171,7 +183,7 @@ export interface ModelManagerInterface { export interface LoaderManagerInterface { gltfLoader: GLTFLoader - objLoader: OBJLoader + objLoader: OBJLoader2Parallel mtlLoader: MTLLoader fbxLoader: FBXLoader stlLoader: STLLoader @@ -186,5 +198,9 @@ export const SUPPORTED_EXTENSIONS = new Set([ '.glb', '.obj', '.fbx', - '.stl' + '.stl', + '.spz', + '.splat', + '.ply', + '.ksplat' ]) diff --git a/src/extensions/core/load3d/loader/FastPLYLoader.ts b/src/extensions/core/load3d/loader/FastPLYLoader.ts new file mode 100644 index 000000000..401ea3e31 --- /dev/null +++ b/src/extensions/core/load3d/loader/FastPLYLoader.ts @@ -0,0 +1,33 @@ +import * as THREE from 'three' + +import { parseASCIIPLY } from '@/scripts/metadata/ply' + +/** + * Fast ASCII PLY Loader + * Optimized for simple ASCII PLY files with position and color data + * 4-5x faster than Three.js PLYLoader for ASCII files + */ +export class FastPLYLoader { + parse(arrayBuffer: ArrayBuffer): THREE.BufferGeometry { + const plyData = parseASCIIPLY(arrayBuffer) + + if (!plyData) { + throw new Error('Failed to parse PLY data') + } + + const geometry = new THREE.BufferGeometry() + geometry.setAttribute( + 'position', + new THREE.BufferAttribute(plyData.positions, 3) + ) + + if (plyData.colors) { + geometry.setAttribute( + 'color', + new THREE.BufferAttribute(plyData.colors, 3) + ) + } + + return geometry + } +} diff --git a/src/extensions/core/maskEditorOld.ts b/src/extensions/core/maskEditorOld.ts deleted file mode 100644 index e34c3f225..000000000 --- a/src/extensions/core/maskEditorOld.ts +++ /dev/null @@ -1,1204 +0,0 @@ -import { api } from '../../scripts/api' -import { app } from '../../scripts/app' -import { ComfyApp } from '../../scripts/app' -import { $el, ComfyDialog } from '../../scripts/ui' -import { ClipspaceDialog } from './clipspace' - -// Helper function to convert a data URL to a Blob object -// @ts-expect-error fixme ts strict error -function dataURLToBlob(dataURL) { - const parts = dataURL.split(';base64,') - const contentType = parts[0].split(':')[1] - const byteString = atob(parts[1]) - const arrayBuffer = new ArrayBuffer(byteString.length) - const uint8Array = new Uint8Array(arrayBuffer) - for (let i = 0; i < byteString.length; i++) { - uint8Array[i] = byteString.charCodeAt(i) - } - return new Blob([arrayBuffer], { type: contentType }) -} - -// @ts-expect-error fixme ts strict error -function loadImage(imagePath) { - return new Promise((resolve) => { - const image = new Image() - - image.onload = function () { - resolve(image) - } - - image.src = imagePath - }) -} - -// @ts-expect-error fixme ts strict error -async function uploadMask(filepath, formData) { - await api - .fetchApi('/upload/mask', { - method: 'POST', - body: formData - }) - .catch((error) => { - console.error('Error:', error) - }) - - // @ts-expect-error fixme ts strict error - ComfyApp.clipspace.imgs[ComfyApp.clipspace['selectedIndex']] = new Image() - // @ts-expect-error fixme ts strict error - ComfyApp.clipspace.imgs[ComfyApp.clipspace['selectedIndex']].src = api.apiURL( - '/view?' + - new URLSearchParams(filepath).toString() + - app.getPreviewFormatParam() + - app.getRandParam() - ) - - // @ts-expect-error fixme ts strict error - if (ComfyApp.clipspace.images) - // @ts-expect-error fixme ts strict error - ComfyApp.clipspace.images[ComfyApp.clipspace['selectedIndex']] = filepath - - ClipspaceDialog.invalidatePreview() -} - -// @ts-expect-error fixme ts strict error -function prepare_mask(image, maskCanvas, maskCtx, maskColor) { - // paste mask data into alpha channel - maskCtx.drawImage(image, 0, 0, maskCanvas.width, maskCanvas.height) - const maskData = maskCtx.getImageData( - 0, - 0, - maskCanvas.width, - maskCanvas.height - ) - - // invert mask - for (let i = 0; i < maskData.data.length; i += 4) { - if (maskData.data[i + 3] == 255) maskData.data[i + 3] = 0 - else maskData.data[i + 3] = 255 - - maskData.data[i] = maskColor.r - maskData.data[i + 1] = maskColor.g - maskData.data[i + 2] = maskColor.b - } - - maskCtx.globalCompositeOperation = 'source-over' - maskCtx.putImageData(maskData, 0, 0) -} - -// Define the PointerType enum -enum PointerType { - Arc = 'arc', - Rect = 'rect' -} - -enum CompositionOperation { - SourceOver = 'source-over', - DestinationOut = 'destination-out' -} - -export class MaskEditorDialogOld extends ComfyDialog { - static instance = null - static mousedown_x: number | null = null - static mousedown_y: number | null = null - - // @ts-expect-error fixme ts strict error - brush: HTMLDivElement - maskCtx: any - // @ts-expect-error fixme ts strict error - maskCanvas: HTMLCanvasElement - // @ts-expect-error fixme ts strict error - brush_size_slider: HTMLDivElement - // @ts-expect-error fixme ts strict error - brush_opacity_slider: HTMLDivElement - // @ts-expect-error fixme ts strict error - colorButton: HTMLButtonElement - // @ts-expect-error fixme ts strict error - saveButton: HTMLButtonElement - // @ts-expect-error fixme ts strict error - zoom_ratio: number - // @ts-expect-error fixme ts strict error - pan_x: number - // @ts-expect-error fixme ts strict error - pan_y: number - // @ts-expect-error fixme ts strict error - imgCanvas: HTMLCanvasElement - // @ts-expect-error fixme ts strict error - last_display_style: string - // @ts-expect-error fixme ts strict error - is_visible: boolean - // @ts-expect-error fixme ts strict error - image: HTMLImageElement - // @ts-expect-error fixme ts strict error - handler_registered: boolean - // @ts-expect-error fixme ts strict error - brush_slider_input: HTMLInputElement - // @ts-expect-error fixme ts strict error - cursorX: number - // @ts-expect-error fixme ts strict error - cursorY: number - // @ts-expect-error fixme ts strict error - mousedown_pan_x: number - // @ts-expect-error fixme ts strict error - mousedown_pan_y: number - // @ts-expect-error fixme ts strict error - last_pressure: number - // @ts-expect-error fixme ts strict error - pointer_type: PointerType - // @ts-expect-error fixme ts strict error - brush_pointer_type_select: HTMLDivElement - - static getInstance() { - if (!MaskEditorDialogOld.instance) { - // @ts-expect-error fixme ts strict error - MaskEditorDialogOld.instance = new MaskEditorDialogOld() - } - - return MaskEditorDialogOld.instance - } - - is_layout_created = false - - constructor() { - super() - this.element = $el('div.comfy-modal', { parent: document.body }, [ - $el('div.comfy-modal-content', [...this.createButtons()]) - ]) - } - - override createButtons() { - return [] - } - - // @ts-expect-error fixme ts strict error - createButton(name, callback): HTMLButtonElement { - var button = document.createElement('button') - button.style.pointerEvents = 'auto' - button.innerText = name - button.addEventListener('click', callback) - return button - } - - // @ts-expect-error fixme ts strict error - createLeftButton(name, callback) { - var button = this.createButton(name, callback) - button.style.cssFloat = 'left' - button.style.marginRight = '4px' - return button - } - - // @ts-expect-error fixme ts strict error - createRightButton(name, callback) { - var button = this.createButton(name, callback) - button.style.cssFloat = 'right' - button.style.marginLeft = '4px' - return button - } - - // @ts-expect-error fixme ts strict error - createLeftSlider(self, name, callback): HTMLDivElement { - const divElement = document.createElement('div') - divElement.id = 'maskeditor-slider' - divElement.style.cssFloat = 'left' - divElement.style.fontFamily = 'sans-serif' - divElement.style.marginRight = '4px' - divElement.style.color = 'var(--input-text)' - divElement.style.backgroundColor = 'var(--comfy-input-bg)' - divElement.style.borderRadius = '8px' - divElement.style.borderColor = 'var(--border-color)' - divElement.style.borderStyle = 'solid' - divElement.style.fontSize = '15px' - divElement.style.height = '25px' - divElement.style.padding = '1px 6px' - divElement.style.display = 'flex' - divElement.style.position = 'relative' - divElement.style.top = '2px' - divElement.style.pointerEvents = 'auto' - self.brush_slider_input = document.createElement('input') - self.brush_slider_input.setAttribute('type', 'range') - self.brush_slider_input.setAttribute('min', '1') - self.brush_slider_input.setAttribute('max', '100') - self.brush_slider_input.setAttribute('value', '10') - const labelElement = document.createElement('label') - labelElement.textContent = name - - divElement.appendChild(labelElement) - divElement.appendChild(self.brush_slider_input) - - self.brush_slider_input.addEventListener('change', callback) - - return divElement - } - - // @ts-expect-error fixme ts strict error - createOpacitySlider(self, name, callback): HTMLDivElement { - const divElement = document.createElement('div') - divElement.id = 'maskeditor-opacity-slider' - divElement.style.cssFloat = 'left' - divElement.style.fontFamily = 'sans-serif' - divElement.style.marginRight = '4px' - divElement.style.color = 'var(--input-text)' - divElement.style.backgroundColor = 'var(--comfy-input-bg)' - divElement.style.borderRadius = '8px' - divElement.style.borderColor = 'var(--border-color)' - divElement.style.borderStyle = 'solid' - divElement.style.fontSize = '15px' - divElement.style.height = '25px' - divElement.style.padding = '1px 6px' - divElement.style.display = 'flex' - divElement.style.position = 'relative' - divElement.style.top = '2px' - divElement.style.pointerEvents = 'auto' - self.opacity_slider_input = document.createElement('input') - self.opacity_slider_input.setAttribute('type', 'range') - self.opacity_slider_input.setAttribute('min', '0.1') - self.opacity_slider_input.setAttribute('max', '1.0') - self.opacity_slider_input.setAttribute('step', '0.01') - self.opacity_slider_input.setAttribute('value', '0.7') - const labelElement = document.createElement('label') - labelElement.textContent = name - - divElement.appendChild(labelElement) - divElement.appendChild(self.opacity_slider_input) - - self.opacity_slider_input.addEventListener('input', callback) - - return divElement - } - - createPointerTypeSelect(self: any): HTMLDivElement { - const divElement = document.createElement('div') - divElement.id = 'maskeditor-pointer-type' - divElement.style.cssFloat = 'left' - divElement.style.fontFamily = 'sans-serif' - divElement.style.marginRight = '4px' - divElement.style.color = 'var(--input-text)' - divElement.style.backgroundColor = 'var(--comfy-input-bg)' - divElement.style.borderRadius = '8px' - divElement.style.borderColor = 'var(--border-color)' - divElement.style.borderStyle = 'solid' - divElement.style.fontSize = '15px' - divElement.style.height = '25px' - divElement.style.padding = '1px 6px' - divElement.style.display = 'flex' - divElement.style.position = 'relative' - divElement.style.top = '2px' - divElement.style.pointerEvents = 'auto' - - const labelElement = document.createElement('label') - labelElement.textContent = 'Pointer Type:' - - const selectElement = document.createElement('select') - selectElement.style.borderRadius = '0' - selectElement.style.borderColor = 'transparent' - selectElement.style.borderStyle = 'unset' - selectElement.style.fontSize = '0.9em' - - const optionArc = document.createElement('option') - optionArc.value = 'arc' - optionArc.text = 'Circle' - optionArc.selected = true // Fix for TypeScript, "selected" should be boolean - - const optionRect = document.createElement('option') - optionRect.value = 'rect' - optionRect.text = 'Square' - - selectElement.appendChild(optionArc) - selectElement.appendChild(optionRect) - - selectElement.addEventListener('change', (event: Event) => { - const target = event.target as HTMLSelectElement - self.pointer_type = target.value - this.setBrushBorderRadius(self) - }) - - divElement.appendChild(labelElement) - divElement.appendChild(selectElement) - - return divElement - } - - setBrushBorderRadius(self: any): void { - if (self.pointer_type === PointerType.Rect) { - this.brush.style.borderRadius = '0%' - // @ts-expect-error - this.brush.style.MozBorderRadius = '0%' - // @ts-expect-error - this.brush.style.WebkitBorderRadius = '0%' - } else { - this.brush.style.borderRadius = '50%' - // @ts-expect-error - this.brush.style.MozBorderRadius = '50%' - // @ts-expect-error - this.brush.style.WebkitBorderRadius = '50%' - } - } - - setlayout(imgCanvas: HTMLCanvasElement, maskCanvas: HTMLCanvasElement) { - const self = this - self.pointer_type = PointerType.Arc - - // If it is specified as relative, using it only as a hidden placeholder for padding is recommended - // to prevent anomalies where it exceeds a certain size and goes outside of the window. - var bottom_panel = document.createElement('div') - bottom_panel.style.position = 'absolute' - bottom_panel.style.bottom = '0px' - bottom_panel.style.left = '20px' - bottom_panel.style.right = '20px' - bottom_panel.style.height = '50px' - bottom_panel.style.pointerEvents = 'none' - - var brush = document.createElement('div') - brush.id = 'brush' - brush.style.backgroundColor = 'transparent' - brush.style.outline = '1px dashed black' - brush.style.boxShadow = '0 0 0 1px white' - brush.style.position = 'absolute' - brush.style.zIndex = '8889' - brush.style.pointerEvents = 'none' - this.brush = brush - this.setBrushBorderRadius(self) - this.element.appendChild(imgCanvas) - this.element.appendChild(maskCanvas) - this.element.appendChild(bottom_panel) - document.body.appendChild(brush) - - var clearButton = this.createLeftButton('Clear', () => { - self.maskCtx.clearRect( - 0, - 0, - self.maskCanvas.width, - self.maskCanvas.height - ) - }) - - this.brush_size_slider = this.createLeftSlider( - self, - 'Thickness', - // @ts-expect-error fixme ts strict error - (event) => { - self.brush_size = event.target.value - self.updateBrushPreview(self) - } - ) - - this.brush_opacity_slider = this.createOpacitySlider( - self, - 'Opacity', - // @ts-expect-error fixme ts strict error - (event) => { - self.brush_opacity = event.target.value - if (self.brush_color_mode !== 'negative') { - self.maskCanvas.style.opacity = self.brush_opacity.toString() - } - } - ) - - this.brush_pointer_type_select = this.createPointerTypeSelect(self) - this.colorButton = this.createLeftButton(this.getColorButtonText(), () => { - if (self.brush_color_mode === 'black') { - self.brush_color_mode = 'white' - } else if (self.brush_color_mode === 'white') { - self.brush_color_mode = 'negative' - } else { - self.brush_color_mode = 'black' - } - - self.updateWhenBrushColorModeChanged() - }) - - var cancelButton = this.createRightButton('Cancel', () => { - document.removeEventListener('keydown', MaskEditorDialogOld.handleKeyDown) - self.close() - }) - - this.saveButton = this.createRightButton('Save', () => { - document.removeEventListener('keydown', MaskEditorDialogOld.handleKeyDown) - self.save() - }) - - this.element.appendChild(imgCanvas) - this.element.appendChild(maskCanvas) - this.element.appendChild(bottom_panel) - - bottom_panel.appendChild(clearButton) - bottom_panel.appendChild(this.saveButton) - bottom_panel.appendChild(cancelButton) - bottom_panel.appendChild(this.brush_size_slider) - bottom_panel.appendChild(this.brush_opacity_slider) - bottom_panel.appendChild(this.brush_pointer_type_select) - bottom_panel.appendChild(this.colorButton) - - imgCanvas.style.position = 'absolute' - maskCanvas.style.position = 'absolute' - - imgCanvas.style.top = '200' - imgCanvas.style.left = '0' - - maskCanvas.style.top = imgCanvas.style.top - maskCanvas.style.left = imgCanvas.style.left - - const maskCanvasStyle = this.getMaskCanvasStyle() - maskCanvas.style.mixBlendMode = maskCanvasStyle.mixBlendMode - maskCanvas.style.opacity = maskCanvasStyle.opacity.toString() - } - - override async show() { - this.zoom_ratio = 1.0 - this.pan_x = 0 - this.pan_y = 0 - - if (!this.is_layout_created) { - // layout - const imgCanvas = document.createElement('canvas') - const maskCanvas = document.createElement('canvas') - - imgCanvas.id = 'imageCanvas' - maskCanvas.id = 'maskCanvas' - - this.setlayout(imgCanvas, maskCanvas) - - // prepare content - this.imgCanvas = imgCanvas - this.maskCanvas = maskCanvas - this.maskCtx = maskCanvas.getContext('2d', { willReadFrequently: true }) - - this.setEventHandler(maskCanvas) - - this.is_layout_created = true - - // replacement of onClose hook since close is not real close - const self = this - const observer = new MutationObserver(function (mutations) { - mutations.forEach(function (mutation) { - if ( - mutation.type === 'attributes' && - mutation.attributeName === 'style' - ) { - if ( - self.last_display_style && - self.last_display_style != 'none' && - self.element.style.display == 'none' - ) { - self.brush.style.display = 'none' - ComfyApp.onClipspaceEditorClosed() - } - - self.last_display_style = self.element.style.display - } - }) - }) - - const config = { attributes: true } - observer.observe(this.element, config) - } - - // The keydown event needs to be reconfigured when closing the dialog as it gets removed. - document.addEventListener('keydown', MaskEditorDialogOld.handleKeyDown) - - if (ComfyApp.clipspace_return_node) { - this.saveButton.innerText = 'Save to node' - } else { - this.saveButton.innerText = 'Save' - } - this.saveButton.disabled = false - - this.element.style.display = 'block' - this.element.style.width = '85%' - this.element.style.margin = '0 7.5%' - this.element.style.height = '100vh' - this.element.style.top = '50%' - this.element.style.left = '42%' - this.element.style.zIndex = '8888' // NOTE: alert dialog must be high priority. - - await this.setImages(this.imgCanvas) - - this.is_visible = true - } - - isOpened() { - return this.element.style.display == 'block' - } - - // @ts-expect-error fixme ts strict error - invalidateCanvas(orig_image, mask_image) { - this.imgCanvas.width = orig_image.width - this.imgCanvas.height = orig_image.height - - this.maskCanvas.width = orig_image.width - this.maskCanvas.height = orig_image.height - - let imgCtx = this.imgCanvas.getContext('2d', { willReadFrequently: true }) - let maskCtx = this.maskCanvas.getContext('2d', { - willReadFrequently: true - }) - - // @ts-expect-error fixme ts strict error - imgCtx.drawImage(orig_image, 0, 0, orig_image.width, orig_image.height) - prepare_mask(mask_image, this.maskCanvas, maskCtx, this.getMaskColor()) - } - - // @ts-expect-error fixme ts strict error - async setImages(imgCanvas) { - let self = this - - const imgCtx = imgCanvas.getContext('2d', { willReadFrequently: true }) - const maskCtx = this.maskCtx - const maskCanvas = this.maskCanvas - - imgCtx.clearRect(0, 0, this.imgCanvas.width, this.imgCanvas.height) - maskCtx.clearRect(0, 0, this.maskCanvas.width, this.maskCanvas.height) - - // image load - const alpha_url = new URL( - // @ts-expect-error fixme ts strict error - ComfyApp.clipspace.imgs[ComfyApp.clipspace['selectedIndex']].src - ) - alpha_url.searchParams.delete('channel') - alpha_url.searchParams.delete('preview') - alpha_url.searchParams.set('channel', 'a') - let mask_image = await loadImage(alpha_url) - - // original image load - const rgb_url = new URL( - // @ts-expect-error fixme ts strict error - ComfyApp.clipspace.imgs[ComfyApp.clipspace['selectedIndex']].src - ) - rgb_url.searchParams.delete('channel') - rgb_url.searchParams.set('channel', 'rgb') - this.image = new Image() - this.image.onload = function () { - maskCanvas.width = self.image.width - maskCanvas.height = self.image.height - - self.invalidateCanvas(self.image, mask_image) - self.initializeCanvasPanZoom() - } - this.image.src = rgb_url.toString() - } - - initializeCanvasPanZoom() { - // set initialize - let drawWidth = this.image.width - let drawHeight = this.image.height - - let width = this.element.clientWidth - let height = this.element.clientHeight - - if (this.image.width > width) { - drawWidth = width - drawHeight = (drawWidth / this.image.width) * this.image.height - } - - if (drawHeight > height) { - drawHeight = height - drawWidth = (drawHeight / this.image.height) * this.image.width - } - - this.zoom_ratio = drawWidth / this.image.width - - const canvasX = (width - drawWidth) / 2 - const canvasY = (height - drawHeight) / 2 - this.pan_x = canvasX - this.pan_y = canvasY - - this.invalidatePanZoom() - } - - invalidatePanZoom() { - let raw_width = this.image.width * this.zoom_ratio - let raw_height = this.image.height * this.zoom_ratio - - if (this.pan_x + raw_width < 10) { - this.pan_x = 10 - raw_width - } - - if (this.pan_y + raw_height < 10) { - this.pan_y = 10 - raw_height - } - - let width = `${raw_width}px` - let height = `${raw_height}px` - - let left = `${this.pan_x}px` - let top = `${this.pan_y}px` - - this.maskCanvas.style.width = width - this.maskCanvas.style.height = height - this.maskCanvas.style.left = left - this.maskCanvas.style.top = top - - this.imgCanvas.style.width = width - this.imgCanvas.style.height = height - this.imgCanvas.style.left = left - this.imgCanvas.style.top = top - } - - // @ts-expect-error fixme ts strict error - setEventHandler(maskCanvas) { - const self = this - - if (!this.handler_registered) { - // @ts-expect-error fixme ts strict error - maskCanvas.addEventListener('contextmenu', (event) => { - event.preventDefault() - }) - - this.element.addEventListener('wheel', (event) => - this.handleWheelEvent(self, event) - ) - this.element.addEventListener('pointermove', (event) => - this.pointMoveEvent(self, event) - ) - this.element.addEventListener('touchmove', (event) => - this.pointMoveEvent(self, event) - ) - - this.element.addEventListener('dragstart', (event) => { - if (event.ctrlKey) { - event.preventDefault() - } - }) - - // @ts-expect-error fixme ts strict error - maskCanvas.addEventListener('pointerdown', (event) => - this.handlePointerDown(self, event) - ) - // @ts-expect-error fixme ts strict error - maskCanvas.addEventListener('pointermove', (event) => - this.draw_move(self, event) - ) - // @ts-expect-error fixme ts strict error - maskCanvas.addEventListener('touchmove', (event) => - this.draw_move(self, event) - ) - maskCanvas.addEventListener('pointerover', () => { - this.brush.style.display = 'block' - }) - maskCanvas.addEventListener('pointerleave', () => { - this.brush.style.display = 'none' - }) - - document.addEventListener( - 'pointerup', - MaskEditorDialogOld.handlePointerUp - ) - - this.handler_registered = true - } - } - - getMaskCanvasStyle() { - if (this.brush_color_mode === 'negative') { - return { - mixBlendMode: 'difference', - opacity: '1' - } - } else { - return { - mixBlendMode: 'initial', - opacity: this.brush_opacity - } - } - } - - getMaskColor() { - if (this.brush_color_mode === 'black') { - return { r: 0, g: 0, b: 0 } - } - if (this.brush_color_mode === 'white') { - return { r: 255, g: 255, b: 255 } - } - if (this.brush_color_mode === 'negative') { - // negative effect only works with white color - return { r: 255, g: 255, b: 255 } - } - - return { r: 0, g: 0, b: 0 } - } - - getMaskFillStyle() { - const maskColor = this.getMaskColor() - - return 'rgb(' + maskColor.r + ',' + maskColor.g + ',' + maskColor.b + ')' - } - - getColorButtonText() { - let colorCaption = 'unknown' - - if (this.brush_color_mode === 'black') { - colorCaption = 'black' - } else if (this.brush_color_mode === 'white') { - colorCaption = 'white' - } else if (this.brush_color_mode === 'negative') { - colorCaption = 'negative' - } - - return 'Color: ' + colorCaption - } - - updateWhenBrushColorModeChanged() { - this.colorButton.innerText = this.getColorButtonText() - - // update mask canvas css styles - - const maskCanvasStyle = this.getMaskCanvasStyle() - this.maskCanvas.style.mixBlendMode = maskCanvasStyle.mixBlendMode - this.maskCanvas.style.opacity = maskCanvasStyle.opacity.toString() - - // update mask canvas rgb colors - - const maskColor = this.getMaskColor() - - const maskData = this.maskCtx.getImageData( - 0, - 0, - this.maskCanvas.width, - this.maskCanvas.height - ) - - for (let i = 0; i < maskData.data.length; i += 4) { - maskData.data[i] = maskColor.r - maskData.data[i + 1] = maskColor.g - maskData.data[i + 2] = maskColor.b - } - - this.maskCtx.putImageData(maskData, 0, 0) - } - - brush_opacity = 0.7 - brush_size = 10 - brush_color_mode = 'black' - drawing_mode = false - lastx = -1 - lasty = -1 - lasttime = 0 - - // @ts-expect-error fixme ts strict error - static handleKeyDown(event) { - const self = MaskEditorDialogOld.instance - if (event.key === ']') { - // @ts-expect-error fixme ts strict error - self.brush_size = Math.min(self.brush_size + 2, 100) - // @ts-expect-error fixme ts strict error - self.brush_slider_input.value = self.brush_size - } else if (event.key === '[') { - // @ts-expect-error fixme ts strict error - self.brush_size = Math.max(self.brush_size - 2, 1) - // @ts-expect-error fixme ts strict error - self.brush_slider_input.value = self.brush_size - } else if (event.key === 'Enter') { - // @ts-expect-error fixme ts strict error - self.save() - } - - // @ts-expect-error fixme ts strict error - self.updateBrushPreview(self) - } - - // @ts-expect-error fixme ts strict error - static handlePointerUp(event) { - event.preventDefault() - - this.mousedown_x = null - this.mousedown_y = null - - // @ts-expect-error fixme ts strict error - MaskEditorDialogOld.instance.drawing_mode = false - } - - // @ts-expect-error fixme ts strict error - updateBrushPreview(self) { - const brush = self.brush - - var centerX = self.cursorX - var centerY = self.cursorY - - brush.style.width = self.brush_size * 2 * this.zoom_ratio + 'px' - brush.style.height = self.brush_size * 2 * this.zoom_ratio + 'px' - brush.style.left = centerX - self.brush_size * this.zoom_ratio + 'px' - brush.style.top = centerY - self.brush_size * this.zoom_ratio + 'px' - } - - // @ts-expect-error fixme ts strict error - handleWheelEvent(_, event) { - event.preventDefault() - - if (event.ctrlKey) { - // zoom canvas - if (event.deltaY < 0) { - this.zoom_ratio = Math.min(10.0, this.zoom_ratio + 0.2) - } else { - this.zoom_ratio = Math.max(0.2, this.zoom_ratio - 0.2) - } - - this.invalidatePanZoom() - } else { - // adjust brush size - if (event.deltaY < 0) this.brush_size = Math.min(this.brush_size + 2, 100) - else this.brush_size = Math.max(this.brush_size - 2, 1) - - this.brush_slider_input.value = this.brush_size.toString() - - this.updateBrushPreview(this) - } - } - - // @ts-expect-error fixme ts strict error - pointMoveEvent(self, event) { - this.cursorX = event.pageX - this.cursorY = event.pageY - - self.updateBrushPreview(self) - - if (event.ctrlKey) { - event.preventDefault() - self.pan_move(self, event) - } - - let left_button_down = - (window.TouchEvent && event instanceof TouchEvent) || event.buttons == 1 - - if (event.shiftKey && left_button_down) { - self.drawing_mode = false - - const y = event.clientY - let delta = (self.zoom_lasty - y) * 0.005 - self.zoom_ratio = Math.max( - Math.min(10.0, self.last_zoom_ratio - delta), - 0.2 - ) - - this.invalidatePanZoom() - return - } - } - - // @ts-expect-error fixme ts strict error - pan_move(self, event) { - if (event.buttons == 1) { - if (MaskEditorDialogOld.mousedown_x) { - let deltaX = MaskEditorDialogOld.mousedown_x - event.clientX - // @ts-expect-error fixme ts strict error - let deltaY = MaskEditorDialogOld.mousedown_y - event.clientY - - self.pan_x = this.mousedown_pan_x - deltaX - self.pan_y = this.mousedown_pan_y - deltaY - - self.invalidatePanZoom() - } - } - } - - // @ts-expect-error fixme ts strict error - draw_move(self, event) { - if (event.ctrlKey || event.shiftKey) { - return - } - - event.preventDefault() - - this.cursorX = event.pageX - this.cursorY = event.pageY - - self.updateBrushPreview(self) - - let left_button_down = - (window.TouchEvent && event instanceof TouchEvent) || event.buttons == 1 - let right_button_down = [2, 5, 32].includes(event.buttons) - - if (!event.altKey && left_button_down) { - var diff = performance.now() - self.lasttime - - const maskRect = self.maskCanvas.getBoundingClientRect() - - var x = event.offsetX - var y = event.offsetY - - if (event.offsetX == null) { - x = event.targetTouches[0].clientX - maskRect.left - } - - if (event.offsetY == null) { - y = event.targetTouches[0].clientY - maskRect.top - } - - x /= self.zoom_ratio - y /= self.zoom_ratio - - var brush_size = this.brush_size - if (event instanceof PointerEvent && event.pointerType == 'pen') { - brush_size *= event.pressure - this.last_pressure = event.pressure - } else if ( - window.TouchEvent && - event instanceof TouchEvent && - diff < 20 - ) { - // The firing interval of PointerEvents in Pen is unreliable, so it is supplemented by TouchEvents. - brush_size *= this.last_pressure - } else { - brush_size = this.brush_size - } - - if (diff > 20 && !this.drawing_mode) - requestAnimationFrame(() => { - self.init_shape(self, CompositionOperation.SourceOver) - self.draw_shape(self, x, y, brush_size) - self.lastx = x - self.lasty = y - }) - else - requestAnimationFrame(() => { - self.init_shape(self, CompositionOperation.SourceOver) - - var dx = x - self.lastx - var dy = y - self.lasty - - var distance = Math.sqrt(dx * dx + dy * dy) - var directionX = dx / distance - var directionY = dy / distance - - for (var i = 0; i < distance; i += 5) { - var px = self.lastx + directionX * i - var py = self.lasty + directionY * i - self.draw_shape(self, px, py, brush_size) - } - self.lastx = x - self.lasty = y - }) - - self.lasttime = performance.now() - } else if ((event.altKey && left_button_down) || right_button_down) { - const maskRect = self.maskCanvas.getBoundingClientRect() - const x = - (event.offsetX || event.targetTouches[0].clientX - maskRect.left) / - self.zoom_ratio - const y = - (event.offsetY || event.targetTouches[0].clientY - maskRect.top) / - self.zoom_ratio - - var brush_size = this.brush_size - if (event instanceof PointerEvent && event.pointerType == 'pen') { - brush_size *= event.pressure - this.last_pressure = event.pressure - } else if ( - window.TouchEvent && - event instanceof TouchEvent && - // @ts-expect-error fixme ts strict error - diff < 20 - ) { - brush_size *= this.last_pressure - } else { - brush_size = this.brush_size - } - - // @ts-expect-error fixme ts strict error - if (diff > 20 && !this.drawing_mode) - // cannot tracking drawing_mode for touch event - requestAnimationFrame(() => { - self.init_shape(self, CompositionOperation.DestinationOut) - self.draw_shape(self, x, y, brush_size) - self.lastx = x - self.lasty = y - }) - else - requestAnimationFrame(() => { - self.init_shape(self, CompositionOperation.DestinationOut) - - var dx = x - self.lastx - var dy = y - self.lasty - - var distance = Math.sqrt(dx * dx + dy * dy) - var directionX = dx / distance - var directionY = dy / distance - - for (var i = 0; i < distance; i += 5) { - var px = self.lastx + directionX * i - var py = self.lasty + directionY * i - self.draw_shape(self, px, py, brush_size) - } - self.lastx = x - self.lasty = y - }) - - self.lasttime = performance.now() - } - } - - // @ts-expect-error fixme ts strict error - handlePointerDown(self, event) { - if (event.ctrlKey) { - if (event.buttons == 1) { - MaskEditorDialogOld.mousedown_x = event.clientX - MaskEditorDialogOld.mousedown_y = event.clientY - - this.mousedown_pan_x = this.pan_x - this.mousedown_pan_y = this.pan_y - } - return - } - - var brush_size = this.brush_size - if (event instanceof PointerEvent && event.pointerType == 'pen') { - brush_size *= event.pressure - this.last_pressure = event.pressure - } - - if ([0, 2, 5].includes(event.button)) { - self.drawing_mode = true - - event.preventDefault() - - if (event.shiftKey) { - self.zoom_lasty = event.clientY - self.last_zoom_ratio = self.zoom_ratio - return - } - - const maskRect = self.maskCanvas.getBoundingClientRect() - const x = - (event.offsetX || event.targetTouches[0].clientX - maskRect.left) / - self.zoom_ratio - const y = - (event.offsetY || event.targetTouches[0].clientY - maskRect.top) / - self.zoom_ratio - - if (!event.altKey && event.button == 0) { - self.init_shape(self, CompositionOperation.SourceOver) - } else { - self.init_shape(self, CompositionOperation.DestinationOut) - } - self.draw_shape(self, x, y, brush_size) - self.lastx = x - self.lasty = y - self.lasttime = performance.now() - } - } - - // @ts-expect-error fixme ts strict error - init_shape(self, compositionOperation) { - self.maskCtx.beginPath() - if (compositionOperation == CompositionOperation.SourceOver) { - self.maskCtx.fillStyle = this.getMaskFillStyle() - self.maskCtx.globalCompositeOperation = CompositionOperation.SourceOver - } else if (compositionOperation == CompositionOperation.DestinationOut) { - self.maskCtx.globalCompositeOperation = - CompositionOperation.DestinationOut - } - } - - // @ts-expect-error fixme ts strict error - draw_shape(self, x, y, brush_size) { - if (self.pointer_type === PointerType.Rect) { - self.maskCtx.rect( - x - brush_size, - y - brush_size, - brush_size * 2, - brush_size * 2 - ) - } else { - self.maskCtx.arc(x, y, brush_size, 0, Math.PI * 2, false) - } - self.maskCtx.fill() - } - - async save() { - const backupCanvas = document.createElement('canvas') - const backupCtx = backupCanvas.getContext('2d', { - willReadFrequently: true - }) - backupCanvas.width = this.image.width - backupCanvas.height = this.image.height - - // @ts-expect-error fixme ts strict error - backupCtx.clearRect(0, 0, backupCanvas.width, backupCanvas.height) - // @ts-expect-error fixme ts strict error - backupCtx.drawImage( - this.maskCanvas, - 0, - 0, - this.maskCanvas.width, - this.maskCanvas.height, - 0, - 0, - backupCanvas.width, - backupCanvas.height - ) - - // paste mask data into alpha channel - // @ts-expect-error fixme ts strict error - const backupData = backupCtx.getImageData( - 0, - 0, - backupCanvas.width, - backupCanvas.height - ) - - // refine mask image - for (let i = 0; i < backupData.data.length; i += 4) { - if (backupData.data[i + 3] == 255) backupData.data[i + 3] = 0 - else backupData.data[i + 3] = 255 - - backupData.data[i] = 0 - backupData.data[i + 1] = 0 - backupData.data[i + 2] = 0 - } - - // @ts-expect-error fixme ts strict error - backupCtx.globalCompositeOperation = CompositionOperation.SourceOver - // @ts-expect-error fixme ts strict error - backupCtx.putImageData(backupData, 0, 0) - - const formData = new FormData() - const filename = 'clipspace-mask-' + performance.now() + '.png' - - const item = { - filename: filename, - subfolder: 'clipspace', - type: 'input' - } - - // @ts-expect-error fixme ts strict error - if (ComfyApp.clipspace.images) ComfyApp.clipspace.images[0] = item - - // @ts-expect-error fixme ts strict error - if (ComfyApp.clipspace.widgets) { - // @ts-expect-error fixme ts strict error - const index = ComfyApp.clipspace.widgets.findIndex( - (obj) => obj.name === 'image' - ) - - // @ts-expect-error fixme ts strict error - if (index >= 0) ComfyApp.clipspace.widgets[index].value = item - } - - const dataURL = backupCanvas.toDataURL() - const blob = dataURLToBlob(dataURL) - - let original_url = new URL(this.image.src) - - type Ref = { filename: string; subfolder?: string; type?: string } - - const original_ref: Ref = { - // @ts-expect-error fixme ts strict error - filename: original_url.searchParams.get('filename') - } - - let original_subfolder = original_url.searchParams.get('subfolder') - if (original_subfolder) original_ref.subfolder = original_subfolder - - let original_type = original_url.searchParams.get('type') - if (original_type) original_ref.type = original_type - - formData.append('image', blob, filename) - formData.append('original_ref', JSON.stringify(original_ref)) - formData.append('type', 'input') - formData.append('subfolder', 'clipspace') - - this.saveButton.innerText = 'Saving...' - this.saveButton.disabled = true - await uploadMask(item, formData) - ComfyApp.onClipspaceEditorSave() - this.close() - } -} diff --git a/src/extensions/core/maskeditor.ts b/src/extensions/core/maskeditor.ts index 5d8dadea9..550bec00a 100644 --- a/src/extensions/core/maskeditor.ts +++ b/src/extensions/core/maskeditor.ts @@ -1,13 +1,11 @@ import _ from 'es-toolkit/compat' import type { LGraphNode } from '@/lib/litegraph/src/litegraph' -import { app } from '@/scripts/app' -import { ComfyApp } from '@/scripts/app' +import { app, ComfyApp } from '@/scripts/app' import { useMaskEditorStore } from '@/stores/maskEditorStore' import { useDialogStore } from '@/stores/dialogStore' -import { MaskEditorDialogOld } from './maskEditorOld' -import { ClipspaceDialog } from './clipspace' import { useMaskEditor } from '@/composables/maskeditor/useMaskEditor' +import { useCanvasTransform } from '@/composables/maskeditor/useCanvasTransform' function openMaskEditor(node: LGraphNode): void { if (!node) { @@ -20,55 +18,44 @@ function openMaskEditor(node: LGraphNode): void { return } - const useNewEditor = app.extensionManager.setting.get( - 'Comfy.MaskEditor.UseNewEditor' - ) + useMaskEditor().openMaskEditor(node) +} - if (useNewEditor) { - useMaskEditor().openMaskEditor(node) - } else { - // Use old editor - ComfyApp.copyToClipspace(node) - // @ts-expect-error clipspace_return_node is an extension property added at runtime - ComfyApp.clipspace_return_node = node - const dlg = MaskEditorDialogOld.getInstance() as any - if (dlg?.isOpened && !dlg.isOpened()) { - dlg.show() - } +// Open mask editor from clipspace (for plugin compatibility) +// This is called when ComfyApp.open_maskeditor() is invoked without arguments +function openMaskEditorFromClipspace(): void { + const node = ComfyApp.clipspace_return_node as LGraphNode | null + if (!node) { + console.error('[MaskEditor] No clipspace_return_node found') + return } + + openMaskEditor(node) } // Check if the dialog is already opened function isOpened(): boolean { - const useNewEditor = app.extensionManager.setting.get( - 'Comfy.MaskEditor.UseNewEditor' - ) - if (useNewEditor) { - return useDialogStore().isDialogOpen('global-mask-editor') - } else { - return (MaskEditorDialogOld.instance as any)?.isOpened?.() ?? false - } + return useDialogStore().isDialogOpen('global-mask-editor') +} + +const changeBrushSize = async (sizeChanger: (oldSize: number) => number) => { + if (!isOpened()) return + + const store = useMaskEditorStore() + const oldBrushSize = store.brushSettings.size + const newBrushSize = sizeChanger(oldBrushSize) + store.setBrushSize(newBrushSize) } app.registerExtension({ name: 'Comfy.MaskEditor', settings: [ - { - id: 'Comfy.MaskEditor.UseNewEditor', - category: ['Mask Editor', 'NewEditor'], - name: 'Use new mask editor', - tooltip: 'Switch to the new mask editor interface', - type: 'boolean', - defaultValue: true, - experimental: true - }, { id: 'Comfy.MaskEditor.BrushAdjustmentSpeed', category: ['Mask Editor', 'BrushAdjustment', 'Sensitivity'], name: 'Brush adjustment speed multiplier', tooltip: 'Controls how quickly the brush size and hardness change when adjusting. Higher values mean faster changes.', - experimental: true, type: 'slider', attrs: { min: 0.1, @@ -85,8 +72,7 @@ app.registerExtension({ tooltip: 'When enabled, brush adjustments will only affect size OR hardness based on which direction you move more', type: 'boolean', - defaultValue: true, - experimental: true + defaultValue: true } ], commands: [ @@ -106,50 +92,69 @@ app.registerExtension({ id: 'Comfy.MaskEditor.BrushSize.Increase', icon: 'pi pi-plus-circle', label: 'Increase Brush Size in MaskEditor', - function: () => changeBrushSize((old) => _.clamp(old + 4, 1, 100)) + function: () => changeBrushSize((old) => _.clamp(old + 2, 1, 250)) }, { id: 'Comfy.MaskEditor.BrushSize.Decrease', icon: 'pi pi-minus-circle', label: 'Decrease Brush Size in MaskEditor', - function: () => changeBrushSize((old) => _.clamp(old - 4, 1, 100)) + function: () => changeBrushSize((old) => _.clamp(old - 2, 1, 250)) + }, + { + id: 'Comfy.MaskEditor.ColorPicker', + icon: 'pi pi-palette', + label: 'Open Color Picker in MaskEditor', + function: () => { + if (!isOpened()) return + + const store = useMaskEditorStore() + store.colorInput?.click() + } + }, + { + id: 'Comfy.MaskEditor.Rotate.Right', + icon: 'pi pi-refresh', + label: 'Rotate Right in MaskEditor', + function: async () => { + if (!isOpened()) return + await useCanvasTransform().rotateClockwise() + } + }, + { + id: 'Comfy.MaskEditor.Rotate.Left', + icon: 'pi pi-undo', + label: 'Rotate Left in MaskEditor', + function: async () => { + if (!isOpened()) return + await useCanvasTransform().rotateCounterclockwise() + } + }, + { + id: 'Comfy.MaskEditor.Mirror.Horizontal', + icon: 'pi pi-arrows-h', + label: 'Mirror Horizontal in MaskEditor', + function: async () => { + if (!isOpened()) return + await useCanvasTransform().mirrorHorizontal() + } + }, + { + id: 'Comfy.MaskEditor.Mirror.Vertical', + icon: 'pi pi-arrows-v', + label: 'Mirror Vertical in MaskEditor', + function: async () => { + if (!isOpened()) return + await useCanvasTransform().mirrorVertical() + } } ], init() { - // Support for old editor clipspace integration - const openMaskEditorFromClipspace = () => { - const useNewEditor = app.extensionManager.setting.get( - 'Comfy.MaskEditor.UseNewEditor' - ) - if (!useNewEditor) { - const dlg = MaskEditorDialogOld.getInstance() as any - if (dlg?.isOpened && !dlg.isOpened()) { - dlg.show() - } - } - } + // Set up ComfyApp static methods for plugin compatibility (deprecated) + ComfyApp.open_maskeditor = openMaskEditorFromClipspace - const context_predicate = (): boolean => { - return !!( - ComfyApp.clipspace && - ComfyApp.clipspace.imgs && - ComfyApp.clipspace.imgs.length > 0 - ) - } - - ClipspaceDialog.registerButton( - 'MaskEditor', - context_predicate, - openMaskEditorFromClipspace + console.warn( + '[MaskEditor] ComfyApp.open_maskeditor is deprecated. ' + + 'Plugins should migrate to using the command system or direct node context menu integration.' ) } }) - -const changeBrushSize = async (sizeChanger: (oldSize: number) => number) => { - if (!isOpened()) return - - const store = useMaskEditorStore() - const oldBrushSize = store.brushSettings.size - const newBrushSize = sizeChanger(oldBrushSize) - store.setBrushSize(newBrushSize) -} diff --git a/src/extensions/core/matchType.ts b/src/extensions/core/matchType.ts deleted file mode 100644 index 686ca3096..000000000 --- a/src/extensions/core/matchType.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { without } from 'es-toolkit' - -import { useChainCallback } from '@/composables/functional/useChainCallback' -import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' -import { LiteGraph } from '@/lib/litegraph/src/litegraph' -import type { LLink } from '@/lib/litegraph/src/LLink' -import type { ISlotType } from '@/lib/litegraph/src/interfaces' -import { app } from '@/scripts/app' - -const MATCH_TYPE = 'COMFY_MATCHTYPE_V3' - -app.registerExtension({ - name: 'Comfy.MatchType', - beforeRegisterNodeDef(nodeType, nodeData) { - const inputs = { - ...nodeData.input?.required, - ...nodeData.input?.optional - } - if (!Object.values(inputs).some((w) => w[0] === MATCH_TYPE)) return - nodeType.prototype.onNodeCreated = useChainCallback( - nodeType.prototype.onNodeCreated, - function (this: LGraphNode) { - const inputGroups: Record = {} - const outputGroups: Record = {} - for (const input of this.inputs) { - if (input.type !== MATCH_TYPE) continue - const template = inputs[input.name][1]?.template - if (!template) continue - input.type = template.allowed_types ?? '*' - inputGroups[template.template_id] ??= [] - inputGroups[template.template_id].push([input.name, input.type]) - } - this.outputs.forEach((output, i) => { - if (output.type !== MATCH_TYPE) return - const id = nodeData.output_matchtypes?.[i] - if (id == undefined) return - outputGroups[id] ??= [] - outputGroups[id].push(i) - }) - for (const groupId in inputGroups) { - addConnectionGroup(this, inputGroups[groupId], outputGroups[groupId]) - } - } - ) - } -}) -function addConnectionGroup( - node: LGraphNode, - inputPairs: [string, ISlotType][], - outputs?: number[] -) { - const connectedTypes: ISlotType[] = new Array(inputPairs.length).fill('*') - node.onConnectionsChange = useChainCallback( - node.onConnectionsChange, - function ( - this: LGraphNode, - contype: ISlotType, - slot: number, - iscon: boolean, - linf: LLink | null | undefined - ) { - const input = this.inputs[slot] - if (contype !== LiteGraph.INPUT || !this.graph || !input) return - const pairIndex = inputPairs.findIndex(([name]) => name === input.name) - if (pairIndex == -1) return - connectedTypes[pairIndex] = inputPairs[pairIndex][1] - if (iscon && linf) { - const { output, subgraphInput } = linf.resolve(this.graph) - const connectingType = (output ?? subgraphInput)?.type - if (connectingType) - linf.type = connectedTypes[pairIndex] = connectingType - } - //An input slot can accept a connection that is - // - Compatible with original type - // - Compatible with all other input types - //An output slot can output - // - Only what every input can output - for (let i = 0; i < inputPairs.length; i++) { - //NOTE: This isn't great. Originally, I kept direct references to each - //input, but these were becoming orphaned - const input = this.inputs.find((inp) => inp.name === inputPairs[i][0]) - if (!input) continue - const otherConnected = [...connectedTypes] - otherConnected.splice(i, 1) - const validType = combineTypes(...otherConnected, inputPairs[i][1]) - if (!validType) throw new Error('invalid connection') - input.type = validType - } - if (outputs) { - const outputType = combineTypes(...connectedTypes) - if (!outputType) throw new Error('invalid connection') - changeOutputType(this, outputType, outputs) - } - } - ) -} - -function changeOutputType( - node: LGraphNode, - combinedType: ISlotType, - outputs: number[] -) { - if (!node.graph) return - for (const index of outputs) { - if (node.outputs[index].type === combinedType) continue - node.outputs[index].type = combinedType - - //check and potentially remove links - for (let link_id of node.outputs[index].links ?? []) { - let link = node.graph.links[link_id] - if (!link) continue - const { input, inputNode, subgraphOutput } = link.resolve(node.graph) - const inputType = (input ?? subgraphOutput)?.type - if (!inputType) continue - const keep = LiteGraph.isValidConnection(combinedType, inputType) - if (!keep && subgraphOutput) subgraphOutput.disconnect() - else if (!keep && inputNode) inputNode.disconnectInput(link.target_slot) - if (input && inputNode?.onConnectionsChange) - inputNode.onConnectionsChange( - LiteGraph.INPUT, - link.target_slot, - keep, - link, - input - ) - } - app.canvas.setDirty(true, true) - } -} -function isStrings(types: ISlotType[]): types is string[] { - return !types.some((t) => typeof t !== 'string') -} - -function combineTypes(...types: ISlotType[]): ISlotType | undefined { - if (!isStrings(types)) return undefined - - const withoutWildcards = without(types, '*') - if (withoutWildcards.length === 0) return '*' - - const typeLists: string[][] = withoutWildcards.map((type) => type.split(',')) - - const combinedTypes = intersection(...typeLists) - if (combinedTypes.length === 0) return undefined - - return combinedTypes.join(',') -} -function intersection(...sets: string[][]): string[] { - const itemCounts: Record = {} - for (const set of sets) - for (const item of new Set(set)) - itemCounts[item] = (itemCounts[item] ?? 0) + 1 - return Object.entries(itemCounts) - .filter(([, count]) => count == sets.length) - .map(([key]) => key) -} diff --git a/src/extensions/core/nightlyBadges.ts b/src/extensions/core/nightlyBadges.ts new file mode 100644 index 000000000..a453a6397 --- /dev/null +++ b/src/extensions/core/nightlyBadges.ts @@ -0,0 +1,17 @@ +import { t } from '@/i18n' +import { useExtensionService } from '@/services/extensionService' +import type { TopbarBadge } from '@/types/comfy' + +const badges: TopbarBadge[] = [ + { + text: t('nightly.badge.label'), + label: t('g.nightly'), + variant: 'warning', + tooltip: t('nightly.badge.tooltip') + } +] + +useExtensionService().registerExtension({ + name: 'Comfy.Nightly.Badges', + topbarBadges: badges +}) diff --git a/src/extensions/core/nodeTemplates.ts b/src/extensions/core/nodeTemplates.ts index c16ebed72..532fbc410 100644 --- a/src/extensions/core/nodeTemplates.ts +++ b/src/extensions/core/nodeTemplates.ts @@ -32,9 +32,13 @@ import { GroupNodeConfig, GroupNodeHandler } from './groupNode' const id = 'Comfy.NodeTemplates' const file = 'comfy.templates.json' +interface NodeTemplate { + name: string + data: string +} + class ManageTemplates extends ComfyDialog { - // @ts-expect-error fixme ts strict error - templates: any[] + templates: NodeTemplate[] = [] draggedEl: HTMLElement | null saveVisualCue: number | null emptyImg: HTMLImageElement @@ -367,12 +371,13 @@ const ext: ComfyExtension = { data = JSON.parse(data || '{}') const nodeIds = Object.keys(app.canvas.selected_nodes) for (let i = 0; i < nodeIds.length; i++) { - const node = app.graph.getNodeById(nodeIds[i]) + const node = app.canvas.graph?.getNodeById(nodeIds[i]) const nodeData = node?.constructor.nodeData - let groupData = GroupNodeHandler.getGroupData(node) - if (groupData) { - groupData = groupData.nodeData + if (!node) continue + const groupConfig = GroupNodeHandler.getGroupData(node) + if (groupConfig) { + const groupData = groupConfig.nodeData // @ts-expect-error if (!data.groupNodes) { // @ts-expect-error @@ -402,7 +407,10 @@ const ext: ComfyExtension = { callback: () => { clipboardAction(async () => { const data = JSON.parse(t.data) - await GroupNodeConfig.registerFromWorkflow(data.groupNodes, {}) + await GroupNodeConfig.registerFromWorkflow( + data.groupNodes ?? {}, + [] + ) // Check for old clipboard format if (!data.reroutes) { diff --git a/src/extensions/core/noteNode.ts b/src/extensions/core/noteNode.ts index 8b2d9ed99..7e0ec56a6 100644 --- a/src/extensions/core/noteNode.ts +++ b/src/extensions/core/noteNode.ts @@ -14,13 +14,15 @@ app.registerExtension({ static collapsable: boolean static title_mode: number - override color = LGraphCanvas.node_colors.yellow.color - override bgcolor = LGraphCanvas.node_colors.yellow.bgcolor groupcolor = LGraphCanvas.node_colors.yellow.groupcolor override isVirtualNode: boolean constructor(title: string) { super(title) + + this.color = LGraphCanvas.node_colors.yellow.color + this.bgcolor = LGraphCanvas.node_colors.yellow.bgcolor + if (!this.properties) { this.properties = { text: '' } } @@ -53,12 +55,14 @@ app.registerExtension({ class MarkdownNoteNode extends LGraphNode { static override title = 'Markdown Note' - override color = LGraphCanvas.node_colors.yellow.color - override bgcolor = LGraphCanvas.node_colors.yellow.bgcolor groupcolor = LGraphCanvas.node_colors.yellow.groupcolor constructor(title: string) { super(title) + + this.color = LGraphCanvas.node_colors.yellow.color + this.bgcolor = LGraphCanvas.node_colors.yellow.bgcolor + if (!this.properties) { this.properties = { text: '' } } diff --git a/src/extensions/core/previewAny.ts b/src/extensions/core/previewAny.ts index d14cfa587..d06ba1584 100644 --- a/src/extensions/core/previewAny.ts +++ b/src/extensions/core/previewAny.ts @@ -24,12 +24,43 @@ useExtensionService().registerExtension({ app ).widget as DOMWidget - showValueWidget.options.read_only = true + const showValueWidgetPlain = ComfyWidgets['STRING']( + this, + 'preview', + ['STRING', { multiline: true }], + app + ).widget as DOMWidget + const showAsPlaintextWidget = ComfyWidgets['BOOLEAN']( + this, + 'previewMode', + [ + 'BOOLEAN', + { label_on: 'Markdown', label_off: 'Plaintext', default: false } + ], + app + ) + + showAsPlaintextWidget.widget.callback = (value: boolean) => { + showValueWidget.hidden = !value + showValueWidget.options.hidden = !value + showValueWidgetPlain.hidden = value + showValueWidgetPlain.options.hidden = value + } + + showValueWidget.hidden = true + showValueWidget.options.hidden = true + showValueWidget.options.read_only = true showValueWidget.element.readOnly = true showValueWidget.element.disabled = true - showValueWidget.serialize = false + + showValueWidgetPlain.hidden = false + showValueWidgetPlain.options.hidden = false + showValueWidgetPlain.options.read_only = true + showValueWidgetPlain.element.readOnly = true + showValueWidgetPlain.element.disabled = true + showValueWidgetPlain.serialize = false } const onExecuted = nodeType.prototype.onExecuted @@ -39,10 +70,14 @@ useExtensionService().registerExtension({ ? void 0 : onExecuted.apply(this, [message]) - const previewWidget = this.widgets?.find((w) => w.name === 'preview') + const previewWidgets = + this.widgets?.filter((w) => w.name === 'preview') ?? [] - if (previewWidget) { - previewWidget.value = message.text[0] + for (const previewWidget of previewWidgets) { + const text = message.text ?? '' + previewWidget.value = Array.isArray(text) + ? (text?.join('\n\n') ?? '') + : text } } } diff --git a/src/extensions/core/saveMesh.ts b/src/extensions/core/saveMesh.ts index ae311d47d..947120467 100644 --- a/src/extensions/core/saveMesh.ts +++ b/src/extensions/core/saveMesh.ts @@ -4,9 +4,15 @@ import Load3D from '@/components/load3d/Load3D.vue' import { useLoad3d } from '@/composables/useLoad3d' import { createExportMenuItems } from '@/extensions/core/load3d/exportMenuHelper' import Load3DConfiguration from '@/extensions/core/load3d/Load3DConfiguration' +import Load3dUtils from '@/extensions/core/load3d/Load3dUtils' import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' import type { IContextMenuValue } from '@/lib/litegraph/src/interfaces' -import { type CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2' +import type { NodeOutputWith, ResultItem } from '@/schemas/apiSchema' + +type SaveMeshOutput = NodeOutputWith<{ + '3d'?: ResultItem[] +}> +import type { CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2' import { ComponentWidgetImpl, addWidget } from '@/scripts/domWidget' import { useExtensionService } from '@/services/extensionService' import { useLoad3dService } from '@/services/load3dService' @@ -54,6 +60,8 @@ useExtensionService().registerExtension({ const load3d = useLoad3dService().getLoad3d(node) if (!load3d) return [] + if (load3d.isSplatModel()) return [] + return createExportMenuItems(load3d) }, @@ -68,22 +76,37 @@ useExtensionService().registerExtension({ const onExecuted = node.onExecuted - node.onExecuted = function (message: any) { - onExecuted?.apply(this, arguments as any) + node.onExecuted = function (output: SaveMeshOutput) { + onExecuted?.call(this, output) - const fileInfo = message['3d'][0] + const fileInfo = output['3d']?.[0] + + if (!fileInfo) return useLoad3d(node).waitForLoad3d((load3d) => { const modelWidget = node.widgets?.find((w) => w.name === 'image') if (load3d && modelWidget) { - const filePath = fileInfo['subfolder'] + '/' + fileInfo['filename'] + const filePath = + (fileInfo.subfolder ?? '') + '/' + (fileInfo.filename ?? '') modelWidget.value = filePath const config = new Load3DConfiguration(load3d, node.properties) - config.configureForSaveMesh(fileInfo['type'], filePath) + const loadFolder = fileInfo.type as 'input' | 'output' + + const onModelLoaded = () => { + load3d.removeEventListener('modelLoadingEnd', onModelLoaded) + void Load3dUtils.generateThumbnailIfNeeded( + load3d, + filePath, + loadFolder + ) + } + load3d.addEventListener('modelLoadingEnd', onModelLoaded) + + config.configureForSaveMesh(loadFolder, filePath) } }) } diff --git a/src/extensions/core/selectionBorder.ts b/src/extensions/core/selectionBorder.ts index c4afafad1..fd3e98a91 100644 --- a/src/extensions/core/selectionBorder.ts +++ b/src/extensions/core/selectionBorder.ts @@ -1,4 +1,5 @@ -import { type LGraphCanvas, createBounds } from '@/lib/litegraph/src/litegraph' +import type { LGraphCanvas, Rectangle } from '@/lib/litegraph/src/litegraph' +import { createBounds } from '@/lib/litegraph/src/litegraph' import { app } from '@/scripts/app' /** @@ -56,7 +57,7 @@ const ext = { app.canvas.onDrawForeground = function ( ctx: CanvasRenderingContext2D, - visibleArea: any + visibleArea: Rectangle ) { // Call original if it exists originalDrawForeground?.call(this, ctx, visibleArea) diff --git a/src/extensions/core/simpleTouchSupport.ts b/src/extensions/core/simpleTouchSupport.ts index c129711ec..67215f471 100644 --- a/src/extensions/core/simpleTouchSupport.ts +++ b/src/extensions/core/simpleTouchSupport.ts @@ -1,6 +1,6 @@ import { LGraphCanvas, LiteGraph } from '@/lib/litegraph/src/litegraph' -import { app } from '../../scripts/app' +import { app } from '@/scripts/app' let touchZooming = false let touchCount = 0 @@ -82,6 +82,26 @@ app.registerExtension({ } ) + const resetTouchState = () => { + touchCount = 0 + touchZooming = false + touchTime = null + lastTouch = null + lastScale = null + touchDist = null + } + + // Reset touch state when page loses visibility (e.g., switching apps on iPad) + // This prevents touchCount from getting stuck when touchend events are missed + document.addEventListener('visibilitychange', () => { + if (document.hidden) { + resetTouchState() + } + }) + + // Also handle touchcancel which fires when touch is interrupted + app.canvasEl.parentElement?.addEventListener('touchcancel', resetTouchState) + app.canvasEl.parentElement?.addEventListener( 'touchmove', (e) => { diff --git a/src/extensions/core/uploadAudio.ts b/src/extensions/core/uploadAudio.ts index dfefc88a0..c1450632b 100644 --- a/src/extensions/core/uploadAudio.ts +++ b/src/extensions/core/uploadAudio.ts @@ -1,6 +1,5 @@ import { MediaRecorder as ExtendableMediaRecorder } from 'extendable-media-recorder' -import { useChainCallback } from '@/composables/functional/useChainCallback' import { useNodeDragAndDrop } from '@/composables/node/useNodeDragAndDrop' import { useNodeFileInput } from '@/composables/node/useNodeFileInput' import { useNodePaste } from '@/composables/node/useNodePaste' @@ -15,6 +14,7 @@ import { getResourceURL, splitFilePath } from '@/renderer/extensions/vueNodes/widgets/utils/audioUtils' +import type { NodeExecutionOutput } from '@/schemas/apiSchema' import type { ComfyNodeDef } from '@/schemas/nodeDefSchema' import type { DOMWidget } from '@/scripts/domWidget' import { useAudioService } from '@/services/audioService' @@ -24,6 +24,17 @@ import { getNodeByLocatorId } from '@/utils/graphTraversalUtil' import { api } from '../../scripts/api' import { app } from '../../scripts/app' +function updateUIWidget( + audioUIWidget: DOMWidget, + url: string = '' +) { + audioUIWidget.element.src = url + audioUIWidget.value = url + audioUIWidget.callback?.(url) + if (url) audioUIWidget.element.classList.remove('empty-audio-widget') + else audioUIWidget.element.classList.add('empty-audio-widget') +} + async function uploadFile( audioWidget: IStringWidget, audioUIWidget: DOMWidget, @@ -54,10 +65,10 @@ async function uploadFile( } if (updateNode) { - audioUIWidget.element.src = api.apiURL( - getResourceURL(...splitFilePath(path)) + updateUIWidget( + audioUIWidget, + api.apiURL(getResourceURL(...splitFilePath(path))) ) - audioWidget.value = path // Manually trigger the callback to update VueNodes audioWidget.callback?.(path) @@ -112,49 +123,47 @@ app.registerExtension({ audioUIWidget.element.classList.add('empty-audio-widget') // Populate the audio widget UI on node execution. const onExecuted = node.onExecuted - node.onExecuted = function (message: any) { - // @ts-expect-error fixme ts strict error - onExecuted?.apply(this, arguments) - const audios = message.audio - if (!audios) return + node.onExecuted = function (output: NodeExecutionOutput) { + onExecuted?.call(this, output) + const audios = output.audio + if (!audios?.length) return const audio = audios[0] - audioUIWidget.element.src = api.apiURL( - getResourceURL(audio.subfolder, audio.filename, audio.type) + const resourceUrl = getResourceURL( + audio.subfolder ?? '', + audio.filename ?? '', + audio.type ) - audioUIWidget.element.classList.remove('empty-audio-widget') + updateUIWidget(audioUIWidget, api.apiURL(resourceUrl)) } } - audioUIWidget.onRemove = useChainCallback( - audioUIWidget.onRemove, - () => { - if (!audioUIWidget.element) return - audioUIWidget.element.pause() - audioUIWidget.element.src = '' - audioUIWidget.element.remove() - } - ) + let value = '' + audioUIWidget.options.getValue = () => value + audioUIWidget.options.setValue = (v) => (value = v) return { widget: audioUIWidget } } } }, - onNodeOutputsUpdated(nodeOutputs: Record) { + onNodeOutputsUpdated( + nodeOutputs: Record + ) { for (const [nodeLocatorId, output] of Object.entries(nodeOutputs)) { - if ('audio' in output) { - const node = getNodeByLocatorId(app.graph, nodeLocatorId) - if (!node) continue + if (!output.audio?.length) continue - // @ts-expect-error fixme ts strict error - const audioUIWidget = node.widgets.find( - (w) => w.name === 'audioUI' - ) as unknown as DOMWidget - const audio = output.audio[0] - audioUIWidget.element.src = api.apiURL( - getResourceURL(audio.subfolder, audio.filename, audio.type) - ) - audioUIWidget.element.classList.remove('empty-audio-widget') - } + const node = getNodeByLocatorId(app.rootGraph, nodeLocatorId) + if (!node) continue + + const audioUIWidget = node.widgets?.find( + (w) => w.name === 'audioUI' + ) as unknown as DOMWidget + const audio = output.audio[0] + const resourceUrl = getResourceURL( + audio.subfolder ?? '', + audio.filename ?? '', + audio.type + ) + updateUIWidget(audioUIWidget, api.apiURL(resourceUrl)) } } }) @@ -178,18 +187,18 @@ app.registerExtension({ const audioUIWidget = node.widgets.find( (w) => w.name === 'audioUI' ) as unknown as DOMWidget - audioUIWidget.options.canvasOnly = true const onAudioWidgetUpdate = () => { - if (typeof audioWidget.value !== 'string') return - audioUIWidget.element.src = api.apiURL( - getResourceURL(...splitFilePath(audioWidget.value)) + updateUIWidget( + audioUIWidget, + api.apiURL( + getResourceURL(...splitFilePath(audioWidget.value ?? '')) + ) ) } // Initially load default audio file to audioUIWidget. - if (audioWidget.value) { - onAudioWidgetUpdate() - } + onAudioWidgetUpdate() + audioWidget.callback = onAudioWidgetUpdate // Load saved audio file widget values if restoring from workflow @@ -197,9 +206,7 @@ app.registerExtension({ node.onGraphConfigured = function () { // @ts-expect-error fixme ts strict error onGraphConfigured?.apply(this, arguments) - if (audioWidget.value) { - onAudioWidgetUpdate() - } + onAudioWidgetUpdate() } const handleUpload = async (files: File[]) => { @@ -323,7 +330,7 @@ app.registerExtension({ URL.revokeObjectURL(audioUIWidget.element.src) } - audioUIWidget.element.src = URL.createObjectURL(audioBlob) + updateUIWidget(audioUIWidget, URL.createObjectURL(audioBlob)) isRecording = false diff --git a/src/extensions/core/webcamCapture.ts b/src/extensions/core/webcamCapture.ts index f429ddda4..6c02229c5 100644 --- a/src/extensions/core/webcamCapture.ts +++ b/src/extensions/core/webcamCapture.ts @@ -97,7 +97,7 @@ app.registerExtension({ const img = new Image() img.onload = () => { node.imgs = [img] - app.graph.setDirtyCanvas(true) + app.canvas.setDirty(true) } img.src = data } diff --git a/src/extensions/core/widgetInputs.ts b/src/extensions/core/widgetInputs.ts index 2c2376b11..879e79976 100644 --- a/src/extensions/core/widgetInputs.ts +++ b/src/extensions/core/widgetInputs.ts @@ -1,20 +1,24 @@ -import { - type CallbackParams, - useChainCallback -} from '@/composables/functional/useChainCallback' +import { useChainCallback } from '@/composables/functional/useChainCallback' import { LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph' import type { INodeInputSlot, INodeOutputSlot, ISlotType, - LLink, - Point + LLink } from '@/lib/litegraph/src/litegraph' +import { NodeSlot } from '@/lib/litegraph/src/node/NodeSlot' import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events' -import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets' +import type { + IBaseWidget, + TWidgetValue +} from '@/lib/litegraph/src/types/widgets' import type { InputSpec } from '@/schemas/nodeDefSchema' import { app } from '@/scripts/app' -import { ComfyWidgets, addValueControlWidgets } from '@/scripts/widgets' +import { + ComfyWidgets, + addValueControlWidgets, + isValidWidgetType +} from '@/scripts/widgets' import { CONFIG, GET_CONFIG } from '@/services/litegraphService' import { mergeInputSpec } from '@/utils/nodeDefUtil' import { applyTextReplacements } from '@/utils/searchAndReplace' @@ -22,7 +26,7 @@ import { isPrimitiveNode } from '@/renderer/utils/nodeTypeGuards' const replacePropertyName = 'Run widget replace on values' export class PrimitiveNode extends LGraphNode { - controlValues?: any[] + controlValues?: TWidgetValue[] lastType?: string static override category: string constructor(title: string) { @@ -37,15 +41,15 @@ export class PrimitiveNode extends LGraphNode { } override applyToGraph(extraLinks: LLink[] = []) { - if (!this.outputs[0].links?.length) return + if (!this.outputs[0].links?.length || !this.graph) return const links = [ - ...this.outputs[0].links.map((l) => app.graph.links[l]), + ...this.outputs[0].links.map((l) => this.graph!.links[l]), ...extraLinks ] let v = this.widgets?.[0].value if (v && this.properties[replacePropertyName]) { - v = applyTextReplacements(app.graph, v as string) + v = applyTextReplacements(this.graph, v as string) } // For each output link copy our value over the original widget value @@ -223,8 +227,8 @@ export class PrimitiveNode extends LGraphNode { // Store current size as addWidget resizes the node const [oldWidth, oldHeight] = this.size - let widget: IBaseWidget | undefined - if (type in ComfyWidgets) { + let widget: IBaseWidget + if (isValidWidgetType(type)) { widget = (ComfyWidgets[type](this, 'value', inputData, app) || {}).widget } else { // @ts-expect-error InputSpec is not typed correctly @@ -253,6 +257,8 @@ export class PrimitiveNode extends LGraphNode { undefined, inputData ) + if (this.widgets?.[1]) widget.linkedWidgets = [this.widgets[1]] + let filter = this.widgets_values?.[2] if (filter && this.widgets && this.widgets.length === 3) { this.widgets[2].value = filter @@ -331,13 +337,13 @@ export class PrimitiveNode extends LGraphNode { const config1 = (output.widget?.[GET_CONFIG] as () => InputSpec)?.() if (!config1) return const isNumber = config1[0] === 'INT' || config1[0] === 'FLOAT' - if (!isNumber) return + if (!isNumber || !this.graph) return for (const linkId of links) { - const link = app.graph.links[linkId] + const link = this.graph.links[linkId] if (!link) continue // Can be null when removing a node - const theirNode = app.graph.getNodeById(link.target_id) + const theirNode = this.graph.getNodeById(link.target_id) if (!theirNode) continue const theirInput = theirNode.inputs[link.target_slot] @@ -441,10 +447,7 @@ function getWidgetType(config: InputSpec) { return { type } } -export function setWidgetConfig( - slot: INodeInputSlot | INodeOutputSlot, - config?: InputSpec -) { +export function setWidgetConfig(slot: INodeInputSlot, config?: InputSpec) { if (!slot.widget) return if (config) { slot.widget[GET_CONFIG] = () => config @@ -452,19 +455,18 @@ export function setWidgetConfig( delete slot.widget } - if ('link' in slot) { - const link = app.graph.links[slot.link ?? -1] - if (link) { - const originNode = app.graph.getNodeById(link.origin_id) - if (originNode && isPrimitiveNode(originNode)) { - if (config) { - originNode.recreateWidget() - } else if (!app.configuringGraph) { - originNode.disconnectOutput(0) - originNode.onLastDisconnect() - } - } - } + if (!(slot instanceof NodeSlot)) return + const graph = slot.node.graph + if (!graph) return + const link = graph.links[slot.link ?? -1] + if (!link) return + const originNode = graph.getNodeById(link.origin_id) + if (!originNode || !isPrimitiveNode(originNode)) return + if (config) { + originNode.recreateWidget() + } else if (!app.configuringGraph) { + originNode.disconnectOutput(0) + originNode.onLastDisconnect() } } @@ -555,20 +557,11 @@ app.registerExtension({ } ) - function isNodeAtPos(pos: Point) { - for (const n of app.graph.nodes) { - if (n.pos[0] === pos[0] && n.pos[1] === pos[1]) { - return true - } - } - return false - } - // Double click a widget input to automatically attach a primitive const origOnInputDblClick = nodeType.prototype.onInputDblClick nodeType.prototype.onInputDblClick = function ( this: LGraphNode, - ...[slot, ...args]: CallbackParams + ...[slot, ...args]: Parameters> ) { const r = origOnInputDblClick?.apply(this, [slot, ...args]) @@ -589,18 +582,18 @@ app.registerExtension({ // Create a primitive node const node = LiteGraph.createNode('PrimitiveNode') - if (!node) return r + const graph = app.canvas.graph + if (!node || !graph) return r - this.graph?.add(node) + graph?.add(node) // Calculate a position that won't directly overlap another node const pos: [number, number] = [ this.pos[0] - node.size[0] - 30, this.pos[1] ] - while (isNodeAtPos(pos)) { + while (graph.getNodeOnPos(pos[0], pos[1], graph.nodes)) pos[1] += LiteGraph.NODE_TITLE_HEIGHT - } node.pos = pos node.connect(0, this, slot) diff --git a/src/i18n.test.ts b/src/i18n.test.ts new file mode 100644 index 000000000..1cb2b673b --- /dev/null +++ b/src/i18n.test.ts @@ -0,0 +1,200 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +const { i18n, loadLocale, mergeCustomNodesI18n } = await import('./i18n') + +// Mock the JSON imports before importing i18n module +vi.mock('./locales/en/main.json', () => ({ default: { welcome: 'Welcome' } })) +vi.mock('./locales/en/nodeDefs.json', () => ({ + default: { testNode: 'Test Node' } +})) +vi.mock('./locales/en/commands.json', () => ({ + default: { save: 'Save' } +})) +vi.mock('./locales/en/settings.json', () => ({ + default: { theme: 'Theme' } +})) + +// Mock lazy-loaded locales +vi.mock('./locales/zh/main.json', () => ({ default: { welcome: '欢迎' } })) +vi.mock('./locales/zh/nodeDefs.json', () => ({ + default: { testNode: '测试节点' } +})) +vi.mock('./locales/zh/commands.json', () => ({ default: { save: '保存' } })) +vi.mock('./locales/zh/settings.json', () => ({ default: { theme: '主题' } })) + +describe('i18n', () => { + beforeEach(async () => { + vi.resetModules() + }) + + describe('mergeCustomNodesI18n', () => { + it('should immediately merge data for already loaded locales (en)', async () => { + // English is pre-loaded, so merge should work immediately + mergeCustomNodesI18n({ + en: { + customNode: { + title: 'Custom Node Title' + } + } + }) + + // Verify the custom node data was merged + const messages = i18n.global.getLocaleMessage('en') as Record< + string, + unknown + > + expect(messages.customNode).toEqual({ title: 'Custom Node Title' }) + }) + + it('should store data for not-yet-loaded locales', async () => { + const { i18n, mergeCustomNodesI18n } = await import('./i18n') + + // Chinese is not pre-loaded, data should be stored but not merged yet + mergeCustomNodesI18n({ + zh: { + customNode: { + title: '自定义节点标题' + } + } + }) + + // zh locale should not exist yet (not loaded) + const zhMessages = i18n.global.getLocaleMessage('zh') as Record< + string, + unknown + > + // Either empty or doesn't have our custom data merged directly + // (since zh wasn't loaded yet, mergeLocaleMessage on non-existent locale + // may create an empty locale or do nothing useful) + expect(zhMessages.customNode).toBeUndefined() + }) + + it('should merge stored data when locale is lazily loaded', async () => { + // First, store custom nodes i18n data (before locale is loaded) + mergeCustomNodesI18n({ + zh: { + customNode: { + title: '自定义节点标题' + } + } + }) + + await loadLocale('zh') + + // Verify both the base locale data and custom node data are present + const zhMessages = i18n.global.getLocaleMessage('zh') as Record< + string, + unknown + > + expect(zhMessages.welcome).toBe('欢迎') + expect(zhMessages.customNode).toEqual({ title: '自定义节点标题' }) + }) + + it('should preserve custom node data when locale is loaded after merge', async () => { + // Simulate the real scenario: + // 1. Custom nodes i18n is loaded first + mergeCustomNodesI18n({ + zh: { + customNode: { + title: '自定义节点标题' + }, + settingsCategories: { + Hotkeys: '快捷键' + } + } + }) + + // 2. Then locale is lazily loaded (this would previously overwrite custom data) + await loadLocale('zh') + + // 3. Verify custom node data is still present + const zhMessages = i18n.global.getLocaleMessage('zh') as Record< + string, + unknown + > + expect(zhMessages.customNode).toEqual({ title: '自定义节点标题' }) + expect(zhMessages.settingsCategories).toEqual({ Hotkeys: '快捷键' }) + + // 4. Also verify base locale data is present + expect(zhMessages.welcome).toBe('欢迎') + expect(zhMessages.nodeDefs).toEqual({ testNode: '测试节点' }) + }) + + it('should handle multiple locales in custom nodes i18n data', async () => { + // Merge data for multiple locales + mergeCustomNodesI18n({ + en: { + customPlugin: { name: 'Easy Use' } + }, + zh: { + customPlugin: { name: '简单使用' } + } + }) + + // English should be merged immediately (pre-loaded) + const enMessages = i18n.global.getLocaleMessage('en') as Record< + string, + unknown + > + expect(enMessages.customPlugin).toEqual({ name: 'Easy Use' }) + + await loadLocale('zh') + const zhMessages = i18n.global.getLocaleMessage('zh') as Record< + string, + unknown + > + expect(zhMessages.customPlugin).toEqual({ name: '简单使用' }) + }) + + it('should handle calling mergeCustomNodesI18n multiple times', async () => { + // Use fresh module instance to ensure clean state + vi.resetModules() + const { i18n, loadLocale, mergeCustomNodesI18n } = await import('./i18n') + + mergeCustomNodesI18n({ + zh: { plugin1: { name: '插件1' } } + }) + + mergeCustomNodesI18n({ + zh: { plugin2: { name: '插件2' } } + }) + + await loadLocale('zh') + + const zhMessages = i18n.global.getLocaleMessage('zh') as Record< + string, + unknown + > + // Only the second call's data should be present + expect(zhMessages.plugin2).toEqual({ name: '插件2' }) + // First call's data is overwritten + expect(zhMessages.plugin1).toBeUndefined() + }) + }) + + describe('loadLocale', () => { + it('should not reload already loaded locale', async () => { + await loadLocale('zh') + await loadLocale('zh') + + // Should complete without error (second call returns early) + }) + + it('should warn for unsupported locale', async () => { + const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) + + await loadLocale('unsupported-locale') + + expect(consoleSpy).toHaveBeenCalledWith( + 'Locale "unsupported-locale" is not supported' + ) + consoleSpy.mockRestore() + }) + + it('should handle concurrent load requests for same locale', async () => { + // Start multiple loads concurrently + const promises = [loadLocale('zh'), loadLocale('zh'), loadLocale('zh')] + + await Promise.all(promises) + }) + }) +}) diff --git a/src/i18n.ts b/src/i18n.ts index d3e34245c..e0ee54e2b 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -30,13 +30,15 @@ const localeLoaders: Record< > = { ar: () => import('./locales/ar/main.json'), es: () => import('./locales/es/main.json'), + fa: () => import('./locales/fa/main.json'), fr: () => import('./locales/fr/main.json'), ja: () => import('./locales/ja/main.json'), ko: () => import('./locales/ko/main.json'), ru: () => import('./locales/ru/main.json'), tr: () => import('./locales/tr/main.json'), zh: () => import('./locales/zh/main.json'), - 'zh-TW': () => import('./locales/zh-TW/main.json') + 'zh-TW': () => import('./locales/zh-TW/main.json'), + 'pt-BR': () => import('./locales/pt-BR/main.json') } const nodeDefsLoaders: Record< @@ -45,13 +47,15 @@ const nodeDefsLoaders: Record< > = { ar: () => import('./locales/ar/nodeDefs.json'), es: () => import('./locales/es/nodeDefs.json'), + fa: () => import('./locales/fa/nodeDefs.json'), fr: () => import('./locales/fr/nodeDefs.json'), ja: () => import('./locales/ja/nodeDefs.json'), ko: () => import('./locales/ko/nodeDefs.json'), ru: () => import('./locales/ru/nodeDefs.json'), tr: () => import('./locales/tr/nodeDefs.json'), zh: () => import('./locales/zh/nodeDefs.json'), - 'zh-TW': () => import('./locales/zh-TW/nodeDefs.json') + 'zh-TW': () => import('./locales/zh-TW/nodeDefs.json'), + 'pt-BR': () => import('./locales/pt-BR/nodeDefs.json') } const commandsLoaders: Record< @@ -60,13 +64,15 @@ const commandsLoaders: Record< > = { ar: () => import('./locales/ar/commands.json'), es: () => import('./locales/es/commands.json'), + fa: () => import('./locales/fa/commands.json'), fr: () => import('./locales/fr/commands.json'), ja: () => import('./locales/ja/commands.json'), ko: () => import('./locales/ko/commands.json'), ru: () => import('./locales/ru/commands.json'), tr: () => import('./locales/tr/commands.json'), zh: () => import('./locales/zh/commands.json'), - 'zh-TW': () => import('./locales/zh-TW/commands.json') + 'zh-TW': () => import('./locales/zh-TW/commands.json'), + 'pt-BR': () => import('./locales/pt-BR/commands.json') } const settingsLoaders: Record< @@ -75,13 +81,15 @@ const settingsLoaders: Record< > = { ar: () => import('./locales/ar/settings.json'), es: () => import('./locales/es/settings.json'), + fa: () => import('./locales/fa/settings.json'), fr: () => import('./locales/fr/settings.json'), ja: () => import('./locales/ja/settings.json'), ko: () => import('./locales/ko/settings.json'), ru: () => import('./locales/ru/settings.json'), tr: () => import('./locales/tr/settings.json'), zh: () => import('./locales/zh/settings.json'), - 'zh-TW': () => import('./locales/zh-TW/settings.json') + 'zh-TW': () => import('./locales/zh-TW/settings.json'), + 'pt-BR': () => import('./locales/pt-BR/settings.json') } // Track which locales have been loaded @@ -90,6 +98,9 @@ const loadedLocales = new Set(['en']) // Track locales currently being loaded to prevent race conditions const loadingLocales = new Map>() +// Store custom nodes i18n data for merging when locales are lazily loaded +const customNodesI18nData: Record = {} + /** * Dynamically load a locale and its associated files (nodeDefs, commands, settings) */ @@ -133,6 +144,10 @@ export async function loadLocale(locale: string): Promise { i18n.global.setLocaleMessage(locale, messages as LocaleMessages) loadedLocales.add(locale) + + if (customNodesI18nData[locale]) { + i18n.global.mergeLocaleMessage(locale, customNodesI18nData[locale]) + } } catch (error) { console.error(`Failed to load locale "${locale}":`, error) throw error @@ -146,6 +161,24 @@ export async function loadLocale(locale: string): Promise { return loadPromise } +/** + * Stores the data for later use when locales are lazily loaded, + * and immediately merges data for already-loaded locales. + */ +export function mergeCustomNodesI18n(i18nData: Record): void { + // Clear existing data and replace with new data + for (const key of Object.keys(customNodesI18nData)) { + delete customNodesI18nData[key] + } + Object.assign(customNodesI18nData, i18nData) + + for (const [locale, message] of Object.entries(i18nData)) { + if (loadedLocales.has(locale)) { + i18n.global.mergeLocaleMessage(locale, message) + } + } +} + // Only include English in the initial bundle const messages = { en: buildLocale(en, enNodes, enCommands, enSettings) @@ -159,6 +192,7 @@ export const i18n = createI18n({ legacy: false, locale: navigator.language.split('-')[0] || 'en', fallbackLocale: 'en', + escapeParameter: true, messages, // Ignore warnings for locale options as each option is in its own language. // e.g. "English", "中文", "Русский", "日本語", "한국어", "Français", "Español" @@ -167,6 +201,7 @@ export const i18n = createI18n({ }) /** Convenience shorthand: i18n.global */ +/** @deprecated use useI18n */ export const { t, te, d } = i18n.global /** @@ -176,5 +211,6 @@ export const { t, te, d } = i18n.global * @param fallbackMessage - The fallback message to use if the key is not found. */ export function st(key: string, fallbackMessage: string) { + // The normal defaultMsg overload fails in some cases for custom nodes return te(key) ? t(key) : fallbackMessage } diff --git a/src/lib/litegraph/AGENTS.md b/src/lib/litegraph/AGENTS.md new file mode 100644 index 000000000..8480cc00a --- /dev/null +++ b/src/lib/litegraph/AGENTS.md @@ -0,0 +1,41 @@ +# Litegraph Guidelines + +## Code Philosophy + +- Write concise, legible, and easily maintainable code +- Avoid repetition where possible, but not at expense of legibility +- Prefer running single tests, not the whole suite, for performance + +## Code Style + +- Prefer single line `if` syntax for concise expressions +- Take advantage of `TypedArray` `subarray` when appropriate +- The `size` and `pos` properties of `Rectangle` share the same array buffer +- Prefer returning `undefined` over `null` +- Type assertions are a last resort (acceptable for legacy code interop) + +## Circular Dependencies in Tests + +**CRITICAL**: Always import from the barrel export for subgraph code: + +```typescript +// ✅ Correct - barrel import +import { LGraph, Subgraph, SubgraphNode } from "@/lib/litegraph/src/litegraph" + +// ❌ Wrong - causes circular dependency +import { LGraph } from "@/lib/litegraph/src/LGraph" +``` + +**Root cause**: `LGraph` ↔ `Subgraph` circular dependency (Subgraph extends LGraph, LGraph creates Subgraph instances). + +## Test Helpers + +```typescript +import { createTestSubgraph, createTestSubgraphNode } from "./fixtures/subgraphHelpers" + +function createTestSetup() { + const subgraph = createTestSubgraph() + const subgraphNode = createTestSubgraphNode(subgraph) + return { subgraph, subgraphNode } +} +``` diff --git a/src/lib/litegraph/CLAUDE.md b/src/lib/litegraph/CLAUDE.md index 68f8bea95..631093341 100644 --- a/src/lib/litegraph/CLAUDE.md +++ b/src/lib/litegraph/CLAUDE.md @@ -1,62 +1,3 @@ -- This codebase has extensive eslint autofix rules and IDEs are configured to use eslint as the format on save tool. Run ESLint instead of manually figuring out whitespace fixes or other trivial style concerns. Review the results and correct any remaining eslint errors. -- Take advantage of `TypedArray` `subarray` when appropriate. -- The `size` and `pos` properties of `Rectangle` share the same array buffer (`subarray`); they may be used to set the rectangles size and position. -- Prefer single line `if` syntax over adding curly braces, when the statement has a very concise expression and concise, single line statement. -- Do not replace `&&=` or `||=` with `=` when there is no reason to do so. If you do find a reason to remove either `&&=` or `||=`, leave a comment explaining why the removal occurred. -- You are allowed to research code on https://developer.mozilla.org/ and https://stackoverflow.com without asking. -- When adding features, always write vitest unit tests using cursor rules in @.cursor -- When writing methods, prefer returning idiomatic JavaScript `undefined` over `null`. - -# Bash commands - -- `pnpm typecheck` Run the typechecker -- `pnpm build` Build the project -- `pnpm lint:fix` Run ESLint - -# Code style - -- Always prefer best practices when writing code. -- Write using concise, legible, and easily maintainable code. -- Avoid repetition where possible, but not at the expense of code legibility. -- Type assertions are an absolute last resort. In almost all cases, they are a crutch that leads to brittle code. - -# Workflow - -- Be sure to typecheck when you're done making a series of code changes -- Prefer running single tests, and not the whole test suite, for performance - -# Testing Guidelines - -## Avoiding Circular Dependencies in Tests - -**CRITICAL**: When writing tests for subgraph-related code, always import from the barrel export to avoid circular dependency issues: - -```typescript -// ✅ CORRECT - Use barrel import -import { LGraph, Subgraph, SubgraphNode } from "@/lib/litegraph/src/litegraph" - -// ❌ WRONG - Direct imports cause circular dependency -import { LGraph } from "@/lib/litegraph/src/LGraph" -import { Subgraph } from "@/lib/litegraph/src/subgraph/Subgraph" -import { SubgraphNode } from "@/lib/litegraph/src/subgraph/SubgraphNode" -``` - -**Root cause**: `LGraph` and `Subgraph` have a circular dependency: -- `LGraph.ts` imports `Subgraph` (creates instances with `new Subgraph()`) -- `Subgraph.ts` extends `LGraph` - -The barrel export (`@/litegraph`) handles this properly, but direct imports cause module loading failures. - -## Test Setup for Subgraphs - -Use the provided test helpers for consistent setup: - -```typescript -import { createTestSubgraph, createTestSubgraphNode } from "./fixtures/subgraphHelpers" - -function createTestSetup() { - const subgraph = createTestSubgraph() - const subgraphNode = createTestSubgraphNode(subgraph) - return { subgraph, subgraphNode } -} -``` + +@AGENTS.md diff --git a/src/lib/litegraph/public/css/litegraph.css b/src/lib/litegraph/public/css/litegraph.css index 0dc83dca9..83b36dd98 100644 --- a/src/lib/litegraph/public/css/litegraph.css +++ b/src/lib/litegraph/public/css/litegraph.css @@ -6,7 +6,6 @@ -moz-user-select: none; -webkit-user-select: none; outline: none; - font-family: Tahoma, sans-serif; } .lgraphcanvas * { @@ -14,15 +13,14 @@ } .litegraph.litecontextmenu { - font-family: Tahoma, sans-serif; position: fixed; top: 100px; left: 100px; min-width: 100px; color: #aaf; padding: 0; - box-shadow: 0 0 10px black !important; - background-color: #2e2e2e !important; + box-shadow: 0 0 10px black; + background-color: #2e2e2e; z-index: 10; max-height: -webkit-fill-available; overflow-y: auto; @@ -36,10 +34,6 @@ } } -.litegraph.litecontextmenu.dark { - background-color: #000 !important; -} - .litegraph.litecontextmenu .litemenu-title img { margin-top: 2px; margin-left: 2px; @@ -51,16 +45,7 @@ padding: 2px; } -.litegraph.litecontextmenu .litemenu-entry.submenu { - background-color: #2e2e2e !important; -} - -.litegraph.litecontextmenu.dark .litemenu-entry.submenu { - background-color: #000 !important; -} - .litegraph .litemenubar ul { - font-family: Tahoma, sans-serif; margin: 0; padding: 0; } @@ -132,14 +117,13 @@ .litegraph .litemenu-entry.separator { display: block; - border-top: 1px solid #333; - border-bottom: 1px solid #666; + border-top: 1px solid var(--border-default); width: 100%; height: 0px; margin: 3px 0 2px 0; background-color: transparent; - padding: 0 !important; - cursor: default !important; + padding: 0; + cursor: default; } .litegraph .litemenu-entry.has_submenu { @@ -155,8 +139,8 @@ } .litegraph .litemenu-entry:hover:not(.disabled):not(.separator) { - background-color: #444 !important; - color: #eee; + background-color: var(--palette-interface-panel-hover-surface); + color: var(--content-hover-fg); transition: all 0.2s; } @@ -178,7 +162,6 @@ } .litegraph.litesearchbox { - font-family: Tahoma, sans-serif; position: absolute; background-color: rgba(0, 0, 0, 0.5); padding-top: 4px; @@ -211,7 +194,6 @@ } .litegraph.lite-search-item { - font-family: Tahoma, sans-serif; background-color: rgba(0, 0, 0, 0.5); color: white; padding-top: 2px; @@ -259,7 +241,8 @@ margin-top: -150px; margin-left: -200px; - background-color: #2a2a2a; + color: var(--base-foreground); + background-color: var(--comfy-menu-bg); min-width: 400px; min-height: 200px; @@ -299,8 +282,7 @@ } .litegraph .dialog .dialog-header { - color: #aaa; - border-bottom: 1px solid #161616; + border-bottom: 1px solid var(--border-default); } .litegraph .dialog .dialog-header { @@ -310,11 +292,11 @@ height: 50px; padding: 10px; margin: 0; - border-top: 1px solid #1a1a1a; + border-top: 1px solid var(--border-default); } .litegraph .dialog .dialog-header .dialog-title { - font: 20px "Arial"; + font: 1rem; margin: 4px; padding: 4px 10px; display: inline-block; @@ -326,7 +308,7 @@ width: 100%; min-height: 100px; display: inline-block; - color: #aaa; + /* color: #aaa; */ /*background-color: black;*/ overflow: auto; } @@ -362,8 +344,7 @@ display: block; width: calc(100% - 4px); height: 1px; - border-top: 1px solid #000; - border-bottom: 1px solid #333; + border-top: 1px solid var(--border-default); margin: 10px 2px; padding: 0; } @@ -373,12 +354,8 @@ padding: 4px; } -.litegraph .dialog .property:hover { - background: #545454; -} - .litegraph .dialog .property_name { - color: #737373; + color: var(--muted-foreground); display: inline-block; text-align: left; vertical-align: top; @@ -395,8 +372,8 @@ .litegraph .dialog .property_value { display: inline-block; text-align: right; - color: #aaa; - background-color: #1a1a1a; + color: var(--input-text); + background-color: var(--component-node-widget-background); /*width: calc( 100% - 122px );*/ max-width: calc(100% - 162px); min-width: 200px; @@ -432,18 +409,18 @@ border-radius: 4px; padding: 4px 20px; margin-left: 0px; - background-color: #060606; - color: #8e8e8e; + background-color: var(--secondary-background); + color: var(--base-foreground); } .litegraph .dialog .btn:hover { - background-color: #111; - color: #fff; + background-color: var(--secondary-background-hover); + color: var(--base-foreground); } .litegraph .dialog .btn.delete:hover { - background-color: #f33; - color: black; + background-color: var(--color-danger-100); + color: var(--base-foreground); } .litegraph .bullet_icon { @@ -497,11 +474,11 @@ .graphmenu-entry.danger, .litemenu-entry.danger { - color: var(--error-text) !important; + color: var(--error-text); } .litegraph .litemenu-entry.danger:hover:not(.disabled) { - color: var(--error-text) !important; + color: var(--error-text); opacity: 0.8; } @@ -518,8 +495,7 @@ } .graphmenu-entry.separator { - background-color: #111; - border-bottom: 1px solid #666; + background-color: var(--border-default); height: 1px; width: calc(100% - 20px); -moz-width: calc(100% - 20px); @@ -551,7 +527,7 @@ min-height: 2em; background-color: #333; font-size: 1.2em; - box-shadow: 0 0 10px black !important; + box-shadow: 0 0 10px black; z-index: 10; } diff --git a/tests-ui/tests/litegraph/utils/CanvasPointer.deviceDetection.test.ts b/src/lib/litegraph/src/CanvasPointer.deviceDetection.test.ts similarity index 100% rename from tests-ui/tests/litegraph/utils/CanvasPointer.deviceDetection.test.ts rename to src/lib/litegraph/src/CanvasPointer.deviceDetection.test.ts diff --git a/src/lib/litegraph/src/DragAndScale.ts b/src/lib/litegraph/src/DragAndScale.ts index c71fca2d2..d2e35252d 100644 --- a/src/lib/litegraph/src/DragAndScale.ts +++ b/src/lib/litegraph/src/DragAndScale.ts @@ -192,8 +192,14 @@ export class DragAndScale { bounds: ReadOnlyRect, { zoom = 0.75 }: { zoom?: number } = {} ): void { - const cw = this.element.width / window.devicePixelRatio - const ch = this.element.height / window.devicePixelRatio + //If element hasn't initialized (browser tab is in background) + //it has a size of 300x150 and a more reasonable default is used instead. + const [width, height] = + this.element.width === 300 && this.element.height === 150 + ? [1920, 1080] + : [this.element.width, this.element.height] + const cw = width / window.devicePixelRatio + const ch = height / window.devicePixelRatio let targetScale = this.scale if (zoom > 0) { diff --git a/tests-ui/tests/litegraph/core/ConfigureGraph.test.ts b/src/lib/litegraph/src/LGraph.configure.test.ts similarity index 92% rename from tests-ui/tests/litegraph/core/ConfigureGraph.test.ts rename to src/lib/litegraph/src/LGraph.configure.test.ts index 935ec1f28..2401dfe30 100644 --- a/tests-ui/tests/litegraph/core/ConfigureGraph.test.ts +++ b/src/lib/litegraph/src/LGraph.configure.test.ts @@ -3,7 +3,7 @@ import { describe } from 'vitest' import { LGraph } from '@/lib/litegraph/src/litegraph' -import { dirtyTest } from './fixtures/testExtensions' +import { dirtyTest } from './__fixtures__/testExtensions' describe.skip('LGraph configure()', () => { dirtyTest( diff --git a/tests-ui/tests/litegraph/core/LGraph_constructor.test.ts b/src/lib/litegraph/src/LGraph.constructor.test.ts similarity index 90% rename from tests-ui/tests/litegraph/core/LGraph_constructor.test.ts rename to src/lib/litegraph/src/LGraph.constructor.test.ts index 62720b9c0..4e214cae5 100644 --- a/tests-ui/tests/litegraph/core/LGraph_constructor.test.ts +++ b/src/lib/litegraph/src/LGraph.constructor.test.ts @@ -3,7 +3,7 @@ import { describe } from 'vitest' import { LGraph } from '@/lib/litegraph/src/litegraph' -import { dirtyTest } from './fixtures/testExtensions' +import { dirtyTest } from './__fixtures__/testExtensions' describe.skip('LGraph (constructor only)', () => { dirtyTest( diff --git a/tests-ui/tests/litegraph/core/serialise.test.ts b/src/lib/litegraph/src/LGraph.serialise.test.ts similarity index 94% rename from tests-ui/tests/litegraph/core/serialise.test.ts rename to src/lib/litegraph/src/LGraph.serialise.test.ts index 629c50e98..3eee37c6a 100644 --- a/tests-ui/tests/litegraph/core/serialise.test.ts +++ b/src/lib/litegraph/src/LGraph.serialise.test.ts @@ -3,7 +3,7 @@ import { describe } from 'vitest' import { LGraph, LGraphGroup, LGraphNode } from '@/lib/litegraph/src/litegraph' import type { ISerialisedGraph } from '@/lib/litegraph/src/litegraph' -import { test } from './fixtures/testExtensions' +import { test } from './__fixtures__/testExtensions' describe('LGraph Serialisation', () => { test('can (de)serialise node / group titles', ({ expect, minimalGraph }) => { diff --git a/tests-ui/tests/litegraph/core/LGraph.test.ts b/src/lib/litegraph/src/LGraph.test.ts similarity index 86% rename from tests-ui/tests/litegraph/core/LGraph.test.ts rename to src/lib/litegraph/src/LGraph.test.ts index 61d3da734..1c2f38da2 100644 --- a/tests-ui/tests/litegraph/core/LGraph.test.ts +++ b/src/lib/litegraph/src/LGraph.test.ts @@ -1,10 +1,43 @@ -import { describe } from 'vitest' +import { describe, expect, it } from 'vitest' import { LGraph, LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph' -import { test } from './fixtures/testExtensions' +import { test } from './__fixtures__/testExtensions' + +function swapNodes(nodes: LGraphNode[]) { + const firstNode = nodes[0] + const lastNode = nodes[nodes.length - 1] + nodes[0] = lastNode + nodes[nodes.length - 1] = firstNode + return nodes +} + +function createGraph(...nodes: LGraphNode[]) { + const graph = new LGraph() + nodes.forEach((node) => graph.add(node)) + return graph +} + +class DummyNode extends LGraphNode { + constructor() { + super('dummy') + } +} describe('LGraph', () => { + it('should serialize deterministic node order', async () => { + LiteGraph.registerNodeType('dummy', DummyNode) + const node1 = new DummyNode() + const node2 = new DummyNode() + const graph = createGraph(node1, node2) + + const result1 = graph.serialize({ sortNodes: true }) + expect(result1.nodes).not.toHaveLength(0) + graph._nodes = swapNodes(graph.nodes) + const result2 = graph.serialize({ sortNodes: true }) + + expect(result1).toEqual(result2) + }) test('can be instantiated', ({ expect }) => { // @ts-expect-error Intentional - extra holds any / all consumer data that should be serialised const graph = new LGraph({ extra: 'TestGraph' }) diff --git a/src/lib/litegraph/src/LGraph.ts b/src/lib/litegraph/src/LGraph.ts index d5d6c1820..49ad35101 100644 --- a/src/lib/litegraph/src/LGraph.ts +++ b/src/lib/litegraph/src/LGraph.ts @@ -15,7 +15,7 @@ import { LGraphGroup } from './LGraphGroup' import { LGraphNode } from './LGraphNode' import type { NodeId } from './LGraphNode' import { LLink } from './LLink' -import type { LinkId } from './LLink' +import type { LinkId, SerialisedLLinkArray } from './LLink' import { MapProxyHandler } from './MapProxyHandler' import { Reroute } from './Reroute' import type { RerouteId } from './Reroute' @@ -98,8 +98,27 @@ type ParamsArray< /** Configuration used by {@link LGraph} `config`. */ export interface LGraphConfig { /** @deprecated Legacy config - unused */ - align_to_grid?: any - links_ontop?: any + align_to_grid?: boolean + links_ontop?: boolean +} + +export interface GroupNodeConfigEntry { + input?: Record + output?: Record +} + +export interface GroupNodeWorkflowData { + external: (number | string)[][] + links: SerialisedLLinkArray[] + nodes: { + index?: number + type?: string + title?: string + inputs?: unknown[] + outputs?: unknown[] + widgets_values?: unknown[] + }[] + config?: Record } export interface LGraphExtra extends Dictionary { @@ -107,6 +126,7 @@ export interface LGraphExtra extends Dictionary { linkExtensions?: { id: number; parentId: number | undefined }[] ds?: DragAndScaleState workflowRendererVersion?: RendererType + groupNodes?: Record } export interface BaseLGraph { @@ -829,8 +849,13 @@ export class LGraph if (!list_of_graphcanvas) return for (const c of list_of_graphcanvas) { - // eslint-disable-next-line prefer-spread - c[action]?.apply(c, params) + const method = c[action] + + if (typeof method === 'function') { + const args = + params == null ? [] : Array.isArray(params) ? params : [params] + ;(method as (...args: unknown[]) => unknown).apply(c, args) + } } } @@ -1217,7 +1242,7 @@ export class LGraph } /** @todo Clean up - never implemented. */ - triggerInput(name: string, value: any): void { + triggerInput(name: string, value: unknown): void { const nodes = this.findNodesByTitle(name) for (const node of nodes) { // @ts-expect-error - onTrigger method may not exist on all node types @@ -1226,7 +1251,7 @@ export class LGraph } /** @todo Clean up - never implemented. */ - setCallback(name: string, func: any): void { + setCallback(name: string, func?: () => void): void { const nodes = this.findNodesByTitle(name) for (const node of nodes) { // @ts-expect-error - setTrigger method may not exist on all node types @@ -1509,6 +1534,22 @@ export class LGraph } { if (items.size === 0) throw new Error('Cannot convert to subgraph: nothing to convert') + + // Record state before conversion for proper undo support + this.beforeChange() + + try { + return this._convertToSubgraphImpl(items) + } finally { + // Mark state change complete for proper undo support + this.afterChange() + } + } + + private _convertToSubgraphImpl(items: Set): { + subgraph: Subgraph + node: SubgraphNode + } { const { state, revision, config } = this const firstChild = [...items][0] if (items.size === 1 && firstChild instanceof LGraphGroup) { @@ -1537,8 +1578,21 @@ export class LGraph // Inputs, outputs, and links const links = internalLinks.map((x) => x.asSerialisable()) - const inputs = mapSubgraphInputsAndLinks(resolvedInputLinks, links) - const outputs = mapSubgraphOutputsAndLinks(resolvedOutputLinks, links) + + const internalReroutes = new Map([...reroutes].map((r) => [r.id, r])) + const externalReroutes = new Map( + [...this.reroutes].filter(([id]) => !internalReroutes.has(id)) + ) + const inputs = mapSubgraphInputsAndLinks( + resolvedInputLinks, + links, + internalReroutes + ) + const outputs = mapSubgraphOutputsAndLinks( + resolvedOutputLinks, + links, + externalReroutes + ) // Prepare subgraph data const data = { @@ -1680,10 +1734,10 @@ export class LGraph // Reconnect output links in parent graph i = 0 for (const [, connections] of outputsGroupedByOutput.entries()) { - // Special handling: Subgraph output node i++ for (const connection of connections) { const { input, inputNode, link, subgraphOutput } = connection + // Special handling: Subgraph output node if (link.target_id === SUBGRAPH_OUTPUT_ID) { link.origin_id = subgraphNode.id link.origin_slot = i - 1 @@ -1715,6 +1769,7 @@ export class LGraph subgraphNode._setConcreteSlots() subgraphNode.arrange() + this.canvasAction((c) => c.canvas.dispatchEvent( new CustomEvent('subgraph-converted', { @@ -1726,10 +1781,30 @@ export class LGraph return { subgraph, node: subgraphNode as SubgraphNode } } - unpackSubgraph(subgraphNode: SubgraphNode) { + unpackSubgraph( + subgraphNode: SubgraphNode, + options?: { skipMissingNodes?: boolean } + ) { if (!(subgraphNode instanceof SubgraphNode)) throw new Error('Can only unpack Subgraph Nodes') + + // Record state before unpacking for proper undo support this.beforeChange() + + try { + this._unpackSubgraphImpl(subgraphNode, options) + } finally { + // Mark state change complete for proper undo support + this.afterChange() + } + } + + private _unpackSubgraphImpl( + subgraphNode: SubgraphNode, + options?: { skipMissingNodes?: boolean } + ) { + const skipMissingNodes = options?.skipMissingNodes ?? false + //NOTE: Create bounds can not be called on positionables directly as the subgraph is not being displayed and boundingRect is not initialized. //NOTE: NODE_TITLE_HEIGHT is explicitly excluded here const positionables = [ @@ -1750,9 +1825,21 @@ export class LGraph const movedNodes = multiClone(subgraphNode.subgraph.nodes) const nodeIdMap = new Map() for (const n_info of movedNodes) { - const node = LiteGraph.createNode(String(n_info.type), n_info.title) + let node = LiteGraph.createNode(String(n_info.type), n_info.title) if (!node) { - throw new Error('Node not found') + if (skipMissingNodes) { + console.warn( + `Cannot unpack node of type "${n_info.type}" - node type not found. Creating placeholder node.` + ) + node = new LGraphNode(n_info.title || n_info.type || 'Missing Node') + node.last_serialization = n_info + node.has_errors = true + node.type = String(n_info.type) + } else { + throw new Error( + `Cannot unpack: node type "${n_info.type}" is not registered` + ) + } } nodeIdMap.set(n_info.id, ++this.last_node_id) @@ -1946,33 +2033,50 @@ export class LGraph while (parentId) { instance.parentId = parentId instance = this.reroutes.get(parentId) - if (!instance) throw new Error('Broken Id link when unpacking') + if (!instance) { + console.error('Broken Id link when unpacking') + break + } if (instance.linkIds.has(linkInstance.id)) throw new Error('Infinite parentId loop') instance.linkIds.add(linkInstance.id) parentId = instance.parentId } } + if (!instance) continue parentId = newLink.iparent while (parentId) { const migratedId = rerouteIdMap.get(parentId) - if (!migratedId) throw new Error('Broken Id link when unpacking') + if (!migratedId) { + console.error('Broken Id link when unpacking') + break + } instance.parentId = migratedId instance = this.reroutes.get(migratedId) - if (!instance) throw new Error('Broken Id link when unpacking') + if (!instance) { + console.error('Broken Id link when unpacking') + break + } if (instance.linkIds.has(linkInstance.id)) throw new Error('Infinite parentId loop') instance.linkIds.add(linkInstance.id) const oldReroute = subgraphNode.subgraph.reroutes.get(parentId) - if (!oldReroute) throw new Error('Broken Id link when unpacking') + if (!oldReroute) { + console.error('Broken Id link when unpacking') + break + } parentId = oldReroute.parentId } + if (!instance) break if (!newLink.externalFirst) { parentId = newLink.eparent while (parentId) { instance.parentId = parentId instance = this.reroutes.get(parentId) - if (!instance) throw new Error('Broken Id link when unpacking') + if (!instance) { + console.error('Broken Id link when unpacking') + break + } if (instance.linkIds.has(linkInstance.id)) throw new Error('Infinite parentId loop') instance.linkIds.add(linkInstance.id) @@ -1988,7 +2092,6 @@ export class LGraph } this.canvasAction((c) => c.selectItems(toSelect)) - this.afterChange() } /** @@ -2479,6 +2582,7 @@ export class Subgraph this.inputNode.configure(data.inputNode) this.outputNode.configure(data.outputNode) + for (const node of this.nodes) node.updateComputedDisabled() } override configure( diff --git a/src/lib/litegraph/src/LGraphBadge.ts b/src/lib/litegraph/src/LGraphBadge.ts index d83a92aef..76f916fec 100644 --- a/src/lib/litegraph/src/LGraphBadge.ts +++ b/src/lib/litegraph/src/LGraphBadge.ts @@ -66,8 +66,12 @@ export class LGraphBadge { const { font } = ctx let iconWidth = 0 if (this.icon) { - ctx.font = `${this.icon.fontSize}px '${this.icon.fontFamily}'` - iconWidth = ctx.measureText(this.icon.unicode).width + this.padding + if (this.icon.image) { + iconWidth = this.icon.size + this.padding + } else if (this.icon.unicode) { + ctx.font = `${this.icon.fontSize}px '${this.icon.fontFamily}'` + iconWidth = ctx.measureText(this.icon.unicode).width + this.padding + } } ctx.font = `${this.fontSize}px sans-serif` const textWidth = this.text ? ctx.measureText(this.text).width : 0 @@ -104,7 +108,8 @@ export class LGraphBadge { // Draw icon if present if (this.icon) { this.icon.draw(ctx, drawX, centerY) - drawX += this.icon.fontSize + this.padding / 2 + 4 + const iconWidth = this.icon.image ? this.icon.size : this.icon.fontSize + drawX += iconWidth + this.padding / 2 + 4 } // Draw badge text diff --git a/tests-ui/tests/litegraph/core/LGraphButton.test.ts b/src/lib/litegraph/src/LGraphButton.test.ts similarity index 91% rename from tests-ui/tests/litegraph/core/LGraphButton.test.ts rename to src/lib/litegraph/src/LGraphButton.test.ts index 18830d8a4..923dd4778 100644 --- a/tests-ui/tests/litegraph/core/LGraphButton.test.ts +++ b/src/lib/litegraph/src/LGraphButton.test.ts @@ -1,21 +1,18 @@ import { describe, expect, it, vi } from 'vitest' -import { LGraphButton } from '@/lib/litegraph/src/litegraph' -import { Rectangle } from '@/lib/litegraph/src/litegraph' +import { LGraphButton, Rectangle } from '@/lib/litegraph/src/litegraph' describe('LGraphButton', () => { describe('Constructor', () => { it('should create a button with default options', () => { - // @ts-expect-error TODO: Fix after merge - LGraphButton constructor type issues - const button = new LGraphButton({}) + const button = new LGraphButton({ text: '' }) expect(button).toBeInstanceOf(LGraphButton) expect(button.name).toBeUndefined() expect(button._last_area).toBeInstanceOf(Rectangle) }) it('should create a button with custom name', () => { - // @ts-expect-error TODO: Fix after merge - LGraphButton constructor type issues - const button = new LGraphButton({ name: 'test_button' }) + const button = new LGraphButton({ text: '', name: 'test_button' }) expect(button.name).toBe('test_button') }) @@ -159,9 +156,8 @@ describe('LGraphButton', () => { const button = new LGraphButton({ text: '→', fontSize: 20, - // @ts-expect-error TODO: Fix after merge - color property not defined in type - color: '#FFFFFF', - backgroundColor: '#333333', + fgColor: '#FFFFFF', + bgColor: '#333333', xOffset: -10, yOffset: 5 }) diff --git a/tests-ui/tests/litegraph/core/LGraphCanvas.titleButtons.test.ts b/src/lib/litegraph/src/LGraphCanvas.titleButtons.test.ts similarity index 77% rename from tests-ui/tests/litegraph/core/LGraphCanvas.titleButtons.test.ts rename to src/lib/litegraph/src/LGraphCanvas.titleButtons.test.ts index f02fe005e..6202bb33d 100644 --- a/tests-ui/tests/litegraph/core/LGraphCanvas.titleButtons.test.ts +++ b/src/lib/litegraph/src/LGraphCanvas.titleButtons.test.ts @@ -1,7 +1,11 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -import { LGraphCanvas } from '@/lib/litegraph/src/litegraph' -import { LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph' +import { + LGraph, + LGraphCanvas, + LGraphNode, + LiteGraph +} from '@/lib/litegraph/src/litegraph' describe('LGraphCanvas Title Button Rendering', () => { let canvas: LGraphCanvas @@ -43,8 +47,8 @@ describe('LGraphCanvas Title Button Rendering', () => { canvasElement.getContext = vi.fn().mockReturnValue(ctx) - // @ts-expect-error TODO: Fix after merge - LGraphCanvas constructor type issues - canvas = new LGraphCanvas(canvasElement, null, { + const graph = new LGraph() + canvas = new LGraphCanvas(canvasElement, graph, { skip_render: true, skip_events: true }) @@ -53,18 +57,9 @@ describe('LGraphCanvas Title Button Rendering', () => { node.pos = [100, 200] node.size = [200, 100] - // Mock required methods node.drawTitleBarBackground = vi.fn() - // @ts-expect-error Property 'drawTitleBarText' does not exist on type 'LGraphNode' - node.drawTitleBarText = vi.fn() node.drawBadges = vi.fn() - // @ts-expect-error TODO: Fix after merge - drawToggles not defined in type - node.drawToggles = vi.fn() - // @ts-expect-error TODO: Fix after merge - drawNodeShape not defined in type - node.drawNodeShape = vi.fn() node.drawSlots = vi.fn() - // @ts-expect-error TODO: Fix after merge - drawContent not defined in type - node.drawContent = vi.fn() node.drawWidgets = vi.fn() node.drawCollapsedSlots = vi.fn() node.drawTitleBox = vi.fn() @@ -72,24 +67,31 @@ describe('LGraphCanvas Title Button Rendering', () => { node.drawProgressBar = vi.fn() node._setConcreteSlots = vi.fn() node.arrange = vi.fn() - // @ts-expect-error TODO: Fix after merge - isSelectable not defined in type - node.isSelectable = vi.fn().mockReturnValue(true) + + const nodeWithMocks = node as LGraphNode & { + drawTitleBarText: ReturnType + drawToggles: ReturnType + drawNodeShape: ReturnType + drawContent: ReturnType + isSelectable: ReturnType + } + nodeWithMocks.drawTitleBarText = vi.fn() + nodeWithMocks.drawToggles = vi.fn() + nodeWithMocks.drawNodeShape = vi.fn() + nodeWithMocks.drawContent = vi.fn() + nodeWithMocks.isSelectable = vi.fn().mockReturnValue(true) }) describe('drawNode title button rendering', () => { it('should render visible title buttons', () => { const button1 = node.addTitleButton({ name: 'button1', - text: 'A', - // @ts-expect-error TODO: Fix after merge - visible property not in LGraphButtonOptions - visible: true + text: 'A' }) const button2 = node.addTitleButton({ name: 'button2', - text: 'B', - // @ts-expect-error TODO: Fix after merge - visible property not in LGraphButtonOptions - visible: true + text: 'B' }) // Mock button methods @@ -124,9 +126,7 @@ describe('LGraphCanvas Title Button Rendering', () => { it('should skip invisible title buttons', () => { const visibleButton = node.addTitleButton({ name: 'visible', - text: 'V', - // @ts-expect-error TODO: Fix after merge - visible property not in LGraphButtonOptions - visible: true + text: 'V' }) const invisibleButton = node.addTitleButton({ @@ -168,9 +168,7 @@ describe('LGraphCanvas Title Button Rendering', () => { for (let i = 0; i < 3; i++) { const button = node.addTitleButton({ name: `button${i}`, - text: String(i), - // @ts-expect-error TODO: Fix after merge - visible property not in LGraphButtonOptions - visible: true + text: String(i) }) button.getWidth = vi.fn().mockReturnValue(15) // All same width for simplicity const spy = vi.spyOn(button, 'draw') @@ -193,18 +191,12 @@ describe('LGraphCanvas Title Button Rendering', () => { it('should render buttons in low quality mode', () => { const button = node.addTitleButton({ name: 'test', - text: 'T', - // @ts-expect-error TODO: Fix after merge - visible property not in LGraphButtonOptions - visible: true + text: 'T' }) button.getWidth = vi.fn().mockReturnValue(20) const drawSpy = vi.spyOn(button, 'draw') - // Set low quality rendering - // @ts-expect-error TODO: Fix after merge - lowQualityRenderingRequired not defined in type - canvas.lowQualityRenderingRequired = true - canvas.drawNode(node, ctx) // Buttons should still be rendered in low quality mode @@ -216,16 +208,12 @@ describe('LGraphCanvas Title Button Rendering', () => { it('should handle buttons with different widths', () => { const smallButton = node.addTitleButton({ name: 'small', - text: 'S', - // @ts-expect-error TODO: Fix after merge - visible property not in LGraphButtonOptions - visible: true + text: 'S' }) const largeButton = node.addTitleButton({ name: 'large', - text: 'LARGE', - // @ts-expect-error TODO: Fix after merge - visible property not in LGraphButtonOptions - visible: true + text: 'LARGE' }) smallButton.getWidth = vi.fn().mockReturnValue(15) @@ -253,9 +241,7 @@ describe('LGraphCanvas Title Button Rendering', () => { const button = node.addTitleButton({ name: 'test', - text: 'X', - // @ts-expect-error TODO: Fix after merge - visible property not in LGraphButtonOptions - visible: true + text: 'X' }) button.getWidth = vi.fn().mockReturnValue(20) diff --git a/src/lib/litegraph/src/LGraphCanvas.ts b/src/lib/litegraph/src/LGraphCanvas.ts index fb21e72b5..40893174f 100644 --- a/src/lib/litegraph/src/LGraphCanvas.ts +++ b/src/lib/litegraph/src/LGraphCanvas.ts @@ -7,6 +7,8 @@ import { getSlotPosition } from '@/renderer/core/canvas/litegraph/slotCalculatio import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations' import { layoutStore } from '@/renderer/core/layout/store/layoutStore' import { LayoutSource } from '@/renderer/core/layout/types' +import { removeNodeTitleHeight } from '@/renderer/core/layout/utils/nodeSizeUtil' +import { forEachNode } from '@/utils/graphTraversalUtil' import { CanvasPointer } from './CanvasPointer' import type { ContextMenu } from './ContextMenu' @@ -51,6 +53,11 @@ import type { LinkSegment, NewNodePosition, NullableProperties, + Panel, + PanelButton, + PanelWidget, + PanelWidgetCallback, + PanelWidgetOptions, Point, Positionable, ReadOnlyRect, @@ -93,7 +100,7 @@ import type { SubgraphIO } from './types/serialisation' import type { NeverNever, PickNevers } from './types/utility' -import type { IBaseWidget } from './types/widgets' +import type { IBaseWidget, TWidgetValue } from './types/widgets' import { alignNodes, distributeNodes, getBoundaryNodes } from './utils/arrange' import { findFirstNode, getAllNestedItems } from './utils/collections' import { resolveConnectingLinkColor } from './utils/linkColors' @@ -223,12 +230,21 @@ interface IPasteFromClipboardOptions { } interface ICreatePanelOptions { - closable?: any - window?: any + closable?: boolean + window?: Window onOpen?: () => void onClose?: () => void - width?: any - height?: any + width?: number | string + height?: number | string +} + +interface SlotTypeDefaultNodeOpts { + node?: string + title?: string + properties?: Record + inputs?: [string, string][] + outputs?: [string, string][] + json?: Parameters[0] } const cursors = { @@ -248,9 +264,7 @@ const link_bounding = new Rectangle() * This class is in charge of rendering one graph inside a canvas. And provides all the interaction required. * Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked */ -export class LGraphCanvas - implements CustomEventDispatcher -{ +export class LGraphCanvas implements CustomEventDispatcher { static DEFAULT_BACKGROUND_IMAGE = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=' @@ -496,10 +510,10 @@ export class LGraphCanvas } options: { - skip_events?: any - viewport?: any - skip_render?: any - autoresize?: any + skip_events?: boolean + viewport?: Rect + skip_render?: boolean + autoresize?: boolean } background_image: string @@ -585,13 +599,27 @@ export class LGraphCanvas /** @deprecated LEGACY: REMOVE THIS, USE {@link graph_mouse} INSTEAD */ canvas_mouse: Point /** to personalize the search box */ - onSearchBox?: (helper: Element, str: string, canvas: LGraphCanvas) => any - onSearchBoxSelection?: (name: any, event: any, canvas: LGraphCanvas) => void + onSearchBox?: ( + helper: HTMLDivElement, + str: string, + canvas: LGraphCanvas + ) => string[] | undefined + onSearchBoxSelection?: ( + name: string, + event: MouseEvent, + canvas: LGraphCanvas + ) => void onMouse?: (e: CanvasPointerEvent) => boolean /** to render background objects (behind nodes and connections) in the canvas affected by transform */ - onDrawBackground?: (ctx: CanvasRenderingContext2D, visible_area: any) => void + onDrawBackground?: ( + ctx: CanvasRenderingContext2D, + visible_area: Rectangle + ) => void /** to render foreground objects (above nodes and connections) in the canvas affected by transform */ - onDrawForeground?: (arg0: CanvasRenderingContext2D, arg1: any) => void + onDrawForeground?: ( + ctx: CanvasRenderingContext2D, + visible_area: Rectangle + ) => void connections_width: number /** The current node being drawn by {@link drawNode}. This should NOT be used to determine the currently selected node. See {@link selectedItems} */ current_node: LGraphNode | null @@ -680,9 +708,9 @@ export class LGraphCanvas _highlight_input?: INodeInputSlot // TODO: Check if panels are used /** @deprecated Panels */ - node_panel?: any + node_panel?: Panel /** @deprecated Panels */ - options_panel?: any + options_panel?: Panel _bg_img?: HTMLImageElement _pattern?: CanvasPattern _pattern_img?: HTMLImageElement @@ -708,11 +736,14 @@ export class LGraphCanvas /** The start position of the drag zoom and original read-only state. */ #dragZoomStart: { pos: Point; scale: number; readOnly: boolean } | null = null + /** If true, enable live selection during drag. Nodes are selected/deselected in real-time. */ + liveSelection: boolean = false + getMenuOptions?(): IContextMenuValue[] getExtraMenuOptions?( canvas: LGraphCanvas, - options: IContextMenuValue[] - ): IContextMenuValue[] + options: (IContextMenuValue | null)[] + ): (IContextMenuValue | null)[] static active_node: LGraphNode /** called before modifying the graph */ onBeforeChange?(graph: LGraph): void @@ -929,7 +960,7 @@ export class LGraphCanvas this.connecting_links = null // to constraint render area to a portion of the canvas - this.viewport = options.viewport || null + this.viewport = options.viewport // link canvas and graph this.graph = graph @@ -961,16 +992,14 @@ export class LGraphCanvas this.startRendering() } - this.autoresize = options.autoresize + this.autoresize = options.autoresize ?? false this.updateLowQualityThreshold() } static onGroupAdd( - // @ts-expect-error - unused parameter - info: unknown, - // @ts-expect-error - unused parameter - entry: unknown, + _info: unknown, + _entry: unknown, mouse_event: MouseEvent ): void { const canvas = LGraphCanvas.active_canvas @@ -1018,10 +1047,8 @@ export class LGraphCanvas } static onNodeAlign( - // @ts-expect-error - unused parameter - value: IContextMenuValue, - // @ts-expect-error - unused parameter - options: IContextMenuOptions, + _value: IContextMenuValue, + _options: IContextMenuOptions, event: MouseEvent, prev_menu: ContextMenu, node: LGraphNode @@ -1044,10 +1071,8 @@ export class LGraphCanvas } static onGroupAlign( - // @ts-expect-error - unused parameter - value: IContextMenuValue, - // @ts-expect-error - unused parameter - options: IContextMenuOptions, + _value: IContextMenuValue, + _options: IContextMenuOptions, event: MouseEvent, prev_menu: ContextMenu ): void { @@ -1068,10 +1093,8 @@ export class LGraphCanvas } static createDistributeMenu( - // @ts-expect-error - unused parameter - value: IContextMenuValue, - // @ts-expect-error - unused parameter - options: IContextMenuOptions, + _value: IContextMenuValue, + _options: IContextMenuOptions, event: MouseEvent, prev_menu: ContextMenu ): void { @@ -1093,16 +1116,13 @@ export class LGraphCanvas } static onMenuAdd( - // @ts-expect-error - unused parameter - value: unknown, - // @ts-expect-error - unused parameter - options: unknown, + _value: unknown, + _options: unknown, e: MouseEvent, prev_menu?: ContextMenu, callback?: (node: LGraphNode | null) => void ): boolean | undefined { const canvas = LGraphCanvas.active_canvas - const ref_window = canvas.getCanvasWindow() const { graph } = canvas if (!graph) return @@ -1153,14 +1173,7 @@ export class LGraphCanvas value: category_path, content: name, has_submenu: true, - callback: function ( - value, - // @ts-expect-error - unused parameter - event, - // @ts-expect-error - unused parameter - mouseEvent, - contextMenu - ) { + callback: function (value, _event, _mouseEvent, contextMenu) { inner_onMenuAdded(value.value, contextMenu) } }) @@ -1179,14 +1192,7 @@ export class LGraphCanvas value: node.type, content: node.title, has_submenu: false, - callback: function ( - value, - // @ts-expect-error - unused parameter - event, - // @ts-expect-error - unused parameter - mouseEvent, - contextMenu - ) { + callback: function (value, _event, _mouseEvent, contextMenu) { if (!canvas.graph) throw new NullGraphError() const first_event = contextMenu.getFirstEvent() @@ -1211,12 +1217,7 @@ export class LGraphCanvas entries.push(entry) } - new LiteGraph.ContextMenu( - entries, - { event: e, parentMenu: prev_menu }, - // @ts-expect-error - extra parameter - ref_window - ) + new LiteGraph.ContextMenu(entries, { event: e, parentMenu: prev_menu }) } } @@ -1225,8 +1226,7 @@ export class LGraphCanvas /** @param _options Parameter is never used */ static showMenuNodeOptionalOutputs( - // @ts-expect-error - unused parameter - v: unknown, + _v: unknown, /** Unused - immediately overwritten */ _options: INodeOutputSlot[], e: MouseEvent, @@ -1264,11 +1264,13 @@ export class LGraphCanvas function inner_clicked( this: ContextMenuDivElement, - v: IContextMenuValue, - e: any, - prev: any + v?: string | IContextMenuValue, + _options?: unknown, + e?: MouseEvent, + prev?: ContextMenu ) { if (!node) return + if (!v || typeof v === 'string') return // TODO: This is a static method, so the below "that" appears broken. if (v.callback) void v.callback.call(this, node, v, e, prev) @@ -1310,8 +1312,7 @@ export class LGraphCanvas /** @param value Parameter is never used */ static onShowMenuNodeProperties( value: NodeProperty | undefined, - // @ts-expect-error - unused parameter - options: unknown, + _options: unknown, e: MouseEvent, prev_menu: ContextMenu, node: LGraphNode @@ -1319,7 +1320,6 @@ export class LGraphCanvas if (!node || !node.properties) return const canvas = LGraphCanvas.active_canvas - const ref_window = canvas.getCanvasWindow() const entries: IContextMenuValue[] = [] for (const i in node.properties) { @@ -1342,21 +1342,18 @@ export class LGraphCanvas return } - new LiteGraph.ContextMenu( - entries, - { - event: e, - callback: inner_clicked, - parentMenu: prev_menu, - allow_html: true, - node - }, - // @ts-expect-error Unused - ref_window - ) + new LiteGraph.ContextMenu(entries, { + event: e, + callback: inner_clicked, + parentMenu: prev_menu, + node + }) - function inner_clicked(this: ContextMenuDivElement, v: { value: any }) { - if (!node) return + function inner_clicked( + this: ContextMenuDivElement, + v?: string | IContextMenuValue + ) { + if (!node || typeof v === 'string' || !v?.value) return const rect = this.getBoundingClientRect() canvas.showEditPropertyValue(node, v.value, { @@ -1375,14 +1372,10 @@ export class LGraphCanvas } static onMenuResizeNode( - // @ts-expect-error - unused parameter - value: IContextMenuValue, - // @ts-expect-error - unused parameter - options: IContextMenuOptions, - // @ts-expect-error - unused parameter - e: MouseEvent, - // @ts-expect-error - unused parameter - menu: ContextMenu, + _value: IContextMenuValue, + _options: IContextMenuOptions, + _e: MouseEvent, + _menu: ContextMenu, node: LGraphNode ): void { if (!node) return @@ -1409,11 +1402,9 @@ export class LGraphCanvas // TODO refactor :: this is used fot title but not for properties! static onShowPropertyEditor( item: { property: keyof LGraphNode; type: string }, - // @ts-expect-error - unused parameter - options: IContextMenuOptions, + _options: IContextMenuOptions, e: MouseEvent, - // @ts-expect-error - unused parameter - menu: ContextMenu, + _menu: ContextMenu, node: LGraphNode ): void { const property = item.property || 'title' @@ -1483,11 +1474,10 @@ export class LGraphCanvas input.focus() - let dialogCloseTimer: number + let dialogCloseTimer: ReturnType | undefined dialog.addEventListener('mouseleave', function () { if (LiteGraph.dialog_close_on_mouse_leave) { if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) { - // @ts-expect-error - setTimeout type dialogCloseTimer = setTimeout( dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay @@ -1542,14 +1532,10 @@ export class LGraphCanvas } static onMenuNodeCollapse( - // @ts-expect-error - unused parameter - value: IContextMenuValue, - // @ts-expect-error - unused parameter - options: IContextMenuOptions, - // @ts-expect-error - unused parameter - e: MouseEvent, - // @ts-expect-error - unused parameter - menu: ContextMenu, + _value: IContextMenuValue, + _options: IContextMenuOptions, + _e: MouseEvent, + _menu: ContextMenu, node: LGraphNode ): void { if (!node.graph) throw new NullGraphError() @@ -1576,14 +1562,10 @@ export class LGraphCanvas } static onMenuToggleAdvanced( - // @ts-expect-error - unused parameter - value: IContextMenuValue, - // @ts-expect-error - unused parameter - options: IContextMenuOptions, - // @ts-expect-error - unused parameter - e: MouseEvent, - // @ts-expect-error - unused parameter - menu: ContextMenu, + _value: IContextMenuValue, + _options: IContextMenuOptions, + _e: MouseEvent, + _menu: ContextMenu, node: LGraphNode ): void { if (!node.graph) throw new NullGraphError() @@ -1608,10 +1590,8 @@ export class LGraphCanvas } static onMenuNodeMode( - // @ts-expect-error - unused parameter - value: IContextMenuValue, - // @ts-expect-error - unused parameter - options: IContextMenuOptions, + _value: IContextMenuValue, + _options: IContextMenuOptions, e: MouseEvent, menu: ContextMenu, node: LGraphNode @@ -1655,8 +1635,7 @@ export class LGraphCanvas /** @param value Parameter is never used */ static onMenuNodeColors( value: IContextMenuValue, - // @ts-expect-error - unused parameter - options: IContextMenuOptions, + _options: IContextMenuOptions, e: MouseEvent, menu: ContextMenu, node: LGraphNode @@ -1717,10 +1696,8 @@ export class LGraphCanvas } static onMenuNodeShapes( - // @ts-expect-error - unused parameter - value: IContextMenuValue<(typeof LiteGraph.VALID_SHAPES)[number]>, - // @ts-expect-error - unused parameter - options: IContextMenuOptions<(typeof LiteGraph.VALID_SHAPES)[number]>, + _value: IContextMenuValue<(typeof LiteGraph.VALID_SHAPES)[number]>, + _options: IContextMenuOptions<(typeof LiteGraph.VALID_SHAPES)[number]>, e: MouseEvent, menu?: ContextMenu<(typeof LiteGraph.VALID_SHAPES)[number]>, node?: LGraphNode @@ -2628,7 +2605,20 @@ export class LGraphCanvas this.processSelect(clickedItem, eUp) } pointer.onDragStart = () => (this.dragging_rectangle = dragRect) - pointer.onDragEnd = (upEvent) => this.#handleMultiSelect(upEvent, dragRect) + + if (this.liveSelection) { + const initialSelection = new Set(this.selectedItems) + + pointer.onDrag = (eMove) => + this.handleLiveSelect(eMove, dragRect, initialSelection) + + pointer.onDragEnd = () => this.finalizeLiveSelect() + } else { + // Classic mode: select only when drag ends + pointer.onDragEnd = (upEvent) => + this.#handleMultiSelect(upEvent, dragRect) + } + pointer.finally = () => (this.dragging_rectangle = null) } @@ -3197,7 +3187,9 @@ export class LGraphCanvas } // get node over - const node = graph.getNodeOnPos(x, y, this.visible_nodes) + const node = LiteGraph.vueNodesMode + ? null + : graph.getNodeOnPos(x, y, this.visible_nodes) const dragRect = this.dragging_rectangle if (dragRect) { @@ -3581,13 +3573,16 @@ export class LGraphCanvas this.node_over?.onMouseUp?.( e, [x - this.node_over.pos[0], y - this.node_over.pos[1]], - // @ts-expect-error - extra parameter this ) - this.node_capturing_input?.onMouseUp?.(e, [ - x - this.node_capturing_input.pos[0], - y - this.node_capturing_input.pos[1] - ]) + this.node_capturing_input?.onMouseUp?.( + e, + [ + x - this.node_capturing_input.pos[0], + y - this.node_capturing_input.pos[1] + ], + this + ) } } else if (e.button === 1) { // middle button @@ -4043,19 +4038,30 @@ export class LGraphCanvas // TODO: Report failures, i.e. `failedNodes` - const newPositions = created.map((node) => ({ - nodeId: String(node.id), - bounds: { - x: node.pos[0], - y: node.pos[1], - width: node.size?.[0] ?? 100, - height: node.size?.[1] ?? 200 - } - })) + const newPositions = created + .filter((item): item is LGraphNode => item instanceof LGraphNode) + .map((node) => { + const fullHeight = node.size?.[1] ?? 200 + const layoutHeight = LiteGraph.vueNodesMode + ? removeNodeTitleHeight(fullHeight) + : fullHeight + return { + nodeId: String(node.id), + bounds: { + x: node.pos[0], + y: node.pos[1], + width: node.size?.[0] ?? 100, + height: layoutHeight + } + } + }) + if (newPositions.length) layoutStore.setSource(LayoutSource.Canvas) layoutStore.batchUpdateNodeBounds(newPositions) this.selectItems(created) + forEachNode(graph, (n) => n.onGraphConfigured?.()) + forEachNode(graph, (n) => n.onAfterGraphConfigured?.()) graph.afterChange() this.emitAfterChange() @@ -4079,76 +4085,156 @@ export class LGraphCanvas this.setDirty(true) } - #handleMultiSelect(e: CanvasPointerEvent, dragRect: Rect) { - // Process drag - // Convert Point pair (pos, offset) to Rect - const { graph, selectedItems, subgraph } = this - if (!graph) throw new NullGraphError() - + /** + * Normalizes a drag rectangle to have positive width and height. + * @param dragRect The drag rectangle to normalize (modified in place) + * @returns The normalized rectangle + */ + #normalizeDragRect(dragRect: Rect): Rect { const w = Math.abs(dragRect[2]) const h = Math.abs(dragRect[3]) if (dragRect[2] < 0) dragRect[0] -= w if (dragRect[3] < 0) dragRect[1] -= h dragRect[2] = w dragRect[3] = h + return dragRect + } - // Select nodes - any part of the node is in the select area - const isSelected = new Set() - const notSelected: Positionable[] = [] + /** + * Gets all positionable items that overlap with the given rectangle. + * @param rect The rectangle to check against + * @returns Set of positionable items that overlap with the rectangle + */ + #getItemsInRect(rect: Rect): Set { + const { graph, subgraph } = this + if (!graph) throw new NullGraphError() + + const items = new Set() if (subgraph) { const { inputNode, outputNode } = subgraph - - if (overlapBounding(dragRect, inputNode.boundingRect)) { - addPositionable(inputNode) - } - if (overlapBounding(dragRect, outputNode.boundingRect)) { - addPositionable(outputNode) - } + if (overlapBounding(rect, inputNode.boundingRect)) items.add(inputNode) + if (overlapBounding(rect, outputNode.boundingRect)) items.add(outputNode) } - for (const nodeX of graph._nodes) { - if (overlapBounding(dragRect, nodeX.boundingRect)) { - addPositionable(nodeX) - } + for (const node of graph._nodes) { + if (overlapBounding(rect, node.boundingRect)) items.add(node) } - // Select groups - the group is wholly inside the select area + // Check groups (must be wholly inside) for (const group of graph.groups) { - if (!containsRect(dragRect, group._bounding)) continue - - group.recomputeInsideNodes() - addPositionable(group) + if (containsRect(rect, group._bounding)) { + group.recomputeInsideNodes() + items.add(group) + } } - // Select reroutes - the centre point is inside the select area + // Check reroutes (center point must be inside) for (const reroute of graph.reroutes.values()) { - if (!isPointInRect(reroute.pos, dragRect)) continue - - selectedItems.add(reroute) - reroute.selected = true - addPositionable(reroute) + if (isPointInRect(reroute.pos, rect)) items.add(reroute) } + return items + } + + /** + * Handles live selection updates during drag. Called on each pointer move. + * @param e The pointer move event + * @param dragRect The current drag rectangle + * @param initialSelection The selection state before the drag started + */ + private handleLiveSelect( + e: CanvasPointerEvent, + dragRect: Rect, + initialSelection: Set + ): void { + // Ensure rect is current even if pointer.onDrag fires before processMouseMove updates it + dragRect[2] = e.canvasX - dragRect[0] + dragRect[3] = e.canvasY - dragRect[1] + + // Create a normalized copy for overlap checking + const normalizedRect: Rect = [ + dragRect[0], + dragRect[1], + dragRect[2], + dragRect[3] + ] + this.#normalizeDragRect(normalizedRect) + + const itemsInRect = this.#getItemsInRect(normalizedRect) + + const desired = new Set() + if (e.shiftKey && !e.altKey) { + for (const item of initialSelection) desired.add(item) + for (const item of itemsInRect) desired.add(item) + } else if (e.altKey && !e.shiftKey) { + for (const item of initialSelection) + if (!itemsInRect.has(item)) desired.add(item) + } else { + for (const item of itemsInRect) desired.add(item) + } + + let changed = false + for (const item of [...this.selectedItems]) { + if (!desired.has(item)) { + this.deselect(item) + changed = true + } + } + for (const item of desired) { + if (!this.selectedItems.has(item)) { + this.select(item) + changed = true + } + } + + if (changed) { + this.onSelectionChange?.(this.selected_nodes) + this.setDirty(true) + } + } + + /** + * Finalizes the live selection when drag ends. + */ + private finalizeLiveSelect(): void { + // Selection is already updated by handleLiveSelect + // Just trigger the final selection change callback + this.onSelectionChange?.(this.selected_nodes) + } + + /** + * Handles multi-select when drag ends (classic mode). + * @param e The pointer up event + * @param dragRect The drag rectangle + */ + #handleMultiSelect(e: CanvasPointerEvent, dragRect: Rect): void { + const normalizedRect: Rect = [ + dragRect[0], + dragRect[1], + dragRect[2], + dragRect[3] + ] + this.#normalizeDragRect(normalizedRect) + + const itemsInRect = this.#getItemsInRect(normalizedRect) + const { selectedItems } = this + if (e.shiftKey) { // Add to selection - for (const item of notSelected) this.select(item) + for (const item of itemsInRect) this.select(item) } else if (e.altKey) { // Remove from selection - for (const item of isSelected) this.deselect(item) + for (const item of itemsInRect) this.deselect(item) } else { // Replace selection for (const item of selectedItems.values()) { - if (!isSelected.has(item)) this.deselect(item) + if (!itemsInRect.has(item)) this.deselect(item) } - for (const item of notSelected) this.select(item) + for (const item of itemsInRect) this.select(item) } - this.onSelectionChange?.(this.selected_nodes) - function addPositionable(item: Positionable): void { - if (!item.selected || !selectedItems.has(item)) notSelected.push(item) - else isSelected.add(item) - } + this.onSelectionChange?.(this.selected_nodes) } /** @@ -4495,9 +4581,8 @@ export class LGraphCanvas /** * converts a coordinate from graph coordinates to canvas2D coordinates */ - convertOffsetToCanvas(pos: Point, out: Point): Point { - // @ts-expect-error Unused param - return this.ds.convertOffsetToCanvas(pos, out) + convertOffsetToCanvas(pos: Point, _out?: Point): Point { + return this.ds.convertOffsetToCanvas(pos) } /** @@ -4788,29 +4873,6 @@ export class LGraphCanvas this.#renderSnapHighlight(ctx, highlightPos) } - // Area-selection rectangle - if (this.dragging_rectangle) { - const { eDown, eMove } = this.pointer - ctx.strokeStyle = '#FFF' - - if (eDown && eMove) { - // Do not scale the selection box - const transform = ctx.getTransform() - const ratio = Math.max(1, window.devicePixelRatio) - ctx.setTransform(ratio, 0, 0, ratio, 0, 0) - - const x = eDown.safeOffsetX - const y = eDown.safeOffsetY - ctx.strokeRect(x, y, eMove.safeOffsetX - x, eMove.safeOffsetY - y) - - ctx.setTransform(transform) - } else { - // Fallback to legacy behaviour - const [x, y, w, h] = this.dragging_rectangle - ctx.strokeRect(x, y, w, h) - } - } - // on top of link center if ( !this.isDragging && @@ -6063,11 +6125,7 @@ export class LGraphCanvas /** * draws every group area in the background */ - drawGroups( - // @ts-expect-error - unused parameter - canvas: HTMLCanvasElement, - ctx: CanvasRenderingContext2D - ): void { + drawGroups(_canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D): void { if (!this.graph) return const groups = this.graph._groups @@ -6161,8 +6219,7 @@ export class LGraphCanvas function inner_clicked( this: LGraphCanvas, v: string, - // @ts-expect-error - unused parameter - options: unknown, + _options: unknown, e: MouseEvent ) { if (!graph) throw new NullGraphError() @@ -6318,8 +6375,7 @@ export class LGraphCanvas ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in if (slotTypesDefault?.[fromSlotType]) { - // TODO: Remove "any" kludge - let nodeNewType: any = false + let nodeNewType: string | Record | false = false if (typeof slotTypesDefault[fromSlotType] == 'object') { for (const typeX in slotTypesDefault[fromSlotType]) { if ( @@ -6337,11 +6393,13 @@ export class LGraphCanvas nodeNewType = slotTypesDefault[fromSlotType] } if (nodeNewType) { - // TODO: Remove "any" kludge - let nodeNewOpts: any = false - if (typeof nodeNewType == 'object' && nodeNewType.node) { - nodeNewOpts = nodeNewType - nodeNewType = nodeNewType.node + let nodeNewOpts: SlotTypeDefaultNodeOpts | undefined + let nodeTypeStr: string + if (typeof nodeNewType == 'object') { + nodeNewOpts = nodeNewType as SlotTypeDefaultNodeOpts + nodeTypeStr = nodeNewOpts.node ?? '' + } else { + nodeTypeStr = nodeNewType } // that.graph.beforeChange(); @@ -6350,7 +6408,7 @@ export class LGraphCanvas const nodeX = opts.position[0] + opts.posAdd[0] + xSizeFix const nodeY = opts.position[1] + opts.posAdd[1] + ySizeFix const pos = [nodeX, nodeY] - const newNode = LiteGraph.createNode(nodeNewType, nodeNewOpts.title, { + const newNode = LiteGraph.createNode(nodeTypeStr, nodeNewOpts?.title, { pos }) if (newNode) { @@ -6363,20 +6421,14 @@ export class LGraphCanvas } if (nodeNewOpts.inputs) { newNode.inputs = [] - for (const i in nodeNewOpts.inputs) { - newNode.addOutput( - nodeNewOpts.inputs[i][0], - nodeNewOpts.inputs[i][1] - ) + for (const input of nodeNewOpts.inputs) { + newNode.addInput(input[0], input[1]) } } if (nodeNewOpts.outputs) { newNode.outputs = [] - for (const i in nodeNewOpts.outputs) { - newNode.addOutput( - nodeNewOpts.outputs[i][0], - nodeNewOpts.outputs[i][1] - ) + for (const output of nodeNewOpts.outputs) { + newNode.addOutput(output[0], output[1]) } } if (nodeNewOpts.json) { @@ -6646,8 +6698,8 @@ export class LGraphCanvas // refactor: there are different dialogs, some uses createDialog some dont prompt( title: string, - value: any, - callback: (arg0: any) => void, + value: string | number, + callback: (value: string) => void, event: CanvasPointerEvent, multiline?: boolean ): HTMLDivElement { @@ -6681,13 +6733,12 @@ export class LGraphCanvas if (this.ds.scale > 1) dialog.style.transform = `scale(${this.ds.scale})` - let dialogCloseTimer: number + let dialogCloseTimer: ReturnType | undefined let prevent_timeout = 0 LiteGraph.pointerListenerAdd(dialog, 'leave', function () { if (prevent_timeout) return if (LiteGraph.dialog_close_on_mouse_leave) { if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) { - // @ts-expect-error - setTimeout type dialogCloseTimer = setTimeout( dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay @@ -6725,7 +6776,7 @@ export class LGraphCanvas dialog.querySelector('.value') if (!value_element) throw new TypeError('value_element was null') - value_element.value = value + value_element.value = String(value) value_element.select() const input = value_element @@ -6874,9 +6925,8 @@ export class LGraphCanvas // hide on mouse leave if (options.hide_on_mouse_leave) { - // FIXME: Remove "any" kludge - let prevent_timeout: any = false - let timeout_close: number | null = null + let prevent_timeout = 0 + let timeout_close: ReturnType | null = null LiteGraph.pointerListenerAdd(dialog, 'enter', function () { if (timeout_close) { clearTimeout(timeout_close) @@ -6888,7 +6938,6 @@ export class LGraphCanvas const hideDelay = options.hide_on_mouse_leave const delay = typeof hideDelay === 'number' ? hideDelay : 500 - // @ts-expect-error - setTimeout type timeout_close = setTimeout(dialog.close, delay) }) // if filtering, check focus changed to comboboxes and prevent closing @@ -6924,7 +6973,7 @@ export class LGraphCanvas that.search_box = dialog let first: string | null = null - let timeout: number | null = null + let timeout: ReturnType | null = null let selected: ChildNode | null = null const maybeInput = dialog.querySelector('input') @@ -6958,7 +7007,6 @@ export class LGraphCanvas if (timeout) { clearInterval(timeout) } - // @ts-expect-error - setTimeout type timeout = setTimeout(refreshHelper, 10) return } @@ -7071,8 +7119,7 @@ export class LGraphCanvas // join node after inserting if (options.node_from) { - // FIXME: any - let iS: any = false + let iS: number | false = false switch (typeof options.slot_from) { case 'string': iS = options.node_from.findOutputSlot(options.slot_from) @@ -7098,8 +7145,8 @@ export class LGraphCanvas // try with first if no name set iS = 0 } - if (options.node_from.outputs[iS] !== undefined) { - if (iS !== false && iS > -1) { + if (iS !== false && options.node_from.outputs[iS] !== undefined) { + if (iS > -1) { if (node == null) throw new TypeError( 'options.slot_from was null when showing search box' @@ -7116,8 +7163,7 @@ export class LGraphCanvas } } if (options.node_to) { - // FIXME: any - let iS: any = false + let iS: number | false = false switch (typeof options.slot_from) { case 'string': iS = options.node_to.findInputSlot(options.slot_from) @@ -7143,8 +7189,8 @@ export class LGraphCanvas // try with first if no name set iS = 0 } - if (options.node_to.inputs[iS] !== undefined) { - if (iS !== false && iS > -1) { + if (iS !== false && options.node_to.inputs[iS] !== undefined) { + if (iS > -1) { if (node == null) throw new TypeError( 'options.slot_from was null when showing search box' @@ -7207,13 +7253,16 @@ export class LGraphCanvas const filter = graphcanvas.filter || graphcanvas.graph.filter - // FIXME: any // filter by type preprocess - let sIn: any = false - let sOut: any = false + let sIn: HTMLSelectElement | null = null + let sOut: HTMLSelectElement | null = null if (options.do_type_filter && that.search_box) { - sIn = that.search_box.querySelector('.slot_in_type_filter') - sOut = that.search_box.querySelector('.slot_out_type_filter') + sIn = that.search_box.querySelector( + '.slot_in_type_filter' + ) + sOut = that.search_box.querySelector( + '.slot_out_type_filter' + ) } const keys = Object.keys(LiteGraph.registered_node_types) @@ -7231,11 +7280,9 @@ export class LGraphCanvas // add general type if filtering if ( options.show_general_after_typefiltered && - (sIn.value || sOut.value) + (sIn?.value || sOut?.value) ) { - // FIXME: Undeclared variable again - // @ts-expect-error Variable declared without type annotation - filtered_extra = [] + const filtered_extra: string[] = [] for (const i in LiteGraph.registered_node_types) { if ( inner_test_filter(i, { @@ -7243,11 +7290,9 @@ export class LGraphCanvas outTypeOverride: sOut && sOut.value ? '*' : false }) ) { - // @ts-expect-error Variable declared without type annotation filtered_extra.push(i) } } - // @ts-expect-error Variable declared without type annotation for (const extraItem of filtered_extra) { addResult(extraItem, 'generic_type') if ( @@ -7260,18 +7305,15 @@ export class LGraphCanvas // check il filtering gave no results if ( - (sIn.value || sOut.value) && + (sIn?.value || sOut?.value) && helper.childNodes.length == 0 && options.show_general_if_none_on_typefilter ) { - // @ts-expect-error Variable declared without type annotation - filtered_extra = [] + const filtered_extra: string[] = [] for (const i in LiteGraph.registered_node_types) { if (inner_test_filter(i, { skipFilter: true })) - // @ts-expect-error Variable declared without type annotation filtered_extra.push(i) } - // @ts-expect-error Variable declared without type annotation for (const extraItem of filtered_extra) { addResult(extraItem, 'not_in_filter') if ( @@ -7311,8 +7353,10 @@ export class LGraphCanvas if (options.do_type_filter && !opts.skipFilter) { const sType = type - let sV = - opts.inTypeOverride !== false ? opts.inTypeOverride : sIn.value + let sV: string | undefined = + typeof opts.inTypeOverride === 'string' + ? opts.inTypeOverride + : sIn?.value // type is stored if (sIn && sV && LiteGraph.registered_slot_in_types[sV]?.nodes) { const doesInc = @@ -7320,8 +7364,9 @@ export class LGraphCanvas if (doesInc === false) return false } - sV = sOut.value - if (opts.outTypeOverride !== false) sV = opts.outTypeOverride + sV = sOut?.value + if (typeof opts.outTypeOverride === 'string') + sV = opts.outTypeOverride // type is stored if (sOut && sV && LiteGraph.registered_slot_out_types[sV]?.nodes) { const doesInc = @@ -7566,13 +7611,12 @@ export class LGraphCanvas } } - let dialogCloseTimer: number + let dialogCloseTimer: ReturnType | undefined let prevent_timeout = 0 dialog.addEventListener('mouseleave', function () { if (prevent_timeout) return if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) { - // @ts-expect-error - setTimeout type dialogCloseTimer = setTimeout( dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay @@ -7603,16 +7647,14 @@ export class LGraphCanvas return dialog } - createPanel(title: string, options: ICreatePanelOptions) { + createPanel(title: string, options: ICreatePanelOptions): Panel { options = options || {} - const ref_window = options.window || window - // TODO: any kludge - const root: any = document.createElement('div') + const root = document.createElement('div') as Panel root.className = 'litegraph dialog' root.innerHTML = "
" - root.header = root.querySelector('.dialog-header') + root.header = root.querySelector('.dialog-header')! if (options.width) root.style.width = @@ -7629,11 +7671,11 @@ export class LGraphCanvas }) root.header.append(close) } - root.title_element = root.querySelector('.dialog-title') + root.title_element = root.querySelector('.dialog-title')! root.title_element.textContent = title - root.content = root.querySelector('.dialog-content') - root.alt_content = root.querySelector('.dialog-alt-content') - root.footer = root.querySelector('.dialog-footer') + root.content = root.querySelector('.dialog-content')! + root.alt_content = root.querySelector('.dialog-alt-content')! + root.footer = root.querySelector('.dialog-footer')! root.footer.style.marginTop = '-96px' root.close = function () { @@ -7643,7 +7685,7 @@ export class LGraphCanvas } // function to swap panel content - root.toggleAltContent = function (force: unknown) { + root.toggleAltContent = function (force?: boolean) { let vTo: string let vAlt: string if (force !== undefined) { @@ -7657,7 +7699,7 @@ export class LGraphCanvas root.content.style.display = vAlt } - root.toggleFooterVisibility = function (force: unknown) { + root.toggleFooterVisibility = function (force?: boolean) { let vTo: string if (force !== undefined) { vTo = force ? 'block' : 'none' @@ -7671,7 +7713,11 @@ export class LGraphCanvas this.content.innerHTML = '' } - root.addHTML = function (code: string, classname: string, on_footer: any) { + root.addHTML = function ( + code: string, + classname?: string, + on_footer?: boolean + ) { const elem = document.createElement('div') if (classname) elem.className = classname elem.innerHTML = code @@ -7680,9 +7726,12 @@ export class LGraphCanvas return elem } - root.addButton = function (name: any, callback: any, options: any) { - // TODO: any kludge - const elem: any = document.createElement('button') + root.addButton = function ( + name: string, + callback: () => void, + options?: unknown + ): PanelButton { + const elem = document.createElement('button') as PanelButton elem.textContent = name elem.options = options elem.classList.add('btn') @@ -7699,20 +7748,18 @@ export class LGraphCanvas root.addWidget = function ( type: string, - name: any, - value: unknown, - options: { label?: any; type?: any; values?: any; callback?: any }, - callback: (arg0: any, arg1: any, arg2: any) => void - ) { + name: string, + value: TWidgetValue, + options?: PanelWidgetOptions, + callback?: PanelWidgetCallback + ): PanelWidget { options = options || {} let str_value = String(value) type = type.toLowerCase() if (type == 'number' && typeof value === 'number') str_value = value.toFixed(3) - // FIXME: any kludge - const elem: HTMLDivElement & { options?: unknown; value?: unknown } = - document.createElement('div') + const elem: PanelWidget = document.createElement('div') as PanelWidget elem.className = 'property' elem.innerHTML = "" @@ -7720,7 +7767,6 @@ export class LGraphCanvas if (!nameSpan) throw new TypeError('Property name element was null.') nameSpan.textContent = options.label || name - // TODO: any kludge const value_element: HTMLSpanElement | null = elem.querySelector('.property_value') if (!value_element) throw new TypeError('Property name element was null.') @@ -7732,7 +7778,8 @@ export class LGraphCanvas if (type == 'code') { elem.addEventListener('click', function () { - root.inner_showCodePad(this.dataset['property']) + const property = this.dataset['property'] + if (property) root.inner_showCodePad?.(property) }) } else if (type == 'boolean') { elem.classList.add('boolean') @@ -7775,33 +7822,27 @@ export class LGraphCanvas value_element.textContent = str_value ?? '' value_element.addEventListener('click', function (event) { - const values = options.values || [] + const values = options?.values || [] const propname = this.parentElement?.dataset['property'] - const inner_clicked = (v: string | null) => { - // node.setProperty(propname,v); - // graphcanvas.dirty_canvas = true; - this.textContent = v + const inner_clicked = (v?: string) => { + this.textContent = v ?? null innerChange(propname, v) return false } - new LiteGraph.ContextMenu( - values, - { - event, - className: 'dark', - callback: inner_clicked - }, - // @ts-expect-error ref_window parameter unused in ContextMenu constructor - ref_window - ) + new LiteGraph.ContextMenu(values, { + event, + className: 'dark', + callback: inner_clicked + }) }) } root.content.append(elem) - function innerChange(name: string | undefined, value: unknown) { - options.callback?.(name, value, options) - callback?.(name, value, options) + function innerChange(name: string | undefined, value: TWidgetValue) { + const opts = options || {} + opts.callback?.(name, value, opts) + callback?.(name, value, opts) } return elem @@ -7830,7 +7871,7 @@ export class LGraphCanvas }, onClose: () => { this.NODEPANEL_IS_OPEN = false - this.node_panel = null + this.node_panel = undefined } }) this.node_panel = panel @@ -7851,11 +7892,9 @@ export class LGraphCanvas panel.addHTML('

Properties

') - const fUpdate = ( - name: string, - value: string | number | boolean | object | undefined - ) => { + const fUpdate: PanelWidgetCallback = (name, value) => { if (!this.graph) throw new NullGraphError() + if (!name) return this.graph.beforeChange(node) switch (name) { case 'Title': @@ -7959,7 +7998,7 @@ export class LGraphCanvas panel.alt_content.innerHTML = "" const textarea: HTMLTextAreaElement = - panel.alt_content.querySelector('textarea') + panel.alt_content.querySelector('textarea')! const fDoneWith = function () { panel.toggleAltContent(false) panel.toggleFooterVisibility(true) @@ -8010,8 +8049,8 @@ export class LGraphCanvas } } - getCanvasMenuOptions(): IContextMenuValue[] { - let options: IContextMenuValue[] + getCanvasMenuOptions(): (IContextMenuValue | null)[] { + let options: (IContextMenuValue | null)[] if (this.getMenuOptions) { options = this.getMenuOptions() } else { @@ -8021,7 +8060,13 @@ export class LGraphCanvas has_submenu: true, callback: LGraphCanvas.onMenuAdd }, - { content: 'Add Group', callback: LGraphCanvas.onGroupAdd } + { content: 'Add Group', callback: LGraphCanvas.onGroupAdd }, + { + content: 'Paste', + callback: () => { + this.pasteFromClipboard() + } + } // { content: "Arrange", callback: that.graph.arrange }, // {content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll } ] @@ -8107,14 +8152,10 @@ export class LGraphCanvas { content: 'Properties Panel', callback: function ( - // @ts-expect-error - unused parameter - item: any, - // @ts-expect-error - unused parameter - options: any, - // @ts-expect-error - unused parameter - e: any, - // @ts-expect-error - unused parameter - menu: any, + _item: Positionable, + _options: IContextMenuOptions | undefined, + _e: MouseEvent | undefined, + _menu: ContextMenu | undefined, node: LGraphNode ) { LGraphCanvas.active_canvas.showShowNodePanel(node) @@ -8225,9 +8266,6 @@ export class LGraphCanvas node: LGraphNode | undefined, event: CanvasPointerEvent ): void { - const canvas = LGraphCanvas.active_canvas - const ref_window = canvas.getCanvasWindow() - // TODO: Remove type kludge let menu_info: (IContextMenuValue | string | null)[] const options: IContextMenuOptions = { @@ -8341,8 +8379,7 @@ export class LGraphCanvas // show menu if (!menu_info) return - // @ts-expect-error Remove param ref_window - unused - new LiteGraph.ContextMenu(menu_info, options, ref_window) + new LiteGraph.ContextMenu(menu_info, options) const createDialog = (options: IDialogOptions) => this.createDialog( @@ -8549,9 +8586,11 @@ export class LGraphCanvas node, newPos: this.calculateNewPosition(node, deltaX, deltaY) }) - } else { - // Non-node children (nested groups, reroutes) - child.move(deltaX, deltaY) + } else if (!(child instanceof LGraphGroup)) { + // Non-node, non-group children (reroutes, etc.) + // Skip groups here - they're already in allItems and will be + // processed in the main loop of moveChildNodesInGroupVueMode + child.move(deltaX, deltaY, true) } } } diff --git a/tests-ui/tests/litegraph/core/LGraphGroup.test.ts b/src/lib/litegraph/src/LGraphGroup.test.ts similarity index 84% rename from tests-ui/tests/litegraph/core/LGraphGroup.test.ts rename to src/lib/litegraph/src/LGraphGroup.test.ts index a50b33256..589e5a958 100644 --- a/tests-ui/tests/litegraph/core/LGraphGroup.test.ts +++ b/src/lib/litegraph/src/LGraphGroup.test.ts @@ -2,7 +2,7 @@ import { describe, expect } from 'vitest' import { LGraphGroup } from '@/lib/litegraph/src/litegraph' -import { test } from './fixtures/testExtensions' +import { test } from './__fixtures__/testExtensions' describe('LGraphGroup', () => { test('serializes to the existing format', () => { diff --git a/src/lib/litegraph/src/LGraphIcon.ts b/src/lib/litegraph/src/LGraphIcon.ts index b5aa628a6..a64981cc2 100644 --- a/src/lib/litegraph/src/LGraphIcon.ts +++ b/src/lib/litegraph/src/LGraphIcon.ts @@ -1,20 +1,24 @@ export interface LGraphIconOptions { - unicode: string + unicode?: string fontFamily?: string + image?: HTMLImageElement color?: string bgColor?: string fontSize?: number + size?: number circlePadding?: number xOffset?: number yOffset?: number } export class LGraphIcon { - unicode: string + unicode?: string fontFamily: string + image?: HTMLImageElement color: string bgColor?: string fontSize: number + size: number circlePadding: number xOffset: number yOffset: number @@ -22,18 +26,22 @@ export class LGraphIcon { constructor({ unicode, fontFamily = 'PrimeIcons', + image, color = '#e6c200', bgColor, fontSize = 16, + size, circlePadding = 2, xOffset = 0, yOffset = 0 }: LGraphIconOptions) { this.unicode = unicode this.fontFamily = fontFamily + this.image = image this.color = color this.bgColor = bgColor this.fontSize = fontSize + this.size = size ?? fontSize this.circlePadding = circlePadding this.xOffset = xOffset this.yOffset = yOffset @@ -43,26 +51,44 @@ export class LGraphIcon { x += this.xOffset y += this.yOffset - const { font, textBaseline, textAlign, fillStyle } = ctx + if (this.image) { + const iconSize = this.size + const iconRadius = iconSize / 2 + this.circlePadding - ctx.font = `${this.fontSize}px '${this.fontFamily}'` - ctx.textBaseline = 'middle' - ctx.textAlign = 'center' - const iconRadius = this.fontSize / 2 + this.circlePadding - // Draw icon background circle if bgColor is set - if (this.bgColor) { - ctx.beginPath() - ctx.arc(x + iconRadius, y, iconRadius, 0, 2 * Math.PI) - ctx.fillStyle = this.bgColor - ctx.fill() + if (this.bgColor) { + const { fillStyle } = ctx + ctx.beginPath() + ctx.arc(x + iconRadius, y, iconRadius, 0, 2 * Math.PI) + ctx.fillStyle = this.bgColor + ctx.fill() + ctx.fillStyle = fillStyle + } + + const imageX = x + this.circlePadding + const imageY = y - iconSize / 2 + ctx.drawImage(this.image, imageX, imageY, iconSize, iconSize) + } else if (this.unicode) { + const { font, textBaseline, textAlign, fillStyle } = ctx + + ctx.font = `${this.fontSize}px '${this.fontFamily}'` + ctx.textBaseline = 'middle' + ctx.textAlign = 'center' + const iconRadius = this.fontSize / 2 + this.circlePadding + + if (this.bgColor) { + ctx.beginPath() + ctx.arc(x + iconRadius, y, iconRadius, 0, 2 * Math.PI) + ctx.fillStyle = this.bgColor + ctx.fill() + } + + ctx.fillStyle = this.color + ctx.fillText(this.unicode, x + iconRadius, y) + + ctx.font = font + ctx.textBaseline = textBaseline + ctx.textAlign = textAlign + ctx.fillStyle = fillStyle } - // Draw icon - ctx.fillStyle = this.color - ctx.fillText(this.unicode, x + iconRadius, y) - - ctx.font = font - ctx.textBaseline = textBaseline - ctx.textAlign = textAlign - ctx.fillStyle = fillStyle } } diff --git a/tests-ui/tests/litegraph/core/LGraphNode.resize.test.ts b/src/lib/litegraph/src/LGraphNode.resize.test.ts similarity index 98% rename from tests-ui/tests/litegraph/core/LGraphNode.resize.test.ts rename to src/lib/litegraph/src/LGraphNode.resize.test.ts index eb967fcab..b3f971eed 100644 --- a/tests-ui/tests/litegraph/core/LGraphNode.resize.test.ts +++ b/src/lib/litegraph/src/LGraphNode.resize.test.ts @@ -2,7 +2,7 @@ import { beforeEach, describe, expect } from 'vitest' import { LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph' -import { test } from './fixtures/testExtensions' +import { test } from './__fixtures__/testExtensions' describe('LGraphNode resize functionality', () => { let node: LGraphNode diff --git a/tests-ui/tests/litegraph/core/LGraphNode.test.ts b/src/lib/litegraph/src/LGraphNode.test.ts similarity index 90% rename from tests-ui/tests/litegraph/core/LGraphNode.test.ts rename to src/lib/litegraph/src/LGraphNode.test.ts index be2828ec8..6682d274c 100644 --- a/tests-ui/tests/litegraph/core/LGraphNode.test.ts +++ b/src/lib/litegraph/src/LGraphNode.test.ts @@ -1,13 +1,24 @@ import { afterEach, beforeEach, describe, expect, vi } from 'vitest' -import type { INodeInputSlot, Point } from '@/lib/litegraph/src/litegraph' -import { LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph' -import { LGraph } from '@/lib/litegraph/src/litegraph' -import { NodeInputSlot } from '@/lib/litegraph/src/litegraph' -import { NodeOutputSlot } from '@/lib/litegraph/src/litegraph' -import type { ISerialisedNode } from '@/lib/litegraph/src/litegraph' +import type { + INodeInputSlot, + Point, + ISerialisedNode +} from '@/lib/litegraph/src/litegraph' +import { + LGraphNode, + LiteGraph, + LGraph, + NodeInputSlot, + NodeOutputSlot +} from '@/lib/litegraph/src/litegraph' -import { test } from './fixtures/testExtensions' +import { test } from './__fixtures__/testExtensions' +import { createMockLGraphNodeWithArrayBoundingRect } from '@/utils/__tests__/litegraphTestUtils' + +interface NodeConstructorWithSlotOffset { + slot_start_y?: number +} function getMockISerialisedNode( data: Partial @@ -32,7 +43,7 @@ describe('LGraphNode', () => { beforeEach(() => { origLiteGraph = Object.assign({}, LiteGraph) - // @ts-expect-error TODO: Fix after merge - Classes property not in type + // @ts-expect-error Intended: Force remove an otherwise readonly non-optional property delete origLiteGraph.Classes Object.assign(LiteGraph, { @@ -261,18 +272,40 @@ describe('LGraphNode', () => { }) }) + describe('Applies correct link type on connection', () => { + it.for<[string, string, string]>([ + ['IMAGE', 'IMAGE', 'IMAGE'], + ['*', 'IMAGE', 'IMAGE'], + ['IMAGE', '*', 'IMAGE'], + ['*', '*', '*'], + ['IMAGE,MASK', 'IMAGE,LATENT', 'IMAGE'], + //An invalid connection should use input type + ['Mask', 'IMAGE', 'IMAGE'] + ])( + 'Link from %s to %s should have type %s', + ([output, input, expected]) => { + const target = new LGraphNode('target') + const source = new LGraphNode('source') + const graph = new LGraph() + + target.addInput('input', input) + source.addOutput('output', output) + + graph.add(source) + graph.add(target) + + const link = source.connect(0, target, 0) + expect(link?.type).toBe(expected) + } + ) + }) + describe('getInputPos and getOutputPos', () => { test('should handle collapsed nodes correctly', () => { - const node = new LGraphNode('TestNode') as unknown as Omit< - LGraphNode, - 'boundingRect' - > & { boundingRect: Float64Array } + const node = createMockLGraphNodeWithArrayBoundingRect('TestNode') node.pos = [100, 100] node.size = [100, 100] - node.boundingRect[0] = 100 - node.boundingRect[1] = 100 - node.boundingRect[2] = 100 - node.boundingRect[3] = 100 + node.updateArea() node.configure( getMockISerialisedNode({ id: 1, @@ -332,16 +365,10 @@ describe('LGraphNode', () => { }) test('should detect input slots correctly', () => { - const node = new LGraphNode('TestNode') as unknown as Omit< - LGraphNode, - 'boundingRect' - > & { boundingRect: Float64Array } + const node = createMockLGraphNodeWithArrayBoundingRect('TestNode') node.pos = [100, 100] node.size = [100, 100] - node.boundingRect[0] = 100 - node.boundingRect[1] = 100 - node.boundingRect[2] = 200 - node.boundingRect[3] = 200 + node.updateArea() node.configure( getMockISerialisedNode({ id: 1, @@ -364,16 +391,10 @@ describe('LGraphNode', () => { }) test('should detect output slots correctly', () => { - const node = new LGraphNode('TestNode') as unknown as Omit< - LGraphNode, - 'boundingRect' - > & { boundingRect: Float64Array } + const node = createMockLGraphNodeWithArrayBoundingRect('TestNode') node.pos = [100, 100] node.size = [100, 100] - node.boundingRect[0] = 100 - node.boundingRect[1] = 100 - node.boundingRect[2] = 200 - node.boundingRect[3] = 200 + node.updateArea() node.configure( getMockISerialisedNode({ id: 1, @@ -397,16 +418,10 @@ describe('LGraphNode', () => { }) test('should prioritize input slots over output slots', () => { - const node = new LGraphNode('TestNode') as unknown as Omit< - LGraphNode, - 'boundingRect' - > & { boundingRect: Float64Array } + const node = createMockLGraphNodeWithArrayBoundingRect('TestNode') node.pos = [100, 100] node.size = [100, 100] - node.boundingRect[0] = 100 - node.boundingRect[1] = 100 - node.boundingRect[2] = 200 - node.boundingRect[3] = 200 + node.updateArea() node.configure( getMockISerialisedNode({ id: 1, @@ -598,7 +613,8 @@ describe('LGraphNode', () => { } node.inputs = [inputSlot, inputSlot2] const slotIndex = 0 - const nodeOffsetY = (node.constructor as any).slot_start_y || 0 + const nodeOffsetY = + (node.constructor as NodeConstructorWithSlotOffset).slot_start_y || 0 const expectedY = 200 + (slotIndex + 0.7) * LiteGraph.NODE_SLOT_HEIGHT + nodeOffsetY const expectedX = 100 + LiteGraph.NODE_SLOT_HEIGHT * 0.5 @@ -610,7 +626,7 @@ describe('LGraphNode', () => { }) test('should return default vertical position including slot_start_y when defined', () => { - ;(node.constructor as any).slot_start_y = 25 + ;(node.constructor as NodeConstructorWithSlotOffset).slot_start_y = 25 node.flags.collapsed = false node.inputs = [inputSlot] const slotIndex = 0 @@ -619,7 +635,7 @@ describe('LGraphNode', () => { 200 + (slotIndex + 0.7) * LiteGraph.NODE_SLOT_HEIGHT + nodeOffsetY const expectedX = 100 + LiteGraph.NODE_SLOT_HEIGHT * 0.5 expect(node.getInputSlotPos(inputSlot)).toEqual([expectedX, expectedY]) - delete (node.constructor as any).slot_start_y + delete (node.constructor as NodeConstructorWithSlotOffset).slot_start_y }) test('should not overwrite onMouseDown prototype', () => { expect(Object.prototype.hasOwnProperty.call(node, 'onMouseDown')).toEqual( diff --git a/tests-ui/tests/litegraph/core/LGraphNode.titleButtons.test.ts b/src/lib/litegraph/src/LGraphNode.titleButtons.test.ts similarity index 91% rename from tests-ui/tests/litegraph/core/LGraphNode.titleButtons.test.ts rename to src/lib/litegraph/src/LGraphNode.titleButtons.test.ts index 6599d6562..9d2a45992 100644 --- a/tests-ui/tests/litegraph/core/LGraphNode.titleButtons.test.ts +++ b/src/lib/litegraph/src/LGraphNode.titleButtons.test.ts @@ -1,8 +1,7 @@ import { describe, expect, it, vi } from 'vitest' -import { LGraphButton } from '@/lib/litegraph/src/litegraph' import type { LGraphCanvas } from '@/lib/litegraph/src/litegraph' -import { LGraphNode } from '@/lib/litegraph/src/litegraph' +import { LGraphButton, LGraphNode } from '@/lib/litegraph/src/litegraph' describe('LGraphNode Title Buttons', () => { describe('addTitleButton', () => { @@ -36,11 +35,10 @@ describe('LGraphNode Title Buttons', () => { expect(node.title_buttons[2]).toBe(button3) }) - it('should create buttons with default options', () => { + it('should create buttons with minimal options', () => { const node = new LGraphNode('Test Node') - // @ts-expect-error TODO: Fix after merge - addTitleButton type issues - const button = node.addTitleButton({}) + const button = node.addTitleButton({ text: '' }) expect(button).toBeInstanceOf(LGraphButton) expect(button.name).toBeUndefined() @@ -56,9 +54,7 @@ describe('LGraphNode Title Buttons', () => { const button = node.addTitleButton({ name: 'close_button', - text: 'X', - // @ts-expect-error TODO: Fix after merge - visible property not defined in type - visible: true + text: 'X' }) // Mock button methods @@ -113,9 +109,7 @@ describe('LGraphNode Title Buttons', () => { const button = node.addTitleButton({ name: 'test_button', - text: 'T', - // @ts-expect-error TODO: Fix after merge - visible property not defined in type - visible: true + text: 'T' }) button.getWidth = vi.fn().mockReturnValue(20) @@ -165,16 +159,12 @@ describe('LGraphNode Title Buttons', () => { const button1 = node.addTitleButton({ name: 'button1', - text: 'A', - // @ts-expect-error TODO: Fix after merge - visible property not defined in type - visible: true + text: 'A' }) const button2 = node.addTitleButton({ name: 'button2', - text: 'B', - // @ts-expect-error TODO: Fix after merge - visible property not defined in type - visible: true + text: 'B' }) // Mock button methods @@ -298,8 +288,7 @@ describe('LGraphNode Title Buttons', () => { describe('onTitleButtonClick', () => { it('should dispatch litegraph:node-title-button-clicked event', () => { const node = new LGraphNode('Test Node') - // @ts-expect-error TODO: Fix after merge - LGraphButton constructor type issues - const button = new LGraphButton({ name: 'test_button' }) + const button = new LGraphButton({ name: 'test_button', text: 'X' }) const canvas = { dispatch: vi.fn() diff --git a/src/lib/litegraph/src/LGraphNode.ts b/src/lib/litegraph/src/LGraphNode.ts index d4149e6ec..1ff503cdc 100644 --- a/src/lib/litegraph/src/LGraphNode.ts +++ b/src/lib/litegraph/src/LGraphNode.ts @@ -1,15 +1,16 @@ import { LGraphNodeProperties } from '@/lib/litegraph/src/LGraphNodeProperties' import { - calculateInputSlotPos, calculateInputSlotPosFromSlot, - calculateOutputSlotPos + getSlotPosition } from '@/renderer/core/canvas/litegraph/slotCalculations' import type { SlotPositionContext } from '@/renderer/core/canvas/litegraph/slotCalculations' import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations' import { LayoutSource } from '@/renderer/core/layout/types' import { adjustColor } from '@/utils/colorUtil' import type { ColorAdjustOptions } from '@/utils/colorUtil' +import { commonType, toClass } from '@/lib/litegraph/src/utils/type' +import { SUBGRAPH_OUTPUT_ID } from '@/lib/litegraph/src/constants' import type { DragAndScale } from './DragAndScale' import type { LGraph } from './LGraph' import { BadgePosition, LGraphBadge } from './LGraphBadge' @@ -38,14 +39,15 @@ import type { INodeSlotContextItem, IPinnable, ISlotType, + Panel, Point, Positionable, ReadOnlyRect, Rect, Size } from './interfaces' -import { LiteGraph } from './litegraph' -import type { LGraphNodeConstructor, Subgraph, SubgraphNode } from './litegraph' +import { LiteGraph, Subgraph } from './litegraph' +import type { LGraphNodeConstructor, SubgraphNode } from './litegraph' import { createBounds, isInRect, @@ -82,7 +84,6 @@ import { findFreeSlotOfType } from './utils/collections' import { warnDeprecated } from './utils/feedback' import { distributeSpace } from './utils/spaceDistribution' import { truncateText } from './utils/textUtils' -import { toClass } from './utils/type' import { BaseWidget } from './widgets/BaseWidget' import { toConcreteWidget } from './widgets/widgetMap' import type { WidgetTypeMap } from './widgets/widgetMap' @@ -94,9 +95,12 @@ export type NodeId = number | string export type NodeProperty = string | number | boolean | object interface INodePropertyInfo { - name: string + name?: string type?: string - default_value: NodeProperty | undefined + default_value?: NodeProperty + widget?: string + label?: string + values?: TWidgetValue[] } interface IMouseOverData { @@ -414,6 +418,7 @@ export class LGraphNode selected?: boolean showAdvanced?: boolean + declare comfyDynamic?: Record declare comfyClass?: string declare isVirtualNode?: boolean applyToGraph?(extraLinks?: LLink[]): void @@ -492,6 +497,7 @@ export class LGraphNode } set shape(v: RenderShape | 'default' | 'box' | 'round' | 'circle' | 'card') { + const oldValue = this._shape switch (v) { case 'default': this._shape = undefined @@ -511,6 +517,14 @@ export class LGraphNode default: this._shape = v } + if (oldValue !== this._shape) { + this.graph?.trigger('node:property:changed', { + nodeId: this.id, + property: 'shape', + oldValue, + newValue: this._shape + }) + } } /** @@ -567,11 +581,11 @@ export class LGraphNode onInputAdded?(this: LGraphNode, input: INodeInputSlot): void onOutputAdded?(this: LGraphNode, output: INodeOutputSlot): void onConfigure?(this: LGraphNode, serialisedNode: ISerialisedNode): void - onSerialize?(this: LGraphNode, serialised: ISerialisedNode): any + onSerialize?(this: LGraphNode, serialised: ISerialisedNode): void onExecute?( this: LGraphNode, param?: unknown, - options?: { action_call?: any } + options?: { action_call?: string } ): void onAction?( this: LGraphNode, @@ -594,8 +608,8 @@ export class LGraphNode target_slot: number, requested_slot?: number | string ): number | false | null - onShowCustomPanelInfo?(this: LGraphNode, panel: any): void - onAddPropertyToPanel?(this: LGraphNode, pName: string, panel: any): boolean + onShowCustomPanelInfo?(this: LGraphNode, panel: Panel): void + onAddPropertyToPanel?(this: LGraphNode, pName: string, panel: Panel): boolean onWidgetChanged?( this: LGraphNode, name: string, @@ -643,10 +657,10 @@ export class LGraphNode onDropData?( this: LGraphNode, data: string | ArrayBuffer, - filename: any, - file: any + filename: string, + file: File ): void - onDropFile?(this: LGraphNode, file: any): void + onDropFile?(this: LGraphNode, file: File): void onInputClick?(this: LGraphNode, index: number, e: CanvasPointerEvent): void onInputDblClick?(this: LGraphNode, index: number, e: CanvasPointerEvent): void onOutputClick?(this: LGraphNode, index: number, e: CanvasPointerEvent): void @@ -655,8 +669,7 @@ export class LGraphNode index: number, e: CanvasPointerEvent ): void - // TODO: Return type - onGetPropertyInfo?(this: LGraphNode, property: string): any + onGetPropertyInfo?(this: LGraphNode, property: string): INodePropertyInfo onNodeOutputAdd?(this: LGraphNode, value: unknown): void onNodeInputAdd?(this: LGraphNode, value: unknown): void onMenuNodeInputs?( @@ -667,7 +680,12 @@ export class LGraphNode this: LGraphNode, entries: (IContextMenuValue | null)[] ): (IContextMenuValue | null)[] - onMouseUp?(this: LGraphNode, e: CanvasPointerEvent, pos: Point): void + onMouseUp?( + this: LGraphNode, + e: CanvasPointerEvent, + pos: Point, + canvas: LGraphCanvas + ): void onMouseEnter?(this: LGraphNode, e: CanvasPointerEvent): void /** Blocks drag if return value is truthy. @param pos Offset from {@link LGraphNode.pos}. */ onMouseDown?( @@ -713,7 +731,7 @@ export class LGraphNode title_height: number, size: Size, scale: number, - fgcolor: any + fgcolor: string ): void onRemoved?(this: LGraphNode): void onMouseMove?( @@ -848,13 +866,12 @@ export class LGraphNode } if (info.widgets_values) { - const widgetsWithValue = this.widgets - .values() - .filter((w) => w.serialize !== false) - .filter((_w, idx) => idx < info.widgets_values!.length) - widgetsWithValue.forEach( - (widget, i) => (widget.value = info.widgets_values![i]) - ) + let i = 0 + for (const widget of this.widgets ?? []) { + if (widget.serialize === false) continue + if (i >= info.widgets_values.length) break + widget.value = info.widgets_values[i++] + } } } @@ -1285,7 +1302,7 @@ export class LGraphNode return trigS } - onAfterExecuteNode(param: unknown, options?: { action_call?: any }) { + onAfterExecuteNode(param: unknown, options?: { action_call?: string }) { const trigS = this.findOutputSlot('onExecuted') if (trigS != -1) { this.triggerSlot(trigS, param, null, options) @@ -1323,7 +1340,7 @@ export class LGraphNode /** * Triggers the node code execution, place a boolean/counter to mark the node as being executed */ - doExecute(param?: unknown, options?: { action_call?: any }): void { + doExecute(param?: unknown, options?: { action_call?: string }): void { options = options || {} if (this.onExecute) { // enable this to give the event an ID @@ -1389,7 +1406,7 @@ export class LGraphNode trigger( action: string, param: unknown, - options: { action_call?: any } + options: { action_call?: string } ): void { const { outputs } = this if (!outputs || !outputs.length) { @@ -1419,7 +1436,7 @@ export class LGraphNode slot: number, param: unknown, link_id: number | null, - options?: { action_call?: any } + options?: { action_call?: string } ): void { options = options || {} if (!this.outputs) return @@ -1650,19 +1667,6 @@ export class LGraphNode this.onInputRemoved?.(slot, slot_info[0]) this.setDirtyCanvas(true, true) } - spliceInputs( - startIndex: number, - deleteCount = -1, - ...toAdd: INodeInputSlot[] - ): INodeInputSlot[] { - if (deleteCount < 0) return this.inputs.splice(startIndex) - const ret = this.inputs.splice(startIndex, deleteCount, ...toAdd) - this.inputs.slice(startIndex).forEach((input, index) => { - const link = input.link && this.graph?.links?.get(input.link) - if (link) link.target_slot = startIndex + index - }) - return ret - } /** * computes the minimum size of a node according to its inputs and output slots @@ -2002,7 +2006,7 @@ export class LGraphNode * @param out `x, y, width, height` are written to this array. * @param ctx The canvas context to use for measuring text. */ - measure(out: Rect, ctx: CanvasRenderingContext2D): void { + measure(out: Rect, ctx?: CanvasRenderingContext2D): void { const titleMode = this.title_mode const renderTitle = titleMode != TitleMode.TRANSPARENT_TITLE && @@ -2015,11 +2019,13 @@ export class LGraphNode out[2] = this.size[0] out[3] = this.size[1] + titleHeight } else { - ctx.font = this.innerFontStyle + if (ctx) ctx.font = this.innerFontStyle this._collapsed_width = Math.min( this.size[0], - ctx.measureText(this.getTitle() ?? '').width + - LiteGraph.NODE_TITLE_HEIGHT * 2 + ctx + ? ctx.measureText(this.getTitle() ?? '').width + + LiteGraph.NODE_TITLE_HEIGHT * 2 + : 0 ) out[2] = this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH out[3] = LiteGraph.NODE_TITLE_HEIGHT @@ -2049,7 +2055,7 @@ export class LGraphNode * Calculates the render area of this node, populating both {@link boundingRect} and {@link renderArea}. * Called automatically at the start of every frame. */ - updateArea(ctx: CanvasRenderingContext2D): void { + updateArea(ctx?: CanvasRenderingContext2D): void { const bounds = this.#boundingRect this.measure(bounds, ctx) this.onBounding?.(bounds) @@ -2769,8 +2775,7 @@ export class LGraphNode !LiteGraph.allow_multi_output_for_events ) { graph.beforeChange() - // @ts-expect-error Unused param - this.disconnectOutput(slot, false, { doProcessChange: false }) + this.disconnectOutput(slot) } } @@ -2842,9 +2847,12 @@ export class LGraphNode inputNode.disconnectInput(inputIndex, true) } + const maybeCommonType = + input.type && output.type && commonType(input.type, output.type) + const link = new LLink( ++graph.state.lastLinkId, - input.type || output.type, + maybeCommonType || input.type || output.type, this.id, outputIndex, inputNode.id, @@ -3073,6 +3081,17 @@ export class LGraphNode for (const link_id of links) { const link_info = graph._links.get(link_id) if (!link_info) continue + if ( + link_info.target_id === SUBGRAPH_OUTPUT_ID && + graph instanceof Subgraph + ) { + const targetSlot = graph.outputNode.slots[link_info.target_slot] + if (targetSlot) { + targetSlot.linkIds.length = 0 + } else { + console.error('Missing subgraphOutput slot when disconnecting link') + } + } const target = graph.getNodeById(link_info.target_id) graph._version++ @@ -3328,7 +3347,7 @@ export class LGraphNode * @returns Position of the input slot */ getInputPos(slot: number): Point { - return calculateInputSlotPos(this.#getSlotPositionContext(), slot) + return getSlotPosition(this, slot, true) } /** @@ -3348,10 +3367,17 @@ export class LGraphNode * @returns Position of the output slot */ getOutputPos(outputSlotIndex: number): Point { - return calculateOutputSlotPos( - this.#getSlotPositionContext(), - outputSlotIndex - ) + return getSlotPosition(this, outputSlotIndex, false) + } + + /** + * Get slot position using layout tree if available, fallback to node's position * Unified implementation used by both LitegraphLinkAdapter and useLinkLayoutSync + * @param slotIndex The slot index + * @param isInput Whether this is an input slot + * @returns Position of the slot center in graph coordinates + */ + getSlotPosition(slotIndex: number, isInput: boolean): Point { + return getSlotPosition(this, slotIndex, isInput) } /** @inheritdoc */ @@ -3991,7 +4017,8 @@ export class LGraphNode isValidTarget || !slot.isWidgetInputSlot || this.#isMouseOverWidget(this.getWidgetFromSlot(slot)) || - slot.isConnected + slot.isConnected || + slot.alwaysVisible ) { ctx.globalAlpha = isValid ? editorAlpha : 0.4 * editorAlpha slot.draw(ctx, { @@ -4027,7 +4054,9 @@ export class LGraphNode w: IBaseWidget }[] = [] - for (const w of this.widgets) { + const visibleWidgets = this.widgets.filter((w) => !w.hidden) + + for (const w of visibleWidgets) { if (w.computeSize) { const height = w.computeSize()[1] + 4 w.computedHeight = height @@ -4066,7 +4095,7 @@ export class LGraphNode // Position widgets let y = startY - for (const w of this.widgets) { + for (const w of visibleWidgets) { w.y = y y += w.computedHeight ?? 0 } diff --git a/tests-ui/tests/litegraph/core/LGraphNode.widgetOrder.test.ts b/src/lib/litegraph/src/LGraphNode.widgetOrder.test.ts similarity index 100% rename from tests-ui/tests/litegraph/core/LGraphNode.widgetOrder.test.ts rename to src/lib/litegraph/src/LGraphNode.widgetOrder.test.ts diff --git a/tests-ui/tests/litegraph/core/LGraphNodeProperties.test.ts b/src/lib/litegraph/src/LGraphNodeProperties.test.ts similarity index 92% rename from tests-ui/tests/litegraph/core/LGraphNodeProperties.test.ts rename to src/lib/litegraph/src/LGraphNodeProperties.test.ts index aca6fe391..1c523945c 100644 --- a/tests-ui/tests/litegraph/core/LGraphNodeProperties.test.ts +++ b/src/lib/litegraph/src/LGraphNodeProperties.test.ts @@ -1,22 +1,25 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { beforeEach, describe, expect, it } from 'vitest' import { LGraphNodeProperties } from '@/lib/litegraph/src/LGraphNodeProperties' +import type { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph' +import { + createMockLGraph, + createMockLGraphNode +} from '@/utils/__tests__/litegraphTestUtils' describe('LGraphNodeProperties', () => { - let mockNode: any - let mockGraph: any + let mockNode: LGraphNode + let mockGraph: LGraph beforeEach(() => { - mockGraph = { - trigger: vi.fn() - } + mockGraph = createMockLGraph() - mockNode = { + mockNode = createMockLGraphNode({ id: 123, title: 'Test Node', flags: {}, graph: mockGraph - } + }) }) describe('property tracking', () => { diff --git a/src/lib/litegraph/src/LGraphNodeProperties.ts b/src/lib/litegraph/src/LGraphNodeProperties.ts index 6e635e228..9df938298 100644 --- a/src/lib/litegraph/src/LGraphNodeProperties.ts +++ b/src/lib/litegraph/src/LGraphNodeProperties.ts @@ -1,5 +1,9 @@ import type { LGraphNode } from './LGraphNode' +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null +} + /** * Default properties to track */ @@ -9,9 +13,10 @@ const DEFAULT_TRACKED_PROPERTIES: string[] = [ 'flags.pinned', 'mode', 'color', - 'bgcolor' + 'bgcolor', + 'shape', + 'showAdvanced' ] - /** * Manages node properties with optional change tracking and instrumentation. */ @@ -37,6 +42,34 @@ export class LGraphNodeProperties { } } + #resolveTargetObject(parts: string[]): { + targetObject: Record + propertyName: string + } { + // LGraphNode supports dynamic property access at runtime + let targetObject: Record = this.node as unknown as Record< + string, + unknown + > + + if (parts.length === 1) { + return { targetObject, propertyName: parts[0] } + } + + for (let i = 0; i < parts.length - 1; i++) { + const key = parts[i] + const next = targetObject[key] + if (isRecord(next)) { + targetObject = next + } + } + + return { + targetObject, + propertyName: parts[parts.length - 1] + } + } + /** * Instruments a single property to track changes */ @@ -47,15 +80,7 @@ export class LGraphNodeProperties { this.#ensureNestedPath(path) } - let targetObject: any = this.node - let propertyName = parts[0] - - if (parts.length > 1) { - for (let i = 0; i < parts.length - 1; i++) { - targetObject = targetObject[parts[i]] - } - propertyName = parts.at(-1)! - } + const { targetObject, propertyName } = this.#resolveTargetObject(parts) const hasProperty = Object.prototype.hasOwnProperty.call( targetObject, @@ -64,11 +89,11 @@ export class LGraphNodeProperties { const currentValue = targetObject[propertyName] if (!hasProperty) { - let value: any = undefined + let value: unknown = undefined Object.defineProperty(targetObject, propertyName, { get: () => value, - set: (newValue: any) => { + set: (newValue: unknown) => { const oldValue = value value = newValue this.#emitPropertyChange(path, oldValue, newValue) @@ -108,13 +133,20 @@ export class LGraphNodeProperties { */ #createInstrumentedDescriptor( propertyPath: string, - initialValue: any + initialValue: unknown ): PropertyDescriptor { - let value = initialValue + return this.#createInstrumentedDescriptorTyped(propertyPath, initialValue) + } + + #createInstrumentedDescriptorTyped( + propertyPath: string, + initialValue: TValue + ): PropertyDescriptor { + let value: TValue = initialValue return { get: () => value, - set: (newValue: any) => { + set: (newValue: TValue) => { const oldValue = value value = newValue this.#emitPropertyChange(propertyPath, oldValue, newValue) @@ -129,8 +161,16 @@ export class LGraphNodeProperties { */ #emitPropertyChange( propertyPath: string, - oldValue: any, - newValue: any + oldValue: unknown, + newValue: unknown + ): void { + this.#emitPropertyChangeTyped(propertyPath, oldValue, newValue) + } + + #emitPropertyChangeTyped( + propertyPath: string, + oldValue: TValue, + newValue: TValue ): void { this.node.graph?.trigger('node:property:changed', { nodeId: this.node.id, @@ -145,7 +185,11 @@ export class LGraphNodeProperties { */ #ensureNestedPath(path: string): void { const parts = path.split('.') - let current: any = this.node + // LGraphNode supports dynamic property access at runtime + let current: Record = this.node as unknown as Record< + string, + unknown + > // Create all parent objects except the last property for (let i = 0; i < parts.length - 1; i++) { @@ -153,7 +197,10 @@ export class LGraphNodeProperties { if (!current[part]) { current[part] = {} } - current = current[part] + const next = current[part] + if (isRecord(next)) { + current = next + } } } @@ -175,7 +222,7 @@ export class LGraphNodeProperties { * Custom toJSON method for JSON.stringify * Returns undefined to exclude from serialization since we only use defaults */ - toJSON(): any { + toJSON(): undefined { return undefined } } diff --git a/tests-ui/tests/litegraph/core/LLink.test.ts b/src/lib/litegraph/src/LLink.test.ts similarity index 89% rename from tests-ui/tests/litegraph/core/LLink.test.ts rename to src/lib/litegraph/src/LLink.test.ts index a1cdfbea0..fc3f5918b 100644 --- a/tests-ui/tests/litegraph/core/LLink.test.ts +++ b/src/lib/litegraph/src/LLink.test.ts @@ -2,7 +2,7 @@ import { describe, expect } from 'vitest' import { LLink } from '@/lib/litegraph/src/litegraph' -import { test } from './fixtures/testExtensions' +import { test } from './__fixtures__/testExtensions' describe('LLink', () => { test('matches previous snapshot', () => { diff --git a/src/lib/litegraph/src/LiteGraphGlobal.ts b/src/lib/litegraph/src/LiteGraphGlobal.ts index d3ae9f018..6a9cb5db6 100644 --- a/src/lib/litegraph/src/LiteGraphGlobal.ts +++ b/src/lib/litegraph/src/LiteGraphGlobal.ts @@ -59,16 +59,16 @@ export class LiteGraphGlobal { NODE_DEFAULT_SHAPE = RenderShape.ROUND NODE_BOX_OUTLINE_COLOR = '#FFF' NODE_ERROR_COLOUR = '#E00' - NODE_FONT = 'Arial' + NODE_FONT = 'Inter' NODE_DEFAULT_BYPASS_COLOR = '#FF00FF' NODE_OPACITY = 0.9 - DEFAULT_FONT = 'Arial' + DEFAULT_FONT = 'Inter' DEFAULT_SHADOW_COLOR = 'rgba(0,0,0,0.5)' DEFAULT_GROUP_FONT = 24 - DEFAULT_GROUP_FONT_SIZE?: any - GROUP_FONT = 'Arial' + DEFAULT_GROUP_FONT_SIZE = 24 + GROUP_FONT = 'Inter' WIDGET_BGCOLOR = '#222' WIDGET_OUTLINE_COLOR = '#666' @@ -716,7 +716,7 @@ export class LiteGraphGlobal { } // used to create nodes from wrapping functions - getParameterNames(func: (...args: any) => any): string[] { + getParameterNames(func: (...args: unknown[]) => unknown): string[] { return String(func) .replaceAll(/\/\/.*$/gm, '') // strip single-line comments .replaceAll(/\s+/g, '') // strip white space @@ -971,7 +971,10 @@ export class LiteGraphGlobal { } } - extendClass(target: any, origin: any): void { + extendClass( + target: Record & { prototype?: object }, + origin: Record & { prototype?: object } + ): void { for (const i in origin) { // copy class properties // eslint-disable-next-line no-prototype-builtins @@ -979,33 +982,24 @@ export class LiteGraphGlobal { target[i] = origin[i] } - if (origin.prototype) { + if (origin.prototype && target.prototype) { + const originProto = origin.prototype as Record + const targetProto = target.prototype as Record + // copy prototype properties - for (const i in origin.prototype) { + for (const i in originProto) { // only enumerable // eslint-disable-next-line no-prototype-builtins - if (!origin.prototype.hasOwnProperty(i)) continue + if (!originProto.hasOwnProperty(i)) continue // avoid overwriting existing ones // eslint-disable-next-line no-prototype-builtins - if (target.prototype.hasOwnProperty(i)) continue + if (targetProto.hasOwnProperty(i)) continue - // copy getters - if (origin.prototype.__lookupGetter__(i)) { - target.prototype.__defineGetter__( - i, - origin.prototype.__lookupGetter__(i) - ) - } else { - target.prototype[i] = origin.prototype[i] - } - - // and setters - if (origin.prototype.__lookupSetter__(i)) { - target.prototype.__defineSetter__( - i, - origin.prototype.__lookupSetter__(i) - ) + // Use Object.getOwnPropertyDescriptor to copy getters/setters properly + const descriptor = Object.getOwnPropertyDescriptor(originProto, i) + if (descriptor) { + Object.defineProperty(targetProto, i, descriptor) } } } diff --git a/src/lib/litegraph/src/MapProxyHandler.ts b/src/lib/litegraph/src/MapProxyHandler.ts index 664ecb3d3..3b8aa5cfc 100644 --- a/src/lib/litegraph/src/MapProxyHandler.ts +++ b/src/lib/litegraph/src/MapProxyHandler.ts @@ -2,9 +2,9 @@ * Temporary workaround until downstream consumers migrate to Map. * A brittle wrapper with many flaws, but should be fine for simple maps using int indexes. */ -export class MapProxyHandler - implements ProxyHandler> -{ +export class MapProxyHandler implements ProxyHandler< + Map +> { getOwnPropertyDescriptor( target: Map, p: string | symbol @@ -30,7 +30,7 @@ export class MapProxyHandler return [...target.keys()].map(String) } - get(target: Map, p: string | symbol): any { + get(target: Map, p: string | symbol): V | undefined { // Workaround does not support link IDs of "values", "entries", "constructor", etc. if (p in target) return Reflect.get(target, p, target) if (typeof p === 'symbol') return @@ -42,7 +42,7 @@ export class MapProxyHandler set( target: Map, p: string | symbol, - newValue: any + newValue: V ): boolean { if (typeof p === 'symbol') return false @@ -55,7 +55,7 @@ export class MapProxyHandler return target.delete(p as number | string) } - static bindAllMethods(map: Map): void { + static bindAllMethods(map: Map): void { map.clear = map.clear.bind(map) map.delete = map.delete.bind(map) map.forEach = map.forEach.bind(map) diff --git a/tests-ui/tests/litegraph/core/fixtures/assets/floatingBranch.json b/src/lib/litegraph/src/__fixtures__/assets/floatingBranch.json similarity index 100% rename from tests-ui/tests/litegraph/core/fixtures/assets/floatingBranch.json rename to src/lib/litegraph/src/__fixtures__/assets/floatingBranch.json diff --git a/tests-ui/tests/litegraph/core/fixtures/assets/floatingLink.json b/src/lib/litegraph/src/__fixtures__/assets/floatingLink.json similarity index 100% rename from tests-ui/tests/litegraph/core/fixtures/assets/floatingLink.json rename to src/lib/litegraph/src/__fixtures__/assets/floatingLink.json diff --git a/tests-ui/tests/litegraph/core/fixtures/assets/linkedNodes.json b/src/lib/litegraph/src/__fixtures__/assets/linkedNodes.json similarity index 100% rename from tests-ui/tests/litegraph/core/fixtures/assets/linkedNodes.json rename to src/lib/litegraph/src/__fixtures__/assets/linkedNodes.json diff --git a/tests-ui/tests/litegraph/core/fixtures/assets/reroutesComplex.json b/src/lib/litegraph/src/__fixtures__/assets/reroutesComplex.json similarity index 100% rename from tests-ui/tests/litegraph/core/fixtures/assets/reroutesComplex.json rename to src/lib/litegraph/src/__fixtures__/assets/reroutesComplex.json diff --git a/tests-ui/tests/litegraph/core/fixtures/assets/testGraphs.ts b/src/lib/litegraph/src/__fixtures__/assets/testGraphs.ts similarity index 81% rename from tests-ui/tests/litegraph/core/fixtures/assets/testGraphs.ts rename to src/lib/litegraph/src/__fixtures__/assets/testGraphs.ts index ffed09e6b..03a3a1260 100644 --- a/tests-ui/tests/litegraph/core/fixtures/assets/testGraphs.ts +++ b/src/lib/litegraph/src/__fixtures__/assets/testGraphs.ts @@ -1,5 +1,6 @@ import type { ISerialisedGraph, + ISerialisedNode, SerialisableGraph } from '@/lib/litegraph/src/litegraph' @@ -19,12 +20,7 @@ export const oldSchemaGraph: ISerialisedGraph = { title: 'A group to test with' } ], - nodes: [ - // @ts-expect-error TODO: Fix after merge - missing required properties for test - { - id: 1 - } - ], + nodes: [{ id: 1 } as Partial as ISerialisedNode], links: [] } @@ -65,11 +61,7 @@ export const basicSerialisableGraph: SerialisableGraph = { } ], nodes: [ - // @ts-expect-error TODO: Fix after merge - missing required properties for test - { - id: 1, - type: 'mustBeSet' - } + { id: 1, type: 'mustBeSet' } as Partial as ISerialisedNode ], links: [] } diff --git a/tests-ui/tests/litegraph/core/fixtures/testExtensions.ts b/src/lib/litegraph/src/__fixtures__/testExtensions.ts similarity index 93% rename from tests-ui/tests/litegraph/core/fixtures/testExtensions.ts rename to src/lib/litegraph/src/__fixtures__/testExtensions.ts index 300b23814..6aff55438 100644 --- a/tests-ui/tests/litegraph/core/fixtures/testExtensions.ts +++ b/src/lib/litegraph/src/__fixtures__/testExtensions.ts @@ -1,3 +1,4 @@ +// oxlint-disable no-empty-pattern import { test as baseTest } from 'vitest' import { LGraph } from '@/lib/litegraph/src/LGraph' @@ -33,7 +34,6 @@ interface DirtyFixtures { } export const test = baseTest.extend({ - // eslint-disable-next-line no-empty-pattern minimalGraph: async ({}, use) => { // Before each test function const serialisable = structuredClone(minimalSerialisableGraph) @@ -48,7 +48,7 @@ export const test = baseTest.extend({ floatingLink as unknown as ISerialisedGraph ), linkedNodesGraph: structuredClone(linkedNodes as unknown as ISerialisedGraph), - // eslint-disable-next-line no-empty-pattern + floatingBranchGraph: async ({}, use) => { const cloned = structuredClone( floatingBranch as unknown as ISerialisedGraph @@ -56,7 +56,7 @@ export const test = baseTest.extend({ const graph = new LGraph(cloned) await use(graph) }, - // eslint-disable-next-line no-empty-pattern + reroutesComplexGraph: async ({}, use) => { const cloned = structuredClone( reroutesComplex as unknown as ISerialisedGraph @@ -68,7 +68,6 @@ export const test = baseTest.extend({ /** Test that use {@link DirtyFixtures}. One test per file. */ export const dirtyTest = test.extend({ - // eslint-disable-next-line no-empty-pattern basicSerialisableGraph: async ({}, use) => { if (!basicSerialisableGraph.nodes) throw new Error('Invalid test object') diff --git a/tests-ui/tests/litegraph/core/__snapshots__/LGraph.test.ts.snap b/src/lib/litegraph/src/__snapshots__/LGraph.test.ts.snap similarity index 100% rename from tests-ui/tests/litegraph/core/__snapshots__/LGraph.test.ts.snap rename to src/lib/litegraph/src/__snapshots__/LGraph.test.ts.snap diff --git a/tests-ui/tests/litegraph/core/__snapshots__/LGraphGroup.test.ts.snap b/src/lib/litegraph/src/__snapshots__/LGraphGroup.test.ts.snap similarity index 100% rename from tests-ui/tests/litegraph/core/__snapshots__/LGraphGroup.test.ts.snap rename to src/lib/litegraph/src/__snapshots__/LGraphGroup.test.ts.snap diff --git a/tests-ui/tests/litegraph/core/__snapshots__/LLink.test.ts.snap b/src/lib/litegraph/src/__snapshots__/LLink.test.ts.snap similarity index 100% rename from tests-ui/tests/litegraph/core/__snapshots__/LLink.test.ts.snap rename to src/lib/litegraph/src/__snapshots__/LLink.test.ts.snap diff --git a/tests-ui/tests/litegraph/core/__snapshots__/litegraph.test.ts.snap b/src/lib/litegraph/src/__snapshots__/litegraph.test.ts.snap similarity index 97% rename from tests-ui/tests/litegraph/core/__snapshots__/litegraph.test.ts.snap rename to src/lib/litegraph/src/__snapshots__/litegraph.test.ts.snap index 1c61d1272..6bdd6b678 100644 --- a/tests-ui/tests/litegraph/core/__snapshots__/litegraph.test.ts.snap +++ b/src/lib/litegraph/src/__snapshots__/litegraph.test.ts.snap @@ -20,9 +20,9 @@ LiteGraphGlobal { }, "ContextMenu": [Function], "CurveEditor": [Function], - "DEFAULT_FONT": "Arial", + "DEFAULT_FONT": "Inter", "DEFAULT_GROUP_FONT": 24, - "DEFAULT_GROUP_FONT_SIZE": undefined, + "DEFAULT_GROUP_FONT_SIZE": 24, "DEFAULT_POSITION": [ 100, 100, @@ -33,7 +33,7 @@ LiteGraphGlobal { "EVENT": -1, "EVENT_LINK_COLOR": "#A86", "GRID_SHAPE": 6, - "GROUP_FONT": "Arial", + "GROUP_FONT": "Inter", "Globals": {}, "HIDDEN_LINK": -1, "INPUT": 1, @@ -65,7 +65,7 @@ LiteGraphGlobal { "NODE_DEFAULT_COLOR": "#333", "NODE_DEFAULT_SHAPE": 2, "NODE_ERROR_COLOUR": "#E00", - "NODE_FONT": "Arial", + "NODE_FONT": "Inter", "NODE_MIN_WIDTH": 50, "NODE_MODES": [ "Always", diff --git a/src/lib/litegraph/src/canvas/InputIndicators.ts b/src/lib/litegraph/src/canvas/InputIndicators.ts index ba014a7f2..ad98cb533 100644 --- a/src/lib/litegraph/src/canvas/InputIndicators.ts +++ b/src/lib/litegraph/src/canvas/InputIndicators.ts @@ -23,7 +23,7 @@ export class InputIndicators implements Disposable { colour1 = '#ff5f00' colour2 = '#00ff7c' colour3 = '#dea7ff' - fontString = 'bold 12px Arial' + fontString = 'bold 12px Inter, sans-serif' // #endregion // #region state diff --git a/tests-ui/tests/litegraph/core/LinkConnector.test.ts b/src/lib/litegraph/src/canvas/LinkConnector.core.test.ts similarity index 92% rename from tests-ui/tests/litegraph/core/LinkConnector.test.ts rename to src/lib/litegraph/src/canvas/LinkConnector.core.test.ts index 52faacf56..4b1d29cca 100644 --- a/tests-ui/tests/litegraph/core/LinkConnector.test.ts +++ b/src/lib/litegraph/src/canvas/LinkConnector.core.test.ts @@ -1,23 +1,31 @@ +// oxlint-disable no-empty-pattern import { test as baseTest, describe, expect, vi } from 'vitest' -import { LinkConnector } from '@/lib/litegraph/src/litegraph' -import type { MovingInputLink } from '@/lib/litegraph/src/litegraph' -import { ToInputRenderLink } from '@/lib/litegraph/src/litegraph' -import type { LinkNetwork } from '@/lib/litegraph/src/litegraph' -import type { ISlotType } from '@/lib/litegraph/src/litegraph' +import type { + MovingInputLink, + RerouteId, + LinkNetwork, + ISlotType +} from '@/lib/litegraph/src/litegraph' import { LGraph, LGraphNode, LLink, Reroute, - type RerouteId + LinkConnector, + ToInputRenderLink, + LinkDirection } from '@/lib/litegraph/src/litegraph' -import { LinkDirection } from '@/lib/litegraph/src/litegraph' +import type { ConnectingLink } from '@/lib/litegraph/src/interfaces' +import { + createMockNodeInputSlot, + createMockNodeOutputSlot +} from '@/utils/__tests__/litegraphTestUtils' interface TestContext { network: LinkNetwork & { add(node: LGraphNode): void } connector: LinkConnector - setConnectingLinks: ReturnType + setConnectingLinks: (value: ConnectingLink[]) => void createTestNode: (id: number, slotType?: ISlotType) => LGraphNode createTestLink: ( id: number, @@ -28,7 +36,6 @@ interface TestContext { } const test = baseTest.extend({ - // eslint-disable-next-line no-empty-pattern network: async ({}, use) => { const graph = new LGraph() const floatingLinks = new Map() @@ -53,9 +60,8 @@ const test = baseTest.extend({ }, setConnectingLinks: async ( - // eslint-disable-next-line no-empty-pattern {}, - use: (mock: ReturnType) => Promise + use: (mock: (value: ConnectingLink[]) => void) => Promise ) => { const mock = vi.fn() await use(mock) @@ -134,7 +140,7 @@ describe('LinkConnector', () => { connector.state.connectingTo = 'input' expect(() => { - connector.moveInputLink(network, { link: 1 } as any) + connector.moveInputLink(network, createMockNodeInputSlot({ link: 1 })) }).toThrow('Already dragging links.') }) }) @@ -172,7 +178,10 @@ describe('LinkConnector', () => { connector.state.connectingTo = 'output' expect(() => { - connector.moveOutputLink(network, { links: [1] } as any) + connector.moveOutputLink( + network, + createMockNodeOutputSlot({ links: [1] }) + ) }).toThrow('Already dragging links.') }) }) diff --git a/tests-ui/tests/litegraph/core/LinkConnector.integration.test.ts b/src/lib/litegraph/src/canvas/LinkConnector.integration.test.ts similarity index 93% rename from tests-ui/tests/litegraph/core/LinkConnector.integration.test.ts rename to src/lib/litegraph/src/canvas/LinkConnector.integration.test.ts index da99d9135..671709345 100644 --- a/tests-ui/tests/litegraph/core/LinkConnector.integration.test.ts +++ b/src/lib/litegraph/src/canvas/LinkConnector.integration.test.ts @@ -1,21 +1,26 @@ +// oxlint-disable no-empty-pattern // TODO: Fix these tests after migration import { afterEach, describe, expect, vi } from 'vitest' -import type { LGraph, Reroute } from '@/lib/litegraph/src/litegraph' -import { - type CanvasPointerEvent, - LGraphNode, - LLink, - LinkConnector, - type RerouteId +import type { + LGraph, + Reroute, + CanvasPointerEvent, + RerouteId } from '@/lib/litegraph/src/litegraph' +import { LGraphNode, LLink, LinkConnector } from '@/lib/litegraph/src/litegraph' -import { test as baseTest } from './fixtures/testExtensions' +import { test as baseTest } from '../__fixtures__/testExtensions' +import type { ConnectingLink } from '@/lib/litegraph/src/interfaces' +import { + createMockCanvasPointerEvent, + createMockCanvasRenderingContext2D +} from '@/utils/__tests__/litegraphTestUtils' interface TestContext { graph: LGraph connector: LinkConnector - setConnectingLinks: ReturnType + setConnectingLinks: (value: ConnectingLink[]) => void createTestNode: (id: number) => LGraphNode reroutesBeforeTest: [rerouteId: RerouteId, reroute: Reroute][] validateIntegrityNoChanges: () => void @@ -34,16 +39,15 @@ const test = baseTest.extend({ }, graph: async ({ reroutesComplexGraph }, use) => { - const ctx = vi.fn(() => ({ measureText: vi.fn(() => ({ width: 10 })) })) + const mockCtx = createMockCanvasRenderingContext2D() for (const node of reroutesComplexGraph.nodes) { - node.updateArea(ctx() as unknown as CanvasRenderingContext2D) + node.updateArea(mockCtx) } await use(reroutesComplexGraph) }, setConnectingLinks: async ( - // eslint-disable-next-line no-empty-pattern {}, - use: (mock: ReturnType) => Promise + use: (mock: (value: ConnectingLink[]) => void) => Promise ) => { const mock = vi.fn() await use(mock) @@ -186,10 +190,10 @@ const test = baseTest.extend({ }) function mockedNodeTitleDropEvent(node: LGraphNode): CanvasPointerEvent { - return { - canvasX: node.pos[0] + node.size[0] / 2, - canvasY: node.pos[1] + 16 - } as any + return createMockCanvasPointerEvent( + node.pos[0] + node.size[0] / 2, + node.pos[1] + 16 + ) } function mockedInputDropEvent( @@ -197,10 +201,7 @@ function mockedInputDropEvent( slot: number ): CanvasPointerEvent { const pos = node.getInputPos(slot) - return { - canvasX: pos[0], - canvasY: pos[1] - } as any + return createMockCanvasPointerEvent(pos[0], pos[1]) } function mockedOutputDropEvent( @@ -208,10 +209,7 @@ function mockedOutputDropEvent( slot: number ): CanvasPointerEvent { const pos = node.getOutputPos(slot) - return { - canvasX: pos[0], - canvasY: pos[1] - } as any + return createMockCanvasPointerEvent(pos[0], pos[1]) } describe('LinkConnector Integration', () => { @@ -239,7 +237,7 @@ describe('LinkConnector Integration', () => { const canvasX = disconnectedNode.pos[0] + disconnectedNode.size[0] / 2 const canvasY = disconnectedNode.pos[1] + 16 - const dropEvent = { canvasX, canvasY } as any + const dropEvent = createMockCanvasPointerEvent(canvasX, canvasY) // Drop links, ensure reset has not been run connector.dropLinks(graph, dropEvent) @@ -281,7 +279,7 @@ describe('LinkConnector Integration', () => { const canvasX = disconnectedNode.pos[0] + disconnectedNode.size[0] / 2 const canvasY = disconnectedNode.pos[1] + 16 - const dropEvent = { canvasX, canvasY } as any + const dropEvent = createMockCanvasPointerEvent(canvasX, canvasY) connector.dropLinks(graph, dropEvent) connector.reset() @@ -422,7 +420,7 @@ describe('LinkConnector Integration', () => { const canvasX = disconnectedNode.pos[0] + disconnectedNode.size[0] / 2 const canvasY = disconnectedNode.pos[1] + 16 - const dropEvent = { canvasX, canvasY } as any + const dropEvent = createMockCanvasPointerEvent(canvasX, canvasY) connector.dropLinks(graph, dropEvent) connector.reset() @@ -473,9 +471,10 @@ describe('LinkConnector Integration', () => { expect(floatingLink).toBeInstanceOf(LLink) const floatingReroute = LLink.getReroutes(graph, floatingLink)[0] - const canvasX = floatingReroute.pos[0] - const canvasY = floatingReroute.pos[1] - const dropEvent = { canvasX, canvasY } as any + const dropEvent = createMockCanvasPointerEvent( + floatingReroute.pos[0], + floatingReroute.pos[1] + ) connector.dropLinks(graph, dropEvent) connector.reset() @@ -554,7 +553,10 @@ describe('LinkConnector Integration', () => { const manyOutputsNode = graph.getNodeById(4)! const canvasX = floatingReroute.pos[0] const canvasY = floatingReroute.pos[1] - const floatingRerouteEvent = { canvasX, canvasY } as any + const floatingRerouteEvent = createMockCanvasPointerEvent( + canvasX, + canvasY + ) connector.moveOutputLink(graph, manyOutputsNode.outputs[0]) connector.dropLinks(graph, floatingRerouteEvent) @@ -579,7 +581,7 @@ describe('LinkConnector Integration', () => { const canvasX = reroute7.pos[0] const canvasY = reroute7.pos[1] - const reroute7Event = { canvasX, canvasY } as any + const reroute7Event = createMockCanvasPointerEvent(canvasX, canvasY) const toSortedRerouteChain = (linkIds: number[]) => linkIds @@ -698,7 +700,7 @@ describe('LinkConnector Integration', () => { const canvasY = disconnectedNode.pos[1] connector.dragFromReroute(graph, floatingReroute) - connector.dropLinks(graph, { canvasX, canvasY } as any) + connector.dropLinks(graph, createMockCanvasPointerEvent(canvasX, canvasY)) connector.reset() expect(graph.floatingLinks.size).toBe(0) @@ -716,7 +718,7 @@ describe('LinkConnector Integration', () => { const canvasY = reroute8.pos[1] connector.dragFromReroute(graph, floatingReroute) - connector.dropLinks(graph, { canvasX, canvasY } as any) + connector.dropLinks(graph, createMockCanvasPointerEvent(canvasX, canvasY)) connector.reset() expect(graph.floatingLinks.size).toBe(0) @@ -801,10 +803,10 @@ describe('LinkConnector Integration', () => { connector.moveOutputLink(graph, floatingOutNode.outputs[0]) const manyOutputsNode = graph.getNodeById(4)! - const dropEvent = { - canvasX: manyOutputsNode.pos[0], - canvasY: manyOutputsNode.pos[1] - } as any + const dropEvent = createMockCanvasPointerEvent( + manyOutputsNode.pos[0], + manyOutputsNode.pos[1] + ) connector.dropLinks(graph, dropEvent) connector.reset() @@ -818,9 +820,11 @@ describe('LinkConnector Integration', () => { connector.moveOutputLink(graph, manyOutputsNode.outputs[0]) const disconnectedNode = graph.getNodeById(9)! - dropEvent.canvasX = disconnectedNode.pos[0] - dropEvent.canvasY = disconnectedNode.pos[1] - connector.dropLinks(graph, dropEvent) + const dropEvent2 = createMockCanvasPointerEvent( + disconnectedNode.pos[0], + disconnectedNode.pos[1] + ) + connector.dropLinks(graph, dropEvent2) connector.reset() const newOutput = disconnectedNode.outputs[0] @@ -951,10 +955,10 @@ describe('LinkConnector Integration', () => { const targetReroute = graph.reroutes.get(targetRerouteId)! const nextLinkIds = getNextLinkIds(targetReroute.linkIds) - const dropEvent = { - canvasX: targetReroute.pos[0], - canvasY: targetReroute.pos[1] - } as any + const dropEvent = createMockCanvasPointerEvent( + targetReroute.pos[0], + targetReroute.pos[1] + ) connector.dragNewFromOutput( graph, @@ -1078,6 +1082,7 @@ describe('LinkConnector Integration', () => { const originalParentChain = LLink.getReroutes(graph, toReroute) const sortAndJoin = (numbers: Iterable) => + // oxlint-disable-next-line require-array-sort-compare [...numbers].sort().join(',') const hasIdenticalLinks = (a: Reroute, b: Reroute) => sortAndJoin(a.linkIds) === sortAndJoin(b.linkIds) && @@ -1093,10 +1098,10 @@ describe('LinkConnector Integration', () => { connector.dragFromReroute(graph, fromReroute) - const dropEvent = { - canvasX: toReroute.pos[0], - canvasY: toReroute.pos[1] - } as any + const dropEvent = createMockCanvasPointerEvent( + toReroute.pos[0], + toReroute.pos[1] + ) connector.dropLinks(graph, dropEvent) connector.reset() @@ -1166,10 +1171,10 @@ describe('LinkConnector Integration', () => { const fromReroute = graph.reroutes.get(from)! const toReroute = graph.reroutes.get(to)! - const dropEvent = { - canvasX: toReroute.pos[0], - canvasY: toReroute.pos[1] - } as any + const dropEvent = createMockCanvasPointerEvent( + toReroute.pos[0], + toReroute.pos[1] + ) connector.dragFromReroute(graph, fromReroute) connector.dropLinks(graph, dropEvent) @@ -1203,10 +1208,10 @@ describe('LinkConnector Integration', () => { const node = graph.getNodeById(nodeId)! const input = node.inputs[0] const reroute = graph.getReroute(rerouteId)! - const dropEvent = { - canvasX: reroute.pos[0], - canvasY: reroute.pos[1] - } as any + const dropEvent = createMockCanvasPointerEvent( + reroute.pos[0], + reroute.pos[1] + ) connector.dragNewFromInput(graph, node, input) connector.dropLinks(graph, dropEvent) @@ -1233,7 +1238,7 @@ describe('LinkConnector Integration', () => { const node = graph.getNodeById(nodeId)! const reroute = graph.getReroute(rerouteId)! - const dropEvent = { canvasX: node.pos[0], canvasY: node.pos[1] } as any + const dropEvent = createMockCanvasPointerEvent(node.pos[0], node.pos[1]) connector.dragFromReroute(graph, reroute) connector.dropLinks(graph, dropEvent) @@ -1261,10 +1266,10 @@ describe('LinkConnector Integration', () => { const node = graph.getNodeById(nodeId)! const reroute = graph.getReroute(rerouteId)! const inputPos = node.getInputPos(0) - const dropOnInputEvent = { - canvasX: inputPos[0], - canvasY: inputPos[1] - } as any + const dropOnInputEvent = createMockCanvasPointerEvent( + inputPos[0], + inputPos[1] + ) connector.dragFromReroute(graph, reroute) connector.dropLinks(graph, dropOnInputEvent) diff --git a/tests-ui/tests/litegraph/canvas/LinkConnector.test.ts b/src/lib/litegraph/src/canvas/LinkConnector.test.ts similarity index 76% rename from tests-ui/tests/litegraph/canvas/LinkConnector.test.ts rename to src/lib/litegraph/src/canvas/LinkConnector.test.ts index 29786bdf1..9ec8e3dfb 100644 --- a/tests-ui/tests/litegraph/canvas/LinkConnector.test.ts +++ b/src/lib/litegraph/src/canvas/LinkConnector.test.ts @@ -1,23 +1,46 @@ // TODO: Fix these tests after migration import { beforeEach, describe, expect, test, vi } from 'vitest' -import type { INodeInputSlot, LGraphNode } from '@/lib/litegraph/src/litegraph' -// We don't strictly need RenderLink interface import for the mock import { LinkConnector } from '@/lib/litegraph/src/litegraph' +import { + createMockCanvasPointerEvent, + createMockLGraphNode, + createMockLinkNetwork, + createMockNodeInputSlot, + createMockNodeOutputSlot +} from '@/utils/__tests__/litegraphTestUtils' // Mocks const mockSetConnectingLinks = vi.fn() +type RenderLinkItem = LinkConnector['renderLinks'][number] + // Mock a structure that has the needed method -function mockRenderLinkImpl(canConnect: boolean) { - return { - canConnectToInput: vi.fn().mockReturnValue(canConnect) - // Add other properties if they become necessary for tests +function mockRenderLinkImpl(canConnect: boolean): RenderLinkItem { + const partial: Partial = { + toType: 'output', + fromPos: [0, 0], + fromSlotIndex: 0, + fromDirection: 0, + network: createMockLinkNetwork(), + node: createMockLGraphNode(), + fromSlot: createMockNodeOutputSlot(), + dragDirection: 0, + canConnectToInput: vi.fn().mockReturnValue(canConnect), + canConnectToOutput: vi.fn().mockReturnValue(false), + canConnectToReroute: vi.fn().mockReturnValue(false), + connectToInput: vi.fn(), + connectToOutput: vi.fn(), + connectToSubgraphInput: vi.fn(), + connectToRerouteOutput: vi.fn(), + connectToSubgraphOutput: vi.fn(), + connectToRerouteInput: vi.fn() } + return partial as RenderLinkItem } -const mockNode = {} as LGraphNode -const mockInput = {} as INodeInputSlot +const mockNode = createMockLGraphNode() +const mockInput = createMockNodeInputSlot() describe.skip('LinkConnector', () => { let connector: LinkConnector @@ -37,8 +60,7 @@ describe.skip('LinkConnector', () => { test('should return true if at least one render link can connect', () => { const link1 = mockRenderLinkImpl(false) const link2 = mockRenderLinkImpl(true) - // Cast to any to satisfy the push requirement, as we only need the canConnectToInput method - connector.renderLinks.push(link1 as any, link2 as any) + connector.renderLinks.push(link1, link2) expect(connector.isInputValidDrop(mockNode, mockInput)).toBe(true) expect(link1.canConnectToInput).toHaveBeenCalledWith(mockNode, mockInput) expect(link2.canConnectToInput).toHaveBeenCalledWith(mockNode, mockInput) @@ -47,7 +69,7 @@ describe.skip('LinkConnector', () => { test('should return false if no render links can connect', () => { const link1 = mockRenderLinkImpl(false) const link2 = mockRenderLinkImpl(false) - connector.renderLinks.push(link1 as any, link2 as any) + connector.renderLinks.push(link1, link2) expect(connector.isInputValidDrop(mockNode, mockInput)).toBe(false) expect(link1.canConnectToInput).toHaveBeenCalledWith(mockNode, mockInput) expect(link2.canConnectToInput).toHaveBeenCalledWith(mockNode, mockInput) @@ -57,7 +79,7 @@ describe.skip('LinkConnector', () => { const link1 = mockRenderLinkImpl(false) const link2 = mockRenderLinkImpl(true) // This one can connect const link3 = mockRenderLinkImpl(false) - connector.renderLinks.push(link1 as any, link2 as any, link3 as any) + connector.renderLinks.push(link1, link2, link3) expect(connector.isInputValidDrop(mockNode, mockInput)).toBe(true) @@ -88,7 +110,10 @@ describe.skip('LinkConnector', () => { test('should call the listener when the event is dispatched before reset', () => { const listener = vi.fn() - const eventData = { renderLinks: [], event: {} as any } // Mock event data + const eventData = { + renderLinks: [], + event: createMockCanvasPointerEvent(0, 0) + } connector.listenUntilReset('before-drop-links', listener) connector.events.dispatch('before-drop-links', eventData) @@ -120,7 +145,10 @@ describe.skip('LinkConnector', () => { test('should not call the listener after reset is dispatched', () => { const listener = vi.fn() - const eventData = { renderLinks: [], event: {} as any } + const eventData = { + renderLinks: [], + event: createMockCanvasPointerEvent(0, 0) + } connector.listenUntilReset('before-drop-links', listener) // Dispatch reset first diff --git a/tests-ui/tests/litegraph/canvas/LinkConnectorSubgraphInputValidation.test.ts b/src/lib/litegraph/src/canvas/LinkConnectorSubgraphInputValidation.test.ts similarity index 78% rename from tests-ui/tests/litegraph/canvas/LinkConnectorSubgraphInputValidation.test.ts rename to src/lib/litegraph/src/canvas/LinkConnectorSubgraphInputValidation.test.ts index 957076526..8c2922005 100644 --- a/tests-ui/tests/litegraph/canvas/LinkConnectorSubgraphInputValidation.test.ts +++ b/src/lib/litegraph/src/canvas/LinkConnectorSubgraphInputValidation.test.ts @@ -1,15 +1,30 @@ // TODO: Fix these tests after migration import { beforeEach, describe, expect, it, vi } from 'vitest' -import { LinkConnector } from '@/lib/litegraph/src/litegraph' -import { MovingOutputLink } from '@/lib/litegraph/src/litegraph' -import { ToOutputRenderLink } from '@/lib/litegraph/src/litegraph' -import { LGraphNode, LLink } from '@/lib/litegraph/src/litegraph' -import type { NodeInputSlot } from '@/lib/litegraph/src/litegraph' +import { + LinkConnector, + MovingOutputLink, + ToOutputRenderLink, + LGraphNode, + LLink +} from '@/lib/litegraph/src/litegraph' +import { ToInputFromIoNodeLink } from '@/lib/litegraph/src/canvas/ToInputFromIoNodeLink' +import type { + CanvasPointerEvent, + NodeInputSlot +} from '@/lib/litegraph/src/litegraph' +import { LinkDirection } from '@/lib/litegraph/src/types/globalEnums' -import { createTestSubgraph } from '../subgraph/fixtures/subgraphHelpers' +import { createTestSubgraph } from '../subgraph/__fixtures__/subgraphHelpers' +import { + createMockCanvasPointerEvent, + createMockNodeInputSlot +} from '@/utils/__tests__/litegraphTestUtils' -describe.skip('LinkConnector SubgraphInput connection validation', () => { +type MockPointerEvent = CanvasPointerEvent +type MockRenderLink = ToOutputRenderLink + +describe('LinkConnector SubgraphInput connection validation', () => { let connector: LinkConnector const mockSetConnectingLinks = vi.fn() @@ -17,8 +32,50 @@ describe.skip('LinkConnector SubgraphInput connection validation', () => { connector = new LinkConnector(mockSetConnectingLinks) vi.clearAllMocks() }) + describe('Link disconnection validation', () => { + it('should properly cleanup a moved input link', () => { + const subgraph = createTestSubgraph({ + inputs: [{ name: 'number_input', type: 'number' }] + }) - describe.skip('MovingOutputLink validation', () => { + const fromTargetNode = new LGraphNode('TargetNode') + fromTargetNode.addInput('number_in', 'number') + subgraph.add(fromTargetNode) + + const toTargetNode = new LGraphNode('TargetNode') + toTargetNode.addInput('number_in', 'number') + subgraph.add(toTargetNode) + + const startLink = subgraph.inputNode.slots[0].connect( + fromTargetNode.inputs[0], + fromTargetNode + ) + + fromTargetNode.onConnectionsChange = vi.fn() + toTargetNode.onConnectionsChange = vi.fn() + + const renderLink = new ToInputFromIoNodeLink( + subgraph, + subgraph.inputNode, + subgraph.inputNode.slots[0], + undefined, + LinkDirection.CENTER, + startLink + ) + renderLink.connectToInput( + toTargetNode, + toTargetNode.inputs[0], + connector.events + ) + + expect(fromTargetNode.inputs[0].link).toBeNull() + expect(toTargetNode.inputs[0].link).not.toBeNull() + expect(toTargetNode.onConnectionsChange).toHaveBeenCalledTimes(1) + expect(fromTargetNode.onConnectionsChange).toHaveBeenCalledTimes(1) + }) + }) + + describe('MovingOutputLink validation', () => { it('should implement canConnectToSubgraphInput method', () => { const subgraph = createTestSubgraph({ inputs: [{ name: 'number_input', type: 'number' }] @@ -113,7 +170,7 @@ describe.skip('LinkConnector SubgraphInput connection validation', () => { }) }) - describe.skip('ToOutputRenderLink validation', () => { + describe('ToOutputRenderLink validation', () => { it('should implement canConnectToSubgraphInput method', () => { // Create a minimal valid setup const subgraph = createTestSubgraph() @@ -130,7 +187,7 @@ describe.skip('LinkConnector SubgraphInput connection validation', () => { }) }) - describe.skip('dropOnIoNode validation', () => { + describe('dropOnIoNode validation', () => { it('should prevent invalid connections when dropping on SubgraphInputNode', () => { const subgraph = createTestSubgraph({ inputs: [{ name: 'number_input', type: 'number' }] @@ -159,10 +216,7 @@ describe.skip('LinkConnector SubgraphInput connection validation', () => { connector.state.connectingTo = 'output' // Create mock event - const mockEvent = { - canvasX: 100, - canvasY: 100 - } as any + const mockEvent: MockPointerEvent = createMockCanvasPointerEvent(100, 100) // Mock the getSlotInPosition to return the subgraph input const mockGetSlotInPosition = vi.fn().mockReturnValue(subgraph.inputs[0]) @@ -209,10 +263,7 @@ describe.skip('LinkConnector SubgraphInput connection validation', () => { connector.state.connectingTo = 'output' // Create mock event - const mockEvent = { - canvasX: 100, - canvasY: 100 - } as any + const mockEvent: MockPointerEvent = createMockCanvasPointerEvent(100, 100) // Mock the getSlotInPosition to return the subgraph input const mockGetSlotInPosition = vi.fn().mockReturnValue(subgraph.inputs[0]) @@ -232,7 +283,7 @@ describe.skip('LinkConnector SubgraphInput connection validation', () => { }) }) - describe.skip('isSubgraphInputValidDrop', () => { + describe('isSubgraphInputValidDrop', () => { it('should check if render links can connect to SubgraphInput', () => { const subgraph = createTestSubgraph({ inputs: [{ name: 'number_input', type: 'number' }] @@ -295,12 +346,12 @@ describe.skip('LinkConnector SubgraphInput connection validation', () => { }) // Create a mock render link without the method - const mockLink = { - fromSlot: { type: 'number' } + const mockLink: Partial = { + fromSlot: createMockNodeInputSlot({ type: 'number' }) // No canConnectToSubgraphInput method - } as any + } - connector.renderLinks.push(mockLink) + connector.renderLinks.push(mockLink as MockRenderLink) const subgraphInput = subgraph.inputs[0] diff --git a/src/lib/litegraph/src/canvas/ToInputFromIoNodeLink.ts b/src/lib/litegraph/src/canvas/ToInputFromIoNodeLink.ts index e98c59d87..073c777f1 100644 --- a/src/lib/litegraph/src/canvas/ToInputFromIoNodeLink.ts +++ b/src/lib/litegraph/src/canvas/ToInputFromIoNodeLink.ts @@ -63,6 +63,9 @@ export class ToInputFromIoNodeLink implements RenderLink { if (existingLink) { // Moving an existing link + const { input, inputNode } = existingLink.resolve(this.network) + if (inputNode && input) + this.node._disconnectNodeInput(inputNode, input, existingLink) events.dispatch('input-moved', this) } else { // Creating a new link diff --git a/src/lib/litegraph/src/canvas/ToOutputRenderLink.test.ts b/src/lib/litegraph/src/canvas/ToOutputRenderLink.test.ts new file mode 100644 index 000000000..7c9706ee6 --- /dev/null +++ b/src/lib/litegraph/src/canvas/ToOutputRenderLink.test.ts @@ -0,0 +1,110 @@ +import { describe, expect, it, vi } from 'vitest' + +import { + LinkDirection, + ToOutputRenderLink +} from '@/lib/litegraph/src/litegraph' +import type { CustomEventTarget } from '@/lib/litegraph/src/infrastructure/CustomEventTarget' +import type { LinkConnectorEventMap } from '@/lib/litegraph/src/infrastructure/LinkConnectorEventMap' +import { + createMockLGraphNode, + createMockLinkNetwork, + createMockNodeInputSlot, + createMockNodeOutputSlot +} from '@/utils/__tests__/litegraphTestUtils' + +describe('ToOutputRenderLink', () => { + describe('connectToOutput', () => { + it('should return early if inputNode is null', () => { + // Setup + const mockNetwork = createMockLinkNetwork() + const mockFromSlot = createMockNodeInputSlot() + const mockNode = createMockLGraphNode({ + inputs: [mockFromSlot], + getInputPos: vi.fn().mockReturnValue([0, 0]) + }) + + const renderLink = new ToOutputRenderLink( + mockNetwork, + mockNode, + mockFromSlot, + undefined, + LinkDirection.CENTER + ) + + // Override the node property to simulate null case + Object.defineProperty(renderLink, 'node', { + value: null + }) + + const mockTargetNode = createMockLGraphNode({ + connectSlots: vi.fn() + }) + const mockEvents: Partial> = { + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + dispatch: vi.fn() + } + + // Act + renderLink.connectToOutput( + mockTargetNode, + createMockNodeOutputSlot(), + mockEvents as CustomEventTarget + ) + + // Assert + expect(mockTargetNode.connectSlots).not.toHaveBeenCalled() + expect(mockEvents.dispatch).not.toHaveBeenCalled() + }) + + it('should create connection and dispatch event when inputNode exists', () => { + // Setup + const mockNetwork = createMockLinkNetwork() + const mockFromSlot = createMockNodeInputSlot() + const mockNode = createMockLGraphNode({ + inputs: [mockFromSlot], + getInputPos: vi.fn().mockReturnValue([0, 0]) + }) + + const renderLink = new ToOutputRenderLink( + mockNetwork, + mockNode, + mockFromSlot, + undefined, + LinkDirection.CENTER + ) + + const mockNewLink = { id: 'new-link' } + const mockTargetNode = createMockLGraphNode({ + connectSlots: vi.fn().mockReturnValue(mockNewLink) + }) + const mockEvents: Partial> = { + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + dispatch: vi.fn() + } + + // Act + renderLink.connectToOutput( + mockTargetNode, + createMockNodeOutputSlot(), + mockEvents as CustomEventTarget + ) + + // Assert + expect(mockTargetNode.connectSlots).toHaveBeenCalledWith( + expect.anything(), + mockNode, + mockFromSlot, + undefined + ) + expect(mockEvents.dispatch).toHaveBeenCalledWith( + 'link-created', + mockNewLink + ) + }) + }) +}) diff --git a/tests-ui/tests/litegraph/core/contextMenuCompat.test.ts b/src/lib/litegraph/src/contextMenuCompat.test.ts similarity index 70% rename from tests-ui/tests/litegraph/core/contextMenuCompat.test.ts rename to src/lib/litegraph/src/contextMenuCompat.test.ts index 623082c68..246551dc9 100644 --- a/tests-ui/tests/litegraph/core/contextMenuCompat.test.ts +++ b/src/lib/litegraph/src/contextMenuCompat.test.ts @@ -1,7 +1,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { legacyMenuCompat } from '@/lib/litegraph/src/contextMenuCompat' +import type { IContextMenuValue } from '@/lib/litegraph/src/litegraph' import { LGraphCanvas } from '@/lib/litegraph/src/litegraph' +import { createMockCanvas } from '@/utils/__tests__/litegraphTestUtils' describe('contextMenuCompat', () => { let originalGetCanvasMenuOptions: typeof LGraphCanvas.prototype.getCanvasMenuOptions @@ -12,11 +14,11 @@ describe('contextMenuCompat', () => { originalGetCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions // Create mock canvas - mockCanvas = { + mockCanvas = createMockCanvas({ constructor: { prototype: LGraphCanvas.prototype - } - } as unknown as LGraphCanvas + } as typeof LGraphCanvas + } as Partial) // Clear console warnings vi.spyOn(console, 'warn').mockImplementation(() => {}) @@ -53,11 +55,12 @@ describe('contextMenuCompat', () => { // Simulate extension monkey-patching const original = LGraphCanvas.prototype.getCanvasMenuOptions - LGraphCanvas.prototype.getCanvasMenuOptions = function (...args: any[]) { - const items = (original as any).apply(this, args) - items.push({ content: 'Custom Item', callback: () => {} }) - return items - } + LGraphCanvas.prototype.getCanvasMenuOptions = + function (): (IContextMenuValue | null)[] { + const items = original.call(this) + items.push({ content: 'Custom Item', callback: () => {} }) + return items + } // Should have logged a warning with extension name expect(warnSpy).toHaveBeenCalledWith( @@ -82,8 +85,10 @@ describe('contextMenuCompat', () => { legacyMenuCompat.install(LGraphCanvas.prototype, methodName) legacyMenuCompat.setCurrentExtension('test.extension') - const patchFunction = function (this: LGraphCanvas, ...args: any[]) { - const items = (originalGetCanvasMenuOptions as any).apply(this, args) + const patchFunction = function ( + this: LGraphCanvas + ): (IContextMenuValue | null)[] { + const items = originalGetCanvasMenuOptions.call(this) items.push({ content: 'Custom', callback: () => {} }) return items } @@ -98,13 +103,15 @@ describe('contextMenuCompat', () => { }) describe('extractLegacyItems', () => { + // Cache base items to ensure reference equality for set-based diffing + const baseItem1 = { content: 'Item 1', callback: () => {} } + const baseItem2 = { content: 'Item 2', callback: () => {} } + beforeEach(() => { - // Setup a mock original method + // Setup a mock original method that returns cached items + // This ensures reference equality when set-based diffing compares items LGraphCanvas.prototype.getCanvasMenuOptions = function () { - return [ - { content: 'Item 1', callback: () => {} }, - { content: 'Item 2', callback: () => {} } - ] + return [baseItem1, baseItem2] } // Install compatibility layer @@ -114,12 +121,13 @@ describe('contextMenuCompat', () => { it('should extract items added by monkey patches', () => { // Monkey-patch to add items const original = LGraphCanvas.prototype.getCanvasMenuOptions - LGraphCanvas.prototype.getCanvasMenuOptions = function (...args: any[]) { - const items = (original as any).apply(this, args) - items.push({ content: 'Custom Item 1', callback: () => {} }) - items.push({ content: 'Custom Item 2', callback: () => {} }) - return items - } + LGraphCanvas.prototype.getCanvasMenuOptions = + function (): (IContextMenuValue | null)[] { + const items = original.apply(this) + items.push({ content: 'Custom Item 1', callback: () => {} }) + items.push({ content: 'Custom Item 2', callback: () => {} }) + return items + } // Extract legacy items const legacyItems = legacyMenuCompat.extractLegacyItems( @@ -142,8 +150,11 @@ describe('contextMenuCompat', () => { expect(legacyItems).toHaveLength(0) }) - it('should return empty array when patched method returns same count', () => { - // Monkey-patch that replaces items but keeps same count + it('should detect replaced items as additions and warn about removed items', () => { + const warnSpy = vi.spyOn(console, 'warn') + + // Monkey-patch that replaces items with different ones (same count) + // With set-based diffing, these are detected as new items since they're different references LGraphCanvas.prototype.getCanvasMenuOptions = function () { return [ { content: 'Replaced 1', callback: () => {} }, @@ -156,7 +167,13 @@ describe('contextMenuCompat', () => { mockCanvas ) - expect(legacyItems).toHaveLength(0) + // Set-based diffing detects the replaced items as additions + expect(legacyItems).toHaveLength(2) + expect(legacyItems[0]).toMatchObject({ content: 'Replaced 1' }) + expect(legacyItems[1]).toMatchObject({ content: 'Replaced 2' }) + + // Should warn about removed original items + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('removed')) }) it('should handle errors gracefully', () => { @@ -181,29 +198,36 @@ describe('contextMenuCompat', () => { }) describe('integration', () => { + // Cache base items to ensure reference equality for set-based diffing + const integrationBaseItem = { content: 'Base Item', callback: () => {} } + const integrationBaseItem1 = { content: 'Base Item 1', callback: () => {} } + const integrationBaseItem2 = { content: 'Base Item 2', callback: () => {} } + it('should work with multiple extensions patching', () => { - // Setup base method + // Setup base method with cached item LGraphCanvas.prototype.getCanvasMenuOptions = function () { - return [{ content: 'Base Item', callback: () => {} }] + return [integrationBaseItem] } legacyMenuCompat.install(LGraphCanvas.prototype, 'getCanvasMenuOptions') // First extension patches const original1 = LGraphCanvas.prototype.getCanvasMenuOptions - LGraphCanvas.prototype.getCanvasMenuOptions = function (...args: any[]) { - const items = (original1 as any).apply(this, args) - items.push({ content: 'Extension 1 Item', callback: () => {} }) - return items - } + LGraphCanvas.prototype.getCanvasMenuOptions = + function (): (IContextMenuValue | null)[] { + const items = original1.apply(this) + items.push({ content: 'Extension 1 Item', callback: () => {} }) + return items + } // Second extension patches const original2 = LGraphCanvas.prototype.getCanvasMenuOptions - LGraphCanvas.prototype.getCanvasMenuOptions = function (...args: any[]) { - const items = (original2 as any).apply(this, args) - items.push({ content: 'Extension 2 Item', callback: () => {} }) - return items - } + LGraphCanvas.prototype.getCanvasMenuOptions = + function (): (IContextMenuValue | null)[] { + const items = original2.apply(this) + items.push({ content: 'Extension 2 Item', callback: () => {} }) + return items + } // Extract legacy items const legacyItems = legacyMenuCompat.extractLegacyItems( @@ -218,24 +242,22 @@ describe('contextMenuCompat', () => { }) it('should extract legacy items only once even when called multiple times', () => { - // Setup base method + // Setup base method with cached items LGraphCanvas.prototype.getCanvasMenuOptions = function () { - return [ - { content: 'Base Item 1', callback: () => {} }, - { content: 'Base Item 2', callback: () => {} } - ] + return [integrationBaseItem1, integrationBaseItem2] } legacyMenuCompat.install(LGraphCanvas.prototype, 'getCanvasMenuOptions') // Simulate legacy extension monkey-patching the prototype const original = LGraphCanvas.prototype.getCanvasMenuOptions - LGraphCanvas.prototype.getCanvasMenuOptions = function (...args: any[]) { - const items = (original as any).apply(this, args) - items.push({ content: 'Legacy Item 1', callback: () => {} }) - items.push({ content: 'Legacy Item 2', callback: () => {} }) - return items - } + LGraphCanvas.prototype.getCanvasMenuOptions = + function (): (IContextMenuValue | null)[] { + const items = original.apply(this) + items.push({ content: 'Legacy Item 1', callback: () => {} }) + items.push({ content: 'Legacy Item 2', callback: () => {} }) + return items + } // Extract legacy items multiple times (simulating repeated menu opens) const legacyItems1 = legacyMenuCompat.extractLegacyItems( @@ -268,17 +290,19 @@ describe('contextMenuCompat', () => { }) it('should not extract items from registered wrapper methods', () => { - // Setup base method + // Setup base method with cached item LGraphCanvas.prototype.getCanvasMenuOptions = function () { - return [{ content: 'Base Item', callback: () => {} }] + return [integrationBaseItem] } legacyMenuCompat.install(LGraphCanvas.prototype, 'getCanvasMenuOptions') // Create a wrapper that adds new API items (simulating useContextMenuTranslation) const originalMethod = LGraphCanvas.prototype.getCanvasMenuOptions - const wrapperMethod = function (this: LGraphCanvas) { - const items = (originalMethod as any).apply(this, []) + const wrapperMethod = function ( + this: LGraphCanvas + ): (IContextMenuValue | null)[] { + const items = originalMethod.apply(this) // Add new API items items.push({ content: 'New API Item 1', callback: () => {} }) items.push({ content: 'New API Item 2', callback: () => {} }) @@ -306,16 +330,16 @@ describe('contextMenuCompat', () => { }) it('should extract legacy items even when a wrapper is registered but not active', () => { - // Setup base method + // Setup base method with cached item LGraphCanvas.prototype.getCanvasMenuOptions = function () { - return [{ content: 'Base Item', callback: () => {} }] + return [integrationBaseItem] } legacyMenuCompat.install(LGraphCanvas.prototype, 'getCanvasMenuOptions') // Register a wrapper (but don't set it as the current method) const originalMethod = LGraphCanvas.prototype.getCanvasMenuOptions - const wrapperMethod = function () { + const wrapperMethod = function (): (IContextMenuValue | null)[] { return [{ content: 'Wrapper Item', callback: () => {} }] } legacyMenuCompat.registerWrapper( @@ -327,11 +351,12 @@ describe('contextMenuCompat', () => { // Monkey-patch with a different function (legacy extension) const original = LGraphCanvas.prototype.getCanvasMenuOptions - LGraphCanvas.prototype.getCanvasMenuOptions = function (...args: any[]) { - const items = (original as any).apply(this, args) - items.push({ content: 'Legacy Item', callback: () => {} }) - return items - } + LGraphCanvas.prototype.getCanvasMenuOptions = + function (): (IContextMenuValue | null)[] { + const items = original.apply(this) + items.push({ content: 'Legacy Item', callback: () => {} }) + return items + } // Extract legacy items - should return the legacy item because current method is NOT the wrapper const legacyItems = legacyMenuCompat.extractLegacyItems( diff --git a/src/lib/litegraph/src/contextMenuCompat.ts b/src/lib/litegraph/src/contextMenuCompat.ts index 5d2cd09ad..c1f039cfa 100644 --- a/src/lib/litegraph/src/contextMenuCompat.ts +++ b/src/lib/litegraph/src/contextMenuCompat.ts @@ -7,7 +7,9 @@ import type { IContextMenuValue } from './interfaces' */ const ENABLE_LEGACY_SUPPORT = true -type ContextMenuValueProvider = (...args: unknown[]) => IContextMenuValue[] +type ContextMenuValueProvider = ( + ...args: unknown[] +) => (IContextMenuValue | null)[] class LegacyMenuCompat { private originalMethods = new Map() @@ -37,16 +39,22 @@ class LegacyMenuCompat { * @param preWrapperFn The method that existed before the wrapper * @param prototype The prototype to verify wrapper installation */ - registerWrapper( - methodName: keyof LGraphCanvas, - wrapperFn: ContextMenuValueProvider, - preWrapperFn: ContextMenuValueProvider, + registerWrapper( + methodName: K, + wrapperFn: LGraphCanvas[K], + preWrapperFn: LGraphCanvas[K], prototype?: LGraphCanvas ) { - this.wrapperMethods.set(methodName, wrapperFn) - this.preWrapperMethods.set(methodName, preWrapperFn) + this.wrapperMethods.set( + methodName as string, + wrapperFn as unknown as ContextMenuValueProvider + ) + this.preWrapperMethods.set( + methodName as string, + preWrapperFn as unknown as ContextMenuValueProvider + ) const isInstalled = prototype && prototype[methodName] === wrapperFn - this.wrapperInstalled.set(methodName, !!isInstalled) + this.wrapperInstalled.set(methodName as string, !!isInstalled) } /** @@ -54,11 +62,17 @@ class LegacyMenuCompat { * @param prototype The prototype to install on * @param methodName The method name to track */ - install(prototype: LGraphCanvas, methodName: keyof LGraphCanvas) { + install( + prototype: LGraphCanvas, + methodName: K + ) { if (!ENABLE_LEGACY_SUPPORT) return const originalMethod = prototype[methodName] - this.originalMethods.set(methodName, originalMethod) + this.originalMethods.set( + methodName as string, + originalMethod as unknown as ContextMenuValueProvider + ) let currentImpl = originalMethod @@ -66,13 +80,14 @@ class LegacyMenuCompat { get() { return currentImpl }, - set: (newImpl: ContextMenuValueProvider) => { - const fnKey = `${methodName}:${newImpl.toString().slice(0, 100)}` + set: (newImpl: LGraphCanvas[K]) => { + if (!newImpl) return + const fnKey = `${methodName as string}:${newImpl.toString().slice(0, 100)}` if (!this.hasWarned.has(fnKey) && this.currentExtension) { this.hasWarned.add(fnKey) console.warn( - `%c[DEPRECATED]%c Monkey-patching ${methodName} is deprecated. (Extension: "${this.currentExtension}")\n` + + `%c[DEPRECATED]%c Monkey-patching ${methodName as string} is deprecated. (Extension: "${this.currentExtension}")\n` + `Please use the new context menu API instead.\n\n` + `See: https://docs.comfy.org/custom-nodes/js/context-menu-migration`, 'color: orange; font-weight: bold', @@ -85,7 +100,15 @@ class LegacyMenuCompat { } /** - * Extract items that were added by legacy monkey patches + * Extract items that were added by legacy monkey patches. + * + * Uses set-based diffing by reference to reliably detect additions regardless + * of item reordering or replacement. Items present in patchedItems but not in + * originalItems (by reference equality) are considered additions. + * + * Note: If a monkey patch removes items (patchedItems has fewer unique items + * than originalItems), a warning is logged but we still return any new items. + * * @param methodName The method name that was monkey-patched * @param context The context to call methods with * @param args Arguments to pass to the methods @@ -95,7 +118,7 @@ class LegacyMenuCompat { methodName: keyof LGraphCanvas, context: LGraphCanvas, ...args: unknown[] - ): IContextMenuValue[] { + ): (IContextMenuValue | null)[] { if (!ENABLE_LEGACY_SUPPORT) return [] if (this.isExtracting) return [] @@ -106,7 +129,7 @@ class LegacyMenuCompat { this.isExtracting = true const originalItems = originalMethod.apply(context, args) as - | IContextMenuValue[] + | (IContextMenuValue | null)[] | undefined if (!originalItems) return [] @@ -127,15 +150,26 @@ class LegacyMenuCompat { const methodToCall = shouldSkipWrapper ? preWrapperMethod : currentMethod const patchedItems = methodToCall.apply(context, args) as - | IContextMenuValue[] + | (IContextMenuValue | null)[] | undefined if (!patchedItems) return [] - if (patchedItems.length > originalItems.length) { - return patchedItems.slice(originalItems.length) as IContextMenuValue[] + // Use set-based diff to detect additions by reference + const originalSet = new Set(originalItems) + const addedItems = patchedItems.filter((item) => !originalSet.has(item)) + + // Warn if items were removed (patched has fewer original items than expected) + const retainedOriginalCount = patchedItems.filter((item) => + originalSet.has(item) + ).length + if (retainedOriginalCount < originalItems.length) { + console.warn( + `[Context Menu Compat] Monkey patch for ${methodName} removed ${originalItems.length - retainedOriginalCount} original menu item(s). ` + + `This may cause unexpected behavior.` + ) } - return [] + return addedItems } catch (e) { console.error('[Context Menu Compat] Failed to extract legacy items:', e) return [] diff --git a/src/lib/litegraph/src/infrastructure/CustomEventTarget.ts b/src/lib/litegraph/src/infrastructure/CustomEventTarget.ts index ced6ad7aa..2c7f93f33 100644 --- a/src/lib/litegraph/src/infrastructure/CustomEventTarget.ts +++ b/src/lib/litegraph/src/infrastructure/CustomEventTarget.ts @@ -81,9 +81,9 @@ export interface CustomEventDispatcher< * ``` */ export class CustomEventTarget< - EventMap extends Record, - Keys extends keyof EventMap & string = keyof EventMap & string - > + EventMap extends Record, + Keys extends keyof EventMap & string = keyof EventMap & string +> extends EventTarget implements ICustomEventTarget { diff --git a/tests-ui/tests/litegraph/infrastructure/Rectangle.resize.test.ts b/src/lib/litegraph/src/infrastructure/Rectangle.resize.test.ts similarity index 100% rename from tests-ui/tests/litegraph/infrastructure/Rectangle.resize.test.ts rename to src/lib/litegraph/src/infrastructure/Rectangle.resize.test.ts diff --git a/tests-ui/tests/litegraph/infrastructure/Rectangle.test.ts b/src/lib/litegraph/src/infrastructure/Rectangle.test.ts similarity index 99% rename from tests-ui/tests/litegraph/infrastructure/Rectangle.test.ts rename to src/lib/litegraph/src/infrastructure/Rectangle.test.ts index b89545845..b4fc56091 100644 --- a/tests-ui/tests/litegraph/infrastructure/Rectangle.test.ts +++ b/src/lib/litegraph/src/infrastructure/Rectangle.test.ts @@ -1,3 +1,4 @@ +// oxlint-disable no-empty-pattern import { test as baseTest, describe, expect, vi } from 'vitest' import { Rectangle } from '@/lib/litegraph/src/litegraph' @@ -6,7 +7,6 @@ import type { Point, Size } from '@/lib/litegraph/src/litegraph' // TODO: If there's a common test context, use it here // For now, we'll define a simple context for Rectangle tests const test = baseTest.extend<{ rect: Rectangle }>({ - // eslint-disable-next-line no-empty-pattern rect: async ({}, use) => { await use(new Rectangle()) } diff --git a/src/lib/litegraph/src/interfaces.ts b/src/lib/litegraph/src/interfaces.ts index 9cd31208f..aad3dff3f 100644 --- a/src/lib/litegraph/src/interfaces.ts +++ b/src/lib/litegraph/src/interfaces.ts @@ -1,5 +1,6 @@ import type { Rectangle } from '@/lib/litegraph/src/infrastructure/Rectangle' import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events' +import type { TWidgetValue } from '@/lib/litegraph/src/types/widgets' import type { ContextMenu } from './ContextMenu' import type { LGraphNode, NodeId } from './LGraphNode' @@ -338,11 +339,13 @@ export interface INodeFlags { */ export interface IWidgetLocator { name: string + type?: string } export interface INodeInputSlot extends INodeSlot { link: LinkId | null widget?: IWidgetLocator + alwaysVisible?: boolean /** * Internal use only; API is not finalised and may change at any time. @@ -379,8 +382,10 @@ interface IContextMenuBase { } /** ContextMenu */ -export interface IContextMenuOptions - extends IContextMenuBase { +export interface IContextMenuOptions< + TValue = unknown, + TExtra = unknown +> extends IContextMenuBase { ignore_item_callbacks?: boolean parentMenu?: ContextMenu event?: MouseEvent @@ -425,13 +430,15 @@ export interface IContextMenuValue< ): void | boolean | Promise } -interface IContextMenuSubmenu - extends IContextMenuOptions { +interface IContextMenuSubmenu< + TValue = unknown +> extends IContextMenuOptions { options: ConstructorParameters>[0] } -export interface ContextMenuDivElement - extends HTMLDivElement { +export interface ContextMenuDivElement< + TValue = unknown +> extends HTMLDivElement { value?: string | IContextMenuValue onclick_callback?: never } @@ -487,3 +494,68 @@ export interface Hoverable extends HasBoundingRect { onPointerEnter?(e?: CanvasPointerEvent): void onPointerLeave?(e?: CanvasPointerEvent): void } + +/** + * Callback for panel widget value changes. + */ +export type PanelWidgetCallback = ( + name: string | undefined, + value: TWidgetValue, + options: PanelWidgetOptions +) => void + +/** + * Options for panel widgets. + */ +export interface PanelWidgetOptions { + label?: string + type?: string + widget?: string + values?: Array | null> + callback?: PanelWidgetCallback +} + +/** + * A button element with optional options property. + */ +export interface PanelButton extends HTMLButtonElement { + options?: unknown +} + +/** + * A widget element with options and value properties. + */ +export interface PanelWidget extends HTMLDivElement { + options?: PanelWidgetOptions + value?: TWidgetValue +} + +/** + * A dialog panel created by LGraphCanvas.createPanel(). + * Extends HTMLDivElement with additional properties and methods for panel management. + */ +export interface Panel extends HTMLDivElement { + header: HTMLElement + title_element: HTMLSpanElement + content: HTMLDivElement + alt_content: HTMLDivElement + footer: HTMLDivElement + node?: LGraphNode + onOpen?: () => void + onClose?: () => void + close(): void + toggleAltContent(force?: boolean): void + toggleFooterVisibility(force?: boolean): void + clear(): void + addHTML(code: string, classname?: string, on_footer?: boolean): HTMLDivElement + addButton(name: string, callback: () => void, options?: unknown): PanelButton + addSeparator(): void + addWidget( + type: string, + name: string, + value: TWidgetValue, + options?: PanelWidgetOptions, + callback?: PanelWidgetCallback + ): PanelWidget + inner_showCodePad?(property: string): void +} diff --git a/tests-ui/tests/litegraph/core/litegraph.test.ts b/src/lib/litegraph/src/litegraph.test.ts similarity index 87% rename from tests-ui/tests/litegraph/core/litegraph.test.ts rename to src/lib/litegraph/src/litegraph.test.ts index cc58100fa..936b82f6e 100644 --- a/tests-ui/tests/litegraph/core/litegraph.test.ts +++ b/src/lib/litegraph/src/litegraph.test.ts @@ -1,10 +1,13 @@ import { clamp } from 'es-toolkit/compat' import { beforeEach, describe, expect, vi } from 'vitest' -import { LiteGraphGlobal } from '@/lib/litegraph/src/litegraph' -import { LGraphCanvas, LiteGraph } from '@/lib/litegraph/src/litegraph' +import { + LiteGraphGlobal, + LGraphCanvas, + LiteGraph +} from '@/lib/litegraph/src/litegraph' -import { test } from './fixtures/testExtensions' +import { test } from './__fixtures__/testExtensions' describe('Litegraph module', () => { test('contains a global export', ({ expect }) => { diff --git a/src/lib/litegraph/src/litegraph.ts b/src/lib/litegraph/src/litegraph.ts index b15109d5c..59d62a84f 100644 --- a/src/lib/litegraph/src/litegraph.ts +++ b/src/lib/litegraph/src/litegraph.ts @@ -62,7 +62,7 @@ export interface LGraphNodeConstructor { size?: Size min_height?: number slot_start_y?: number - widgets_info?: any + widgets_info?: Record collapsable?: boolean color?: string bgcolor?: string @@ -104,6 +104,8 @@ export type { } from './interfaces' export { LGraph, + type GroupNodeConfigEntry, + type GroupNodeWorkflowData, type LGraphTriggerAction, type LGraphTriggerParam } from './LGraph' diff --git a/tests-ui/tests/litegraph/core/measure.test.ts b/src/lib/litegraph/src/measure.test.ts similarity index 100% rename from tests-ui/tests/litegraph/core/measure.test.ts rename to src/lib/litegraph/src/measure.test.ts diff --git a/src/lib/litegraph/src/node/NodeInputSlot.ts b/src/lib/litegraph/src/node/NodeInputSlot.ts index 0e59e51d6..6f8dc352c 100644 --- a/src/lib/litegraph/src/node/NodeInputSlot.ts +++ b/src/lib/litegraph/src/node/NodeInputSlot.ts @@ -17,6 +17,7 @@ import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets' export class NodeInputSlot extends NodeSlot implements INodeInputSlot { link: LinkId | null + alwaysVisible?: boolean get isWidgetInputSlot(): boolean { return !!this.widget diff --git a/tests-ui/tests/litegraph/core/NodeSlot.test.ts b/src/lib/litegraph/src/node/NodeSlot.test.ts similarity index 71% rename from tests-ui/tests/litegraph/core/NodeSlot.test.ts rename to src/lib/litegraph/src/node/NodeSlot.test.ts index 15215cbf0..6971736fc 100644 --- a/tests-ui/tests/litegraph/core/NodeSlot.test.ts +++ b/src/lib/litegraph/src/node/NodeSlot.test.ts @@ -8,16 +8,19 @@ import { inputAsSerialisable, outputAsSerialisable } from '@/lib/litegraph/src/litegraph' +import type { ReadOnlyRect } from '@/lib/litegraph/src/interfaces' + +const boundingRect: ReadOnlyRect = [0, 0, 10, 10] describe('NodeSlot', () => { describe('inputAsSerialisable', () => { it('removes _data from serialized slot', () => { - // @ts-expect-error Missing boundingRect property for test const slot: INodeOutputSlot = { _data: 'test data', name: 'test-id', type: 'STRING', - links: [] + links: [], + boundingRect } // @ts-expect-error Argument type mismatch for test const serialized = outputAsSerialisable(slot) @@ -25,20 +28,14 @@ describe('NodeSlot', () => { }) it('removes pos from widget input slots', () => { + // Minimal slot for serialization test - boundingRect is calculated at runtime, not serialized const widgetInputSlot: INodeInputSlot = { name: 'test-id', pos: [10, 20], type: 'STRING', link: null, - widget: { - name: 'test-widget', - // @ts-expect-error TODO: Fix after merge - type property not in IWidgetLocator - type: 'combo', - value: 'test-value-1', - options: { - values: ['test-value-1', 'test-value-2'] - } - } + widget: { name: 'test-widget', type: 'combo' }, + boundingRect } const serialized = inputAsSerialisable(widgetInputSlot) @@ -46,30 +43,27 @@ describe('NodeSlot', () => { }) it('preserves pos for non-widget input slots', () => { - // @ts-expect-error TODO: Fix after merge - missing boundingRect property for test const normalSlot: INodeInputSlot = { name: 'test-id', type: 'STRING', pos: [10, 20], - link: null + link: null, + boundingRect } const serialized = inputAsSerialisable(normalSlot) expect(serialized).toHaveProperty('pos') }) it('preserves only widget name during serialization', () => { + // Extra widget properties simulate real data that should be stripped during serialization const widgetInputSlot: INodeInputSlot = { name: 'test-id', type: 'STRING', link: null, + boundingRect, widget: { name: 'test-widget', - // @ts-expect-error TODO: Fix after merge - type property not in IWidgetLocator - type: 'combo', - value: 'test-value-1', - options: { - values: ['test-value-1', 'test-value-2'] - } + type: 'combo' } } diff --git a/src/lib/litegraph/src/node/NodeSlot.ts b/src/lib/litegraph/src/node/NodeSlot.ts index e7a2de382..9fac7816f 100644 --- a/src/lib/litegraph/src/node/NodeSlot.ts +++ b/src/lib/litegraph/src/node/NodeSlot.ts @@ -30,6 +30,8 @@ export interface IDrawOptions { highlight?: boolean } +const ROTATION_OFFSET = -Math.PI + /** Shared base class for {@link LGraphNode} input and output slots. */ export abstract class NodeSlot extends SlotBase implements INodeSlot { pos?: Point @@ -130,6 +132,7 @@ export abstract class NodeSlot extends SlotBase implements INodeSlot { slot_type === SlotType.Array ? SlotShape.Grid : this.shape ) as SlotShape + ctx.save() ctx.beginPath() let doFill = true @@ -163,16 +166,52 @@ export abstract class NodeSlot extends SlotBase implements INodeSlot { if (lowQuality) { ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8) } else { - let radius: number if (slot_shape === SlotShape.HollowCircle) { + const path = new Path2D() + path.arc(pos[0], pos[1], 10, 0, Math.PI * 2) + path.arc(pos[0], pos[1], highlight ? 2.5 : 1.5, 0, Math.PI * 2) + ctx.clip(path, 'evenodd') + } + const radius = highlight ? 5 : 4 + const typesSet = new Set( + `${this.type}` + .split(',') + .map( + this.isConnected + ? (type) => colorContext.getConnectedColor(type) + : (type) => colorContext.getDisconnectedColor(type) + ) + ) + const types = [...typesSet].slice(0, 3) + if (types.length > 1) { doFill = false - doStroke = true - ctx.lineWidth = 3 - ctx.strokeStyle = ctx.fillStyle - radius = highlight ? 4 : 3 - } else { - // Normal circle - radius = highlight ? 5 : 4 + const arcLen = (Math.PI * 2) / types.length + types.forEach((type, idx) => { + ctx.moveTo(pos[0], pos[1]) + ctx.fillStyle = type + ctx.arc( + pos[0], + pos[1], + radius, + arcLen * idx + ROTATION_OFFSET, + Math.PI * 2 + ROTATION_OFFSET + ) + ctx.fill() + ctx.beginPath() + }) + //add stroke dividers + ctx.save() + ctx.strokeStyle = 'black' + ctx.lineWidth = 0.5 + types.forEach((_, idx) => { + ctx.moveTo(pos[0], pos[1]) + const xOffset = Math.cos(arcLen * idx + ROTATION_OFFSET) * radius + const yOffset = Math.sin(arcLen * idx + ROTATION_OFFSET) * radius + ctx.lineTo(pos[0] + xOffset, pos[1] + yOffset) + }) + ctx.stroke() + ctx.restore() + ctx.beginPath() } ctx.arc(pos[0], pos[1], radius, 0, Math.PI * 2) } @@ -180,6 +219,7 @@ export abstract class NodeSlot extends SlotBase implements INodeSlot { if (doFill) ctx.fill() if (!lowQuality && doStroke) ctx.stroke() + ctx.restore() // render slot label const hideLabel = lowQuality || this.isWidgetInputSlot diff --git a/tests-ui/tests/litegraph/subgraph/ExecutableNodeDTO.test.ts b/src/lib/litegraph/src/subgraph/ExecutableNodeDTO.test.ts similarity index 97% rename from tests-ui/tests/litegraph/subgraph/ExecutableNodeDTO.test.ts rename to src/lib/litegraph/src/subgraph/ExecutableNodeDTO.test.ts index 4418d9422..c475bb3aa 100644 --- a/tests-ui/tests/litegraph/subgraph/ExecutableNodeDTO.test.ts +++ b/src/lib/litegraph/src/subgraph/ExecutableNodeDTO.test.ts @@ -1,14 +1,17 @@ // TODO: Fix these tests after migration import { describe, expect, it, vi } from 'vitest' -import { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph' -import { ExecutableNodeDTO } from '@/lib/litegraph/src/litegraph' +import { + LGraph, + LGraphNode, + ExecutableNodeDTO +} from '@/lib/litegraph/src/litegraph' import { createNestedSubgraphs, createTestSubgraph, createTestSubgraphNode -} from './fixtures/subgraphHelpers' +} from './__fixtures__/subgraphHelpers' describe.skip('ExecutableNodeDTO Creation', () => { it('should create DTO from regular node', () => { @@ -83,10 +86,8 @@ describe.skip('ExecutableNodeDTO Creation', () => { expect(dto.applyToGraph).toBeDefined() - // Test that wrapper calls original method const args = ['arg1', 'arg2'] - // @ts-expect-error TODO: Fix after merge - applyToGraph expects different arguments - dto.applyToGraph!(args[0], args[1]) + ;(dto.applyToGraph as (...args: unknown[]) => void)(args[0], args[1]) expect(mockApplyToGraph).toHaveBeenCalledWith(args[0], args[1]) }) diff --git a/tests-ui/tests/litegraph/subgraph/Subgraph.test.ts b/src/lib/litegraph/src/subgraph/Subgraph.test.ts similarity index 92% rename from tests-ui/tests/litegraph/subgraph/Subgraph.test.ts rename to src/lib/litegraph/src/subgraph/Subgraph.test.ts index 773bcca00..ecd939654 100644 --- a/tests-ui/tests/litegraph/subgraph/Subgraph.test.ts +++ b/src/lib/litegraph/src/subgraph/Subgraph.test.ts @@ -8,16 +8,19 @@ */ import { describe, expect, it } from 'vitest' -import { RecursionError } from '@/lib/litegraph/src/litegraph' -import { LGraph, Subgraph } from '@/lib/litegraph/src/litegraph' -import { createUuidv4 } from '@/lib/litegraph/src/litegraph' +import { + createUuidv4, + RecursionError, + LGraph, + Subgraph +} from '@/lib/litegraph/src/litegraph' -import { subgraphTest } from './fixtures/subgraphFixtures' +import { subgraphTest } from './__fixtures__/subgraphFixtures' import { assertSubgraphStructure, createTestSubgraph, createTestSubgraphData -} from './fixtures/subgraphHelpers' +} from './__fixtures__/subgraphHelpers' describe.skip('Subgraph Construction', () => { it('should create a subgraph with minimal data', () => { @@ -182,14 +185,10 @@ describe.skip('Subgraph Serialization', () => { expect(serialized.inputs).toHaveLength(1) expect(serialized.outputs).toHaveLength(1) - // @ts-expect-error TODO: Fix after merge - serialized.inputs possibly undefined - expect(serialized.inputs[0].name).toBe('input') - // @ts-expect-error TODO: Fix after merge - serialized.inputs possibly undefined - expect(serialized.inputs[0].type).toBe('number') - // @ts-expect-error TODO: Fix after merge - serialized.outputs possibly undefined - expect(serialized.outputs[0].name).toBe('output') - // @ts-expect-error TODO: Fix after merge - serialized.outputs possibly undefined - expect(serialized.outputs[0].type).toBe('number') + expect(serialized.inputs![0].name).toBe('input') + expect(serialized.inputs![0].type).toBe('number') + expect(serialized.outputs![0].name).toBe('output') + expect(serialized.outputs![0].type).toBe('number') } ) diff --git a/tests-ui/tests/litegraph/subgraph/SubgraphConversion.test.ts b/src/lib/litegraph/src/subgraph/SubgraphConversion.test.ts similarity index 98% rename from tests-ui/tests/litegraph/subgraph/SubgraphConversion.test.ts rename to src/lib/litegraph/src/subgraph/SubgraphConversion.test.ts index f978710ae..4e5cbae35 100644 --- a/tests-ui/tests/litegraph/subgraph/SubgraphConversion.test.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphConversion.test.ts @@ -1,18 +1,17 @@ // TODO: Fix these tests after migration import { assert, describe, expect, it } from 'vitest' -import type { LGraph } from '@/lib/litegraph/src/litegraph' import { - type ISlotType, LGraphGroup, LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph' +import type { LGraph, ISlotType } from '@/lib/litegraph/src/litegraph' import { createTestSubgraph, createTestSubgraphNode -} from './fixtures/subgraphHelpers' +} from './__fixtures__/subgraphHelpers' function createNode( graph: LGraph, diff --git a/tests-ui/tests/litegraph/subgraph/SubgraphEdgeCases.test.ts b/src/lib/litegraph/src/subgraph/SubgraphEdgeCases.test.ts similarity index 98% rename from tests-ui/tests/litegraph/subgraph/SubgraphEdgeCases.test.ts rename to src/lib/litegraph/src/subgraph/SubgraphEdgeCases.test.ts index 8734575b1..b161467e2 100644 --- a/tests-ui/tests/litegraph/subgraph/SubgraphEdgeCases.test.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphEdgeCases.test.ts @@ -13,7 +13,7 @@ import { createNestedSubgraphs, createTestSubgraph, createTestSubgraphNode -} from './fixtures/subgraphHelpers' +} from './__fixtures__/subgraphHelpers' describe.skip('SubgraphEdgeCases - Recursion Detection', () => { it('should handle circular subgraph references without crashing', () => { @@ -350,8 +350,7 @@ describe.skip('SubgraphEdgeCases - Performance and Scale', () => { const subgraphNode = createTestSubgraphNode(subgraph) // Simulate concurrent operations - // @ts-expect-error TODO: Fix after merge - operations implicitly has any[] type - const operations = [] + const operations: Array<() => void> = [] for (let i = 0; i < 20; i++) { operations.push( () => { @@ -371,7 +370,6 @@ describe.skip('SubgraphEdgeCases - Performance and Scale', () => { // Execute all operations - should not crash expect(() => { - // @ts-expect-error TODO: Fix after merge - operations implicitly has any[] type for (const op of operations) op() }).not.toThrow() }) diff --git a/tests-ui/tests/litegraph/subgraph/SubgraphEvents.test.ts b/src/lib/litegraph/src/subgraph/SubgraphEvents.test.ts similarity index 96% rename from tests-ui/tests/litegraph/subgraph/SubgraphEvents.test.ts rename to src/lib/litegraph/src/subgraph/SubgraphEvents.test.ts index abff4fa7a..ce4fe45c8 100644 --- a/tests-ui/tests/litegraph/subgraph/SubgraphEvents.test.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphEvents.test.ts @@ -1,8 +1,8 @@ // TODO: Fix these tests after migration import { describe, expect, vi } from 'vitest' -import { subgraphTest } from './fixtures/subgraphFixtures' -import { verifyEventSequence } from './fixtures/subgraphHelpers' +import { subgraphTest } from './__fixtures__/subgraphFixtures' +import { verifyEventSequence } from './__fixtures__/subgraphHelpers' describe.skip('SubgraphEvents - Event Payload Verification', () => { subgraphTest( @@ -22,7 +22,6 @@ describe.skip('SubgraphEvents - Event Payload Verification', () => { }) }) - // @ts-expect-error TODO: Fix after merge - detail is of type unknown expect(addedEvents[0].detail.input).toBe(input) } ) @@ -44,7 +43,6 @@ describe.skip('SubgraphEvents - Event Payload Verification', () => { }) }) - // @ts-expect-error TODO: Fix after merge - detail is of type unknown expect(addedEvents[0].detail.output).toBe(output) } ) @@ -71,7 +69,6 @@ describe.skip('SubgraphEvents - Event Payload Verification', () => { index: 0 }) - // @ts-expect-error TODO: Fix after merge - detail is of type unknown expect(removingEvents[0].detail.input).toBe(input) } ) @@ -98,7 +95,6 @@ describe.skip('SubgraphEvents - Event Payload Verification', () => { index: 0 }) - // @ts-expect-error TODO: Fix after merge - detail is of type unknown expect(removingEvents[0].detail.output).toBe(output) } ) @@ -126,7 +122,6 @@ describe.skip('SubgraphEvents - Event Payload Verification', () => { newName: 'new_name' }) - // @ts-expect-error TODO: Fix after merge - detail is of type unknown expect(renamingEvents[0].detail.input).toBe(input) // Verify the label was updated after the event (renameInput sets label, not name) @@ -160,7 +155,6 @@ describe.skip('SubgraphEvents - Event Payload Verification', () => { newName: 'new_name' }) - // @ts-expect-error TODO: Fix after merge - detail is of type unknown expect(renamingEvents[0].detail.output).toBe(output) // Verify the label was updated after the event diff --git a/tests-ui/tests/litegraph/subgraph/SubgraphIO.test.ts b/src/lib/litegraph/src/subgraph/SubgraphIO.test.ts similarity index 93% rename from tests-ui/tests/litegraph/subgraph/SubgraphIO.test.ts rename to src/lib/litegraph/src/subgraph/SubgraphIO.test.ts index 25f2ecd10..04c6757b6 100644 --- a/tests-ui/tests/litegraph/subgraph/SubgraphIO.test.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphIO.test.ts @@ -5,11 +5,11 @@ import { LGraphNode } from '@/lib/litegraph/src/litegraph' import { ToInputFromIoNodeLink } from '@/lib/litegraph/src/canvas/ToInputFromIoNodeLink' import { LinkDirection } from '@/lib/litegraph/src//types/globalEnums' -import { subgraphTest } from './fixtures/subgraphFixtures' +import { subgraphTest } from './__fixtures__/subgraphFixtures' import { createTestSubgraph, createTestSubgraphNode -} from './fixtures/subgraphHelpers' +} from './__fixtures__/subgraphHelpers' describe('SubgraphIO - Input Slot Dual-Nature Behavior', () => { subgraphTest( @@ -28,8 +28,7 @@ describe('SubgraphIO - Input Slot Dual-Nature Behavior', () => { }).not.toThrow() expect( - // @ts-expect-error TODO: Fix after merge - link can be null - externalNode.outputs[0].links?.includes(subgraphNode.inputs[0].link) + externalNode.outputs[0].links?.includes(subgraphNode.inputs[0].link!) ).toBe(true) expect(subgraphNode.inputs[0].link).not.toBe(null) } @@ -47,10 +46,8 @@ describe('SubgraphIO - Input Slot Dual-Nature Behavior', () => { expect(simpleSubgraph.inputs.length).toBe(initialInputCount + 1) // The empty slot should be configurable - const emptyInput = simpleSubgraph.inputs.at(-1) - // @ts-expect-error TODO: Fix after merge - emptyInput possibly undefined + const emptyInput = simpleSubgraph.inputs.at(-1)! expect(emptyInput.name).toBe('') - // @ts-expect-error TODO: Fix after merge - emptyInput possibly undefined expect(emptyInput.type).toBe('*') } ) @@ -149,8 +146,7 @@ describe('SubgraphIO - Output Slot Dual-Nature Behavior', () => { }).not.toThrow() expect( - // @ts-expect-error TODO: Fix after merge - link can be null - subgraphNode.outputs[0].links?.includes(externalNode.inputs[0].link) + subgraphNode.outputs[0].links?.includes(externalNode.inputs[0].link!) ).toBe(true) expect(externalNode.inputs[0].link).not.toBe(null) } @@ -168,10 +164,8 @@ describe('SubgraphIO - Output Slot Dual-Nature Behavior', () => { expect(simpleSubgraph.outputs.length).toBe(initialOutputCount + 1) // The empty slot should be configurable - const emptyOutput = simpleSubgraph.outputs.at(-1) - // @ts-expect-error TODO: Fix after merge - emptyOutput possibly undefined + const emptyOutput = simpleSubgraph.outputs.at(-1)! expect(emptyOutput.name).toBe('') - // @ts-expect-error TODO: Fix after merge - emptyOutput possibly undefined expect(emptyOutput.type).toBe('*') } ) @@ -454,15 +448,11 @@ describe('SubgraphIO - Empty Slot Connection', () => { // 3. A link should be established inside the subgraph expect(internalNode.inputs[0].link).not.toBe(null) - const link = subgraph.links.get(internalNode.inputs[0].link!) + const link = subgraph.links.get(internalNode.inputs[0].link!)! expect(link).toBeDefined() - // @ts-expect-error TODO: Fix after merge - link possibly undefined expect(link.target_id).toBe(internalNode.id) - // @ts-expect-error TODO: Fix after merge - link possibly undefined expect(link.target_slot).toBe(0) - // @ts-expect-error TODO: Fix after merge - link possibly undefined expect(link.origin_id).toBe(subgraph.inputNode.id) - // @ts-expect-error TODO: Fix after merge - link possibly undefined expect(link.origin_slot).toBe(1) // Should be the second slot } ) diff --git a/src/lib/litegraph/src/subgraph/SubgraphIONodeBase.ts b/src/lib/litegraph/src/subgraph/SubgraphIONodeBase.ts index f5cf4dd75..a50633848 100644 --- a/src/lib/litegraph/src/subgraph/SubgraphIONodeBase.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphIONodeBase.ts @@ -30,8 +30,8 @@ import type { SubgraphInput } from './SubgraphInput' import type { SubgraphOutput } from './SubgraphOutput' export abstract class SubgraphIONodeBase< - TSlot extends SubgraphInput | SubgraphOutput - > + TSlot extends SubgraphInput | SubgraphOutput +> implements Positionable, Hoverable, Serialisable { static margin = 10 @@ -195,7 +195,7 @@ export abstract class SubgraphIONodeBase< if (!(options.length > 0)) return new LiteGraph.ContextMenu(options, { - event: event as any, + event, title: slot.name || 'Subgraph Output', callback: (item: IContextMenuValue) => { this.#onSlotMenuAction(item, slot, event) @@ -353,7 +353,7 @@ export abstract class SubgraphIONodeBase< editorAlpha?: number ): void { ctx.fillStyle = '#AAA' - ctx.font = '12px Arial' + ctx.font = '12px Inter, sans-serif' ctx.textBaseline = 'middle' for (const slot of this.allSlots) { diff --git a/tests-ui/tests/litegraph/subgraph/SubgraphMemory.test.ts b/src/lib/litegraph/src/subgraph/SubgraphMemory.test.ts similarity index 93% rename from tests-ui/tests/litegraph/subgraph/SubgraphMemory.test.ts rename to src/lib/litegraph/src/subgraph/SubgraphMemory.test.ts index 5e6770d1e..7b936392a 100644 --- a/tests-ui/tests/litegraph/subgraph/SubgraphMemory.test.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphMemory.test.ts @@ -2,12 +2,13 @@ import { describe, expect, it, vi } from 'vitest' import { LGraph } from '@/lib/litegraph/src/litegraph' +import type { IWidget } from '@/lib/litegraph/src/types/widgets' -import { subgraphTest } from './fixtures/subgraphFixtures' +import { subgraphTest } from './__fixtures__/subgraphFixtures' import { createTestSubgraph, createTestSubgraphNode -} from './fixtures/subgraphHelpers' +} from './__fixtures__/subgraphHelpers' describe.skip('SubgraphNode Memory Management', () => { describe.skip('Event Listener Cleanup', () => { @@ -72,10 +73,10 @@ describe.skip('SubgraphNode Memory Management', () => { size: [200, 100], inputs: [], outputs: [], - // @ts-expect-error TODO: Fix after merge - properties not in ExportedSubgraphInstance properties: {}, flags: {}, - mode: 0 + mode: 0, + order: 0 }) } @@ -93,12 +94,13 @@ describe.skip('SubgraphNode Memory Management', () => { }) const subgraphNode = createTestSubgraphNode(subgraph) - // Simulate widget promotion scenario const input = subgraphNode.inputs[0] const mockWidget = { type: 'number', name: 'promoted_widget', value: 123, + options: {}, + y: 0, draw: vi.fn(), mouse: vi.fn(), computeSize: vi.fn(), @@ -107,21 +109,16 @@ describe.skip('SubgraphNode Memory Management', () => { name: 'promoted_widget', value: 123 }) - } + } as Partial as IWidget - // Simulate widget promotion - // @ts-expect-error TODO: Fix after merge - mockWidget type mismatch input._widget = mockWidget input.widget = { name: 'promoted_widget' } - // @ts-expect-error TODO: Fix after merge - mockWidget type mismatch subgraphNode.widgets.push(mockWidget) expect(input._widget).toBe(mockWidget) expect(input.widget).toBeDefined() expect(subgraphNode.widgets).toContain(mockWidget) - // Remove widget (this should clean up references) - // @ts-expect-error TODO: Fix after merge - mockWidget type mismatch subgraphNode.removeWidget(mockWidget) // Widget should be removed from array @@ -146,10 +143,10 @@ describe.skip('SubgraphNode Memory Management', () => { size: [200, 100], inputs: [], outputs: [], - // @ts-expect-error TODO: Fix after merge - properties not in ExportedSubgraphInstance properties: {}, flags: {}, - mode: 0 + mode: 0, + order: 0 }) } @@ -328,17 +325,26 @@ describe.skip('SubgraphMemory - Widget Reference Management', () => { const initialWidgetCount = subgraphNode.widgets?.length || 0 - // Add mock widgets - const widget1 = { type: 'number', value: 1, name: 'widget1' } - const widget2 = { type: 'string', value: 'test', name: 'widget2' } + const widget1 = { + type: 'number', + value: 1, + name: 'widget1', + options: {}, + y: 0 + } as Partial as IWidget + const widget2 = { + type: 'string', + value: 'test', + name: 'widget2', + options: {}, + y: 0 + } as Partial as IWidget if (subgraphNode.widgets) { - // @ts-expect-error TODO: Fix after merge - widget type mismatch subgraphNode.widgets.push(widget1, widget2) expect(subgraphNode.widgets.length).toBe(initialWidgetCount + 2) } - // Remove widgets if (subgraphNode.widgets) { subgraphNode.widgets.length = initialWidgetCount expect(subgraphNode.widgets.length).toBe(initialWidgetCount) diff --git a/tests-ui/tests/litegraph/subgraph/SubgraphNode.test.ts b/src/lib/litegraph/src/subgraph/SubgraphNode.test.ts similarity index 98% rename from tests-ui/tests/litegraph/subgraph/SubgraphNode.test.ts rename to src/lib/litegraph/src/subgraph/SubgraphNode.test.ts index 40ad062c6..96e2e5dd3 100644 --- a/tests-ui/tests/litegraph/subgraph/SubgraphNode.test.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphNode.test.ts @@ -7,13 +7,14 @@ */ import { describe, expect, it, vi } from 'vitest' +import type { SubgraphNode } from '@/lib/litegraph/src/litegraph' import { LGraph, Subgraph } from '@/lib/litegraph/src/litegraph' -import { subgraphTest } from './fixtures/subgraphFixtures' +import { subgraphTest } from './__fixtures__/subgraphFixtures' import { createTestSubgraph, createTestSubgraphNode -} from './fixtures/subgraphHelpers' +} from './__fixtures__/subgraphHelpers' describe.skip('SubgraphNode Construction', () => { it('should create a SubgraphNode from a subgraph definition', () => { @@ -210,10 +211,10 @@ describe.skip('SubgraphNode Lifecycle', () => { size: [180, 100], inputs: [], outputs: [], - // @ts-expect-error TODO: Fix after merge - properties not in ExportedSubgraphInstance properties: {}, flags: {}, - mode: 0 + mode: 0, + order: 0 }) // Should reflect updated subgraph structure @@ -542,8 +543,6 @@ describe.skip('SubgraphNode Cleanup', () => { const rootGraph = new LGraph() const subgraph = createTestSubgraph() - // Add and remove nodes multiple times - // @ts-expect-error TODO: Fix after merge - SubgraphNode should be Subgraph const removedNodes: SubgraphNode[] = [] for (let i = 0; i < 3; i++) { const node = createTestSubgraphNode(subgraph) diff --git a/tests-ui/tests/litegraph/subgraph/SubgraphNode.titleButton.test.ts b/src/lib/litegraph/src/subgraph/SubgraphNode.titleButton.test.ts similarity index 99% rename from tests-ui/tests/litegraph/subgraph/SubgraphNode.titleButton.test.ts rename to src/lib/litegraph/src/subgraph/SubgraphNode.titleButton.test.ts index 9bc64802c..c76e9d5ee 100644 --- a/tests-ui/tests/litegraph/subgraph/SubgraphNode.titleButton.test.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphNode.titleButton.test.ts @@ -7,7 +7,7 @@ import type { LGraphCanvas } from '@/lib/litegraph/src/litegraph' import { createTestSubgraph, createTestSubgraphNode -} from './fixtures/subgraphHelpers' +} from './__fixtures__/subgraphHelpers' describe.skip('SubgraphNode Title Button', () => { describe.skip('Constructor', () => { diff --git a/src/lib/litegraph/src/subgraph/SubgraphNode.ts b/src/lib/litegraph/src/subgraph/SubgraphNode.ts index 92b61460f..e26e1cd48 100644 --- a/src/lib/litegraph/src/subgraph/SubgraphNode.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphNode.ts @@ -28,6 +28,7 @@ import type { } from '@/lib/litegraph/src/types/serialisation' import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets' import type { UUID } from '@/lib/litegraph/src/utils/uuid' +import { AssetWidget } from '@/lib/litegraph/src/widgets/AssetWidget' import { toConcreteWidget } from '@/lib/litegraph/src/widgets/widgetMap' import { ExecutableNodeDTO } from './ExecutableNodeDTO' @@ -300,17 +301,24 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph { continue } - const resolved = link.resolve(this.subgraph) - if (!resolved.input || !resolved.inputNode) { - console.warn('Invalid resolved link', resolved, this) + const { inputNode } = link.resolve(this.subgraph) + if (!inputNode) { + console.warn('Failed to resolve inputNode', link, this) + continue + } + + //Manually find input since target_slot can't be trusted + const targetInput = inputNode.inputs.find((inp) => inp.link === linkId) + if (!targetInput) { + console.warn('Failed to find corresponding input', link, inputNode) continue } // No widget - ignore this link - const widget = resolved.inputNode.getWidgetFromSlot(resolved.input) + const widget = inputNode.getWidgetFromSlot(targetInput) if (!widget) continue - this.#setWidget(subgraphInput, input, widget, resolved.input.widget) + this.#setWidget(subgraphInput, input, widget, targetInput.widget) break } } @@ -326,6 +334,8 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph { const promotedWidget = toConcreteWidget(widget, this).createCopyForNode( this ) + if (widget instanceof AssetWidget) + promotedWidget.options.nodeType ??= widget.node.type Object.assign(promotedWidget, { get name() { diff --git a/src/lib/litegraph/src/subgraph/SubgraphOutput.ts b/src/lib/litegraph/src/subgraph/SubgraphOutput.ts index a33654cc2..460e49ff5 100644 --- a/src/lib/litegraph/src/subgraph/SubgraphOutput.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphOutput.ts @@ -158,6 +158,7 @@ export class SubgraphOutput extends SubgraphSlot { //should never have more than one connection for (const linkId of this.linkIds) { const link = subgraph.links[linkId] + if (!link) continue subgraph.removeLink(linkId) const { output, outputNode } = link.resolve(subgraph) if (output) diff --git a/tests-ui/tests/litegraph/subgraph/SubgraphSerialization.test.ts b/src/lib/litegraph/src/subgraph/SubgraphSerialization.test.ts similarity index 99% rename from tests-ui/tests/litegraph/subgraph/SubgraphSerialization.test.ts rename to src/lib/litegraph/src/subgraph/SubgraphSerialization.test.ts index 35e113b0e..f773fe63d 100644 --- a/tests-ui/tests/litegraph/subgraph/SubgraphSerialization.test.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphSerialization.test.ts @@ -12,7 +12,7 @@ import { LGraph, Subgraph } from '@/lib/litegraph/src/litegraph' import { createTestSubgraph, createTestSubgraphNode -} from './fixtures/subgraphHelpers' +} from './__fixtures__/subgraphHelpers' describe.skip('SubgraphSerialization - Basic Serialization', () => { it('should save and load simple subgraphs', () => { diff --git a/tests-ui/tests/litegraph/subgraph/SubgraphSlotConnections.test.ts b/src/lib/litegraph/src/subgraph/SubgraphSlotConnections.test.ts similarity index 96% rename from tests-ui/tests/litegraph/subgraph/SubgraphSlotConnections.test.ts rename to src/lib/litegraph/src/subgraph/SubgraphSlotConnections.test.ts index 0c10b782a..515c0ac22 100644 --- a/tests-ui/tests/litegraph/subgraph/SubgraphSlotConnections.test.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphSlotConnections.test.ts @@ -1,21 +1,24 @@ // TODO: Fix these tests after migration import { describe, expect, it, vi } from 'vitest' -import { LinkConnector } from '@/lib/litegraph/src/litegraph' -import { ToInputFromIoNodeLink } from '@/lib/litegraph/src/litegraph' -import { SUBGRAPH_INPUT_ID } from '@/lib/litegraph/src/litegraph' -import { LGraphNode, type LinkNetwork } from '@/lib/litegraph/src/litegraph' -import type { NodeInputSlot } from '@/lib/litegraph/src/litegraph' -import type { NodeOutputSlot } from '@/lib/litegraph/src/litegraph' import { + SUBGRAPH_INPUT_ID, + LinkConnector, + ToInputFromIoNodeLink, + LGraphNode, isSubgraphInput, isSubgraphOutput } from '@/lib/litegraph/src/litegraph' +import type { + LinkNetwork, + NodeInputSlot, + NodeOutputSlot +} from '@/lib/litegraph/src/litegraph' import { createTestSubgraph, createTestSubgraphNode -} from './fixtures/subgraphHelpers' +} from './__fixtures__/subgraphHelpers' describe.skip('Subgraph slot connections', () => { describe.skip('SubgraphInput connections', () => { diff --git a/tests-ui/tests/litegraph/subgraph/SubgraphSlotVisualFeedback.test.ts b/src/lib/litegraph/src/subgraph/SubgraphSlotVisualFeedback.test.ts similarity index 98% rename from tests-ui/tests/litegraph/subgraph/SubgraphSlotVisualFeedback.test.ts rename to src/lib/litegraph/src/subgraph/SubgraphSlotVisualFeedback.test.ts index 0ed819a4f..baf842812 100644 --- a/tests-ui/tests/litegraph/subgraph/SubgraphSlotVisualFeedback.test.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphSlotVisualFeedback.test.ts @@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { LGraphNode } from '@/lib/litegraph/src/litegraph' -import { createTestSubgraph } from './fixtures/subgraphHelpers' +import { createTestSubgraph } from './__fixtures__/subgraphHelpers' describe.skip('SubgraphSlot visual feedback', () => { let mockCtx: CanvasRenderingContext2D diff --git a/tests-ui/tests/litegraph/subgraph/SubgraphWidgetPromotion.test.ts b/src/lib/litegraph/src/subgraph/SubgraphWidgetPromotion.test.ts similarity index 96% rename from tests-ui/tests/litegraph/subgraph/SubgraphWidgetPromotion.test.ts rename to src/lib/litegraph/src/subgraph/SubgraphWidgetPromotion.test.ts index 36793829d..d47ca186d 100644 --- a/tests-ui/tests/litegraph/subgraph/SubgraphWidgetPromotion.test.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphWidgetPromotion.test.ts @@ -1,16 +1,18 @@ // TODO: Fix these tests after migration import { describe, expect, it } from 'vitest' -import type { ISlotType, Subgraph } from '@/lib/litegraph/src/litegraph' -import { LGraphNode } from '@/lib/litegraph/src/litegraph' -import type { TWidgetType } from '@/lib/litegraph/src/litegraph' -import { BaseWidget } from '@/lib/litegraph/src/litegraph' +import type { + ISlotType, + Subgraph, + TWidgetType +} from '@/lib/litegraph/src/litegraph' +import { BaseWidget, LGraphNode } from '@/lib/litegraph/src/litegraph' import { createEventCapture, createTestSubgraph, createTestSubgraphNode -} from './fixtures/subgraphHelpers' +} from './__fixtures__/subgraphHelpers' // Helper to create a node with a widget function createNodeWithWidget( @@ -132,9 +134,7 @@ describe.skip('SubgraphWidgetPromotion', () => { // Check event was fired const promotedEvents = eventCapture.getEventsByType('widget-promoted') expect(promotedEvents).toHaveLength(1) - // @ts-expect-error Object is of type 'unknown' expect(promotedEvents[0].detail.widget).toBeDefined() - // @ts-expect-error Object is of type 'unknown' expect(promotedEvents[0].detail.subgraphNode).toBe(subgraphNode) eventCapture.cleanup() @@ -159,9 +159,7 @@ describe.skip('SubgraphWidgetPromotion', () => { // Check event was fired const demotedEvents = eventCapture.getEventsByType('widget-demoted') expect(demotedEvents).toHaveLength(1) - // @ts-expect-error Object is of type 'unknown' expect(demotedEvents[0].detail.widget).toBeDefined() - // @ts-expect-error Object is of type 'unknown' expect(demotedEvents[0].detail.subgraphNode).toBe(subgraphNode) // Widget should be removed diff --git a/tests-ui/tests/litegraph/subgraph/fixtures/README.md b/src/lib/litegraph/src/subgraph/__fixtures__/README.md similarity index 100% rename from tests-ui/tests/litegraph/subgraph/fixtures/README.md rename to src/lib/litegraph/src/subgraph/__fixtures__/README.md diff --git a/tests-ui/tests/litegraph/subgraph/fixtures/subgraphFixtures.ts b/src/lib/litegraph/src/subgraph/__fixtures__/subgraphFixtures.ts similarity index 69% rename from tests-ui/tests/litegraph/subgraph/fixtures/subgraphFixtures.ts rename to src/lib/litegraph/src/subgraph/__fixtures__/subgraphFixtures.ts index 02134868c..25fab065e 100644 --- a/tests-ui/tests/litegraph/subgraph/fixtures/subgraphFixtures.ts +++ b/src/lib/litegraph/src/subgraph/__fixtures__/subgraphFixtures.ts @@ -1,3 +1,4 @@ +// oxlint-disable no-empty-pattern /** * Vitest Fixtures for Subgraph Testing * @@ -7,15 +8,17 @@ */ import type { Subgraph } from '@/lib/litegraph/src/litegraph' import { LGraph } from '@/lib/litegraph/src/litegraph' +import type { SubgraphEventMap } from '@/lib/litegraph/src/infrastructure/SubgraphEventMap' import type { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode' -import { test } from '../../core/fixtures/testExtensions' +import { test } from '../../__fixtures__/testExtensions' import { createEventCapture, createNestedSubgraphs, createTestSubgraph, createTestSubgraphNode } from './subgraphHelpers' +import type { EventCapture } from './subgraphHelpers' interface SubgraphFixtures { /** A minimal subgraph with no inputs, outputs, or nodes */ @@ -40,7 +43,7 @@ interface SubgraphFixtures { /** Event capture system for testing subgraph events */ eventCapture: { subgraph: Subgraph - capture: ReturnType + capture: EventCapture } } @@ -58,9 +61,7 @@ interface SubgraphFixtures { * ``` */ export const subgraphTest = test.extend({ - // @ts-expect-error TODO: Fix after merge - fixture use parameter type - // eslint-disable-next-line no-empty-pattern - emptySubgraph: async ({}, use: (value: unknown) => Promise) => { + emptySubgraph: async ({}, use) => { const subgraph = createTestSubgraph({ name: 'Empty Test Subgraph', inputCount: 0, @@ -71,9 +72,7 @@ export const subgraphTest = test.extend({ await use(subgraph) }, - // @ts-expect-error TODO: Fix after merge - fixture use parameter type - // eslint-disable-next-line no-empty-pattern - simpleSubgraph: async ({}, use: (value: unknown) => Promise) => { + simpleSubgraph: async ({}, use) => { const subgraph = createTestSubgraph({ name: 'Simple Test Subgraph', inputs: [{ name: 'input', type: 'number' }], @@ -84,9 +83,7 @@ export const subgraphTest = test.extend({ await use(subgraph) }, - // @ts-expect-error TODO: Fix after merge - fixture use parameter type - // eslint-disable-next-line no-empty-pattern - complexSubgraph: async ({}, use: (value: unknown) => Promise) => { + complexSubgraph: async ({}, use) => { const subgraph = createTestSubgraph({ name: 'Complex Test Subgraph', inputs: [ @@ -104,9 +101,7 @@ export const subgraphTest = test.extend({ await use(subgraph) }, - // @ts-expect-error TODO: Fix after merge - fixture use parameter type - // eslint-disable-next-line no-empty-pattern - nestedSubgraph: async ({}, use: (value: unknown) => Promise) => { + nestedSubgraph: async ({}, use) => { const nested = createNestedSubgraphs({ depth: 3, nodesPerLevel: 2, @@ -117,10 +112,7 @@ export const subgraphTest = test.extend({ await use(nested) }, - // @ts-expect-error TODO: Fix after merge - fixture use parameter type - // eslint-disable-next-line no-empty-pattern - subgraphWithNode: async ({}, use: (value: unknown) => Promise) => { - // Create the subgraph definition + subgraphWithNode: async ({}, use) => { const subgraph = createTestSubgraph({ name: 'Subgraph With Node', inputs: [{ name: 'input', type: '*' }], @@ -128,14 +120,12 @@ export const subgraphTest = test.extend({ nodeCount: 1 }) - // Create the parent graph and subgraph node instance const parentGraph = new LGraph() const subgraphNode = createTestSubgraphNode(subgraph, { pos: [200, 200], size: [180, 80] }) - // Add the subgraph node to the parent graph parentGraph.add(subgraphNode) await use({ @@ -145,15 +135,12 @@ export const subgraphTest = test.extend({ }) }, - // @ts-expect-error TODO: Fix after merge - fixture use parameter type - // eslint-disable-next-line no-empty-pattern - eventCapture: async ({}, use: (value: unknown) => Promise) => { + eventCapture: async ({}, use) => { const subgraph = createTestSubgraph({ name: 'Event Test Subgraph' }) - // Set up event capture for all subgraph events - const capture = createEventCapture(subgraph.events, [ + const capture = createEventCapture(subgraph.events, [ 'adding-input', 'input-added', 'removing-input', @@ -166,7 +153,6 @@ export const subgraphTest = test.extend({ await use({ subgraph, capture }) - // Cleanup event listeners capture.cleanup() } }) diff --git a/tests-ui/tests/litegraph/subgraph/fixtures/subgraphHelpers.ts b/src/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers.ts similarity index 86% rename from tests-ui/tests/litegraph/subgraph/fixtures/subgraphHelpers.ts rename to src/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers.ts index 736f979d3..5672dc685 100644 --- a/tests-ui/tests/litegraph/subgraph/fixtures/subgraphHelpers.ts +++ b/src/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers.ts @@ -55,6 +55,16 @@ interface CapturedEvent { timestamp: number } +/** Return type for createEventCapture with typed getEventsByType */ +export interface EventCapture { + events: CapturedEvent[] + clear: () => void + cleanup: () => void + getEventsByType: ( + type: K + ) => CapturedEvent[] +} + /** * Creates a test subgraph with specified inputs, outputs, and nodes. * This is the primary function for creating subgraphs in tests. @@ -91,34 +101,35 @@ export function createTestSubgraph( } const rootGraph = new LGraph() - // Create the base subgraph data const subgraphData: ExportedSubgraph = { - // Basic graph properties version: 1, + revision: 0, + state: { + lastNodeId: 0, + lastLinkId: 0, + lastGroupId: 0, + lastRerouteId: 0 + }, nodes: [], - // @ts-expect-error TODO: Fix after merge - links type mismatch - links: {}, + links: [], groups: [], config: {}, definitions: { subgraphs: [] }, - // Subgraph-specific properties id: options.id || createUuidv4(), name: options.name || 'Test Subgraph', - // IO Nodes (required for subgraph functionality) inputNode: { - id: -10, // SUBGRAPH_INPUT_ID - bounding: [10, 100, 150, 126], // [x, y, width, height] + id: -10, + bounding: [10, 100, 150, 126], pinned: false }, outputNode: { - id: -20, // SUBGRAPH_OUTPUT_ID - bounding: [400, 100, 140, 126], // [x, y, width, height] + id: -20, + bounding: [400, 100, 140, 126], pinned: false }, - // IO definitions - will be populated by addInput/addOutput calls inputs: [], outputs: [], widgets: [] @@ -127,11 +138,9 @@ export function createTestSubgraph( // Create the subgraph const subgraph = new Subgraph(rootGraph, subgraphData) - // Add requested inputs if (options.inputs) { for (const input of options.inputs) { - // @ts-expect-error TODO: Fix after merge - addInput parameter types - subgraph.addInput(input.name, input.type) + subgraph.addInput(input.name, String(input.type)) } } else if (options.inputCount) { for (let i = 0; i < options.inputCount; i++) { @@ -139,11 +148,9 @@ export function createTestSubgraph( } } - // Add requested outputs if (options.outputs) { for (const output of options.outputs) { - // @ts-expect-error TODO: Fix after merge - addOutput parameter types - subgraph.addOutput(output.name, output.type) + subgraph.addOutput(output.name, String(output.type)) } } else if (options.outputCount) { for (let i = 0; i < options.outputCount; i++) { @@ -193,10 +200,10 @@ export function createTestSubgraphNode( size: options.size || [200, 100], inputs: [], outputs: [], - // @ts-expect-error TODO: Fix after merge - properties type mismatch properties: {}, flags: {}, - mode: 0 + mode: 0, + order: 0 } return new SubgraphNode(parentGraph, subgraph, instanceData) @@ -237,18 +244,11 @@ export function createNestedSubgraphs(options: NestedSubgraphOptions = {}) { subgraphs.push(subgraph) - // Create instance in parent const subgraphNode = createTestSubgraphNode(subgraph, { pos: [100 + level * 200, 100] }) - if (currentParent instanceof LGraph) { - currentParent.add(subgraphNode) - } else { - // @ts-expect-error TODO: Fix after merge - add method parameter types - currentParent.add(subgraphNode) - } - + currentParent.add(subgraphNode) subgraphNodes.push(subgraphNode) // Next level will be nested inside this subgraph @@ -353,9 +353,15 @@ export function createTestSubgraphData( ): ExportedSubgraph { return { version: 1, + revision: 0, + state: { + lastNodeId: 0, + lastLinkId: 0, + lastGroupId: 0, + lastRerouteId: 0 + }, nodes: [], - // @ts-expect-error TODO: Fix after merge - links type mismatch - links: {}, + links: [], groups: [], config: {}, definitions: { subgraphs: [] }, @@ -386,13 +392,13 @@ export function createTestSubgraphData( * Creates an event capture system for testing event sequences. * @param eventTarget The event target to monitor * @param eventTypes Array of event types to capture - * @returns Object with captured events and helper methods + * @returns Object with captured events and typed getEventsByType method */ -export function createEventCapture( +export function createEventCapture( eventTarget: EventTarget, - eventTypes: string[] -) { - const capturedEvents: CapturedEvent[] = [] + eventTypes: Array +): EventCapture { + const capturedEvents: CapturedEvent[] = [] const listeners: Array<() => void> = [] // Set up listeners for each event type @@ -400,7 +406,7 @@ export function createEventCapture( const listener = (event: Event) => { capturedEvents.push({ type: eventType, - detail: (event as CustomEvent).detail, + detail: (event as CustomEvent).detail, timestamp: Date.now() }) } @@ -418,7 +424,9 @@ export function createEventCapture( // Remove all event listeners to prevent memory leaks for (const cleanup of listeners) cleanup() }, - getEventsByType: (type: string) => - capturedEvents.filter((e) => e.type === type) + getEventsByType: (type: K) => + capturedEvents.filter((e) => e.type === type) as CapturedEvent< + TEventMap[K] + >[] } } diff --git a/tests-ui/tests/litegraph/subgraph/fixtures/testSubgraphs.json b/src/lib/litegraph/src/subgraph/__fixtures__/testSubgraphs.json similarity index 100% rename from tests-ui/tests/litegraph/subgraph/fixtures/testSubgraphs.json rename to src/lib/litegraph/src/subgraph/__fixtures__/testSubgraphs.json diff --git a/tests-ui/tests/litegraph/subgraph/subgraphUtils.test.ts b/src/lib/litegraph/src/subgraph/subgraphUtils.test.ts similarity index 98% rename from tests-ui/tests/litegraph/subgraph/subgraphUtils.test.ts rename to src/lib/litegraph/src/subgraph/subgraphUtils.test.ts index c1cea490f..a5f5893b0 100644 --- a/tests-ui/tests/litegraph/subgraph/subgraphUtils.test.ts +++ b/src/lib/litegraph/src/subgraph/subgraphUtils.test.ts @@ -1,8 +1,8 @@ // TODO: Fix these tests after migration import { describe, expect, it } from 'vitest' -import { LGraph } from '@/lib/litegraph/src/litegraph' import { + LGraph, findUsedSubgraphIds, getDirectSubgraphIds } from '@/lib/litegraph/src/litegraph' @@ -11,7 +11,7 @@ import type { UUID } from '@/lib/litegraph/src/litegraph' import { createTestSubgraph, createTestSubgraphNode -} from './fixtures/subgraphHelpers' +} from './__fixtures__/subgraphHelpers' describe.skip('subgraphUtils', () => { describe.skip('getDirectSubgraphIds', () => { diff --git a/src/lib/litegraph/src/subgraph/subgraphUtils.ts b/src/lib/litegraph/src/subgraph/subgraphUtils.ts index 70ffc7a5d..518869503 100644 --- a/src/lib/litegraph/src/subgraph/subgraphUtils.ts +++ b/src/lib/litegraph/src/subgraph/subgraphUtils.ts @@ -4,6 +4,7 @@ import { LGraphNode } from '@/lib/litegraph/src/LGraphNode' import { LLink } from '@/lib/litegraph/src/LLink' import type { ResolvedConnection } from '@/lib/litegraph/src/LLink' import { Reroute } from '@/lib/litegraph/src/Reroute' +import type { RerouteId } from '@/lib/litegraph/src/Reroute' import { SUBGRAPH_INPUT_ID, SUBGRAPH_OUTPUT_ID @@ -221,11 +222,13 @@ export function multiClone(nodes: Iterable): ISerialisedNode[] { const newNode = LiteGraph.createNode(node.type) if (!newNode) { console.warn('Failed to create node', node.type) + const serializedData = structuredClone(node.serialize()) + clonedNodes.push(serializedData) continue } // Must be cloned; litegraph "serialize" is mostly shallow clone - const data = LiteGraph.cloneObject(node.serialize()) + const data = structuredClone(node.serialize()) newNode.configure(data) clonedNodes.push(newNode.serialize()) @@ -257,10 +260,29 @@ export function groupResolvedByOutput( return groupedByOutput } +function mapReroutes( + link: SerialisableLLink, + reroutes: Map +) { + let child: SerialisableLLink | Reroute = link + let nextReroute = + child.parentId === undefined ? undefined : reroutes.get(child.parentId) + + while (child.parentId !== undefined && nextReroute) { + child = nextReroute + nextReroute = + child.parentId === undefined ? undefined : reroutes.get(child.parentId) + } + + const lastId = child.parentId + child.parentId = undefined + return lastId +} export function mapSubgraphInputsAndLinks( resolvedInputLinks: ResolvedConnection[], - links: SerialisableLLink[] + links: SerialisableLLink[], + reroutes: Map ): SubgraphIO[] { // Group matching links const groupedByOutput = groupResolvedByOutput(resolvedInputLinks) @@ -277,8 +299,10 @@ export function mapSubgraphInputsAndLinks( if (!input) continue const linkData = link.asSerialisable() + link.parentId = mapReroutes(link, reroutes) linkData.origin_id = SUBGRAPH_INPUT_ID linkData.origin_slot = inputs.length + links.push(linkData) inputLinks.push(linkData) } @@ -338,7 +362,8 @@ export function mapSubgraphInputsAndLinks( */ export function mapSubgraphOutputsAndLinks( resolvedOutputLinks: ResolvedConnection[], - links: SerialisableLLink[] + links: SerialisableLLink[], + reroutes: Map ): SubgraphIO[] { // Group matching links const groupedByOutput = groupResolvedByOutput(resolvedOutputLinks) @@ -353,10 +378,11 @@ export function mapSubgraphOutputsAndLinks( const { link, output } = resolved if (!output) continue - // Link const linkData = link.asSerialisable() + linkData.parentId = mapReroutes(link, reroutes) linkData.target_id = SUBGRAPH_OUTPUT_ID linkData.target_slot = outputs.length + links.push(linkData) outputLinks.push(linkData) } diff --git a/src/lib/litegraph/src/types/events.ts b/src/lib/litegraph/src/types/events.ts index e5f8912ab..17ab8e8c2 100644 --- a/src/lib/litegraph/src/types/events.ts +++ b/src/lib/litegraph/src/types/events.ts @@ -46,9 +46,7 @@ export interface CanvasPointerEvent extends PointerEvent, CanvasMouseEvent {} /** MouseEvent with canvasX/Y and deltaX/Y properties */ interface CanvasMouseEvent - extends MouseEvent, - Readonly, - LegacyMouseEvent {} + extends MouseEvent, Readonly, LegacyMouseEvent {} export type CanvasEventDetail = | GenericEventDetail diff --git a/src/lib/litegraph/src/types/serialisation.ts b/src/lib/litegraph/src/types/serialisation.ts index 8a31e5652..c97293829 100644 --- a/src/lib/litegraph/src/types/serialisation.ts +++ b/src/lib/litegraph/src/types/serialisation.ts @@ -111,6 +111,8 @@ export interface ExportedSubgraphInstance extends NodeSubgraphSharedProps { * @see {@link ExportedSubgraph.subgraphs} */ type: UUID + /** Custom properties for this subgraph instance */ + properties?: Dictionary } /** diff --git a/src/lib/litegraph/src/types/widgets.ts b/src/lib/litegraph/src/types/widgets.ts index 5f0d1b9a3..d3c249ba3 100644 --- a/src/lib/litegraph/src/types/widgets.ts +++ b/src/lib/litegraph/src/types/widgets.ts @@ -1,3 +1,5 @@ +import type { Bounds } from '@/renderer/core/layout/types' + import type { CanvasColour, Point, RequiredProps, Size } from '../interfaces' import type { CanvasPointer, LGraphCanvas, LGraphNode } from '../litegraph' import type { CanvasPointerEvent } from './events' @@ -27,6 +29,8 @@ export interface IWidgetOptions { socketless?: boolean /** If `true`, the widget will not be rendered by the Vue renderer. */ canvasOnly?: boolean + /** Used as a temporary override for determining the asset type in vue mode*/ + nodeType?: string values?: TValues /** Optional function to format values for display (e.g., hash → human-readable name) */ @@ -52,6 +56,10 @@ interface IWidgetKnobOptions extends IWidgetOptions { gradient_stops?: string } +export interface IWidgetAssetOptions extends IWidgetOptions { + openModal: (widget: IBaseWidget) => void +} + /** * A widget for a node. * All types are based on IBaseWidget - additions can be made there or directly on individual types. @@ -82,6 +90,8 @@ export type IWidget = | ISelectButtonWidget | ITextareaWidget | IAssetWidget + | IImageCropWidget + | IBoundingBoxWidget export interface IBooleanWidget extends IBaseWidget { type: 'toggle' @@ -94,27 +104,32 @@ export interface INumericWidget extends IBaseWidget { value: number } -export interface ISliderWidget - extends IBaseWidget { +export interface ISliderWidget extends IBaseWidget< + number, + 'slider', + IWidgetSliderOptions +> { type: 'slider' value: number marker?: number } -export interface IKnobWidget - extends IBaseWidget { +export interface IKnobWidget extends IBaseWidget< + number, + 'knob', + IWidgetKnobOptions +> { type: 'knob' value: number options: IWidgetKnobOptions } /** Avoids the type issues with the legacy IComboWidget type */ -export interface IStringComboWidget - extends IBaseWidget< - string, - 'combo', - RequiredProps, 'values'> - > { +export interface IStringComboWidget extends IBaseWidget< + string, + 'combo', + RequiredProps, 'values'> +> { type: 'combo' value: string } @@ -125,25 +140,29 @@ type ComboWidgetValues = | ((widget?: IComboWidget, node?: LGraphNode) => string[]) /** A combo-box widget (dropdown, select, etc) */ -export interface IComboWidget - extends IBaseWidget< - string | number, - 'combo', - RequiredProps, 'values'> - > { +export interface IComboWidget extends IBaseWidget< + string | number, + 'combo', + RequiredProps, 'values'> +> { type: 'combo' value: string | number } /** A widget with a string value */ -export interface IStringWidget - extends IBaseWidget> { +export interface IStringWidget extends IBaseWidget< + string, + 'string' | 'text', + IWidgetOptions +> { type: 'string' | 'text' value: string } -export interface IButtonWidget - extends IBaseWidget { +export interface IButtonWidget extends IBaseWidget< + string | undefined, + 'button' +> { type: 'button' value: string | undefined clicked: boolean @@ -181,15 +200,19 @@ interface IImageWidget extends IBaseWidget { } /** Tree select widget for hierarchical selection */ -export interface ITreeSelectWidget - extends IBaseWidget { +export interface ITreeSelectWidget extends IBaseWidget< + string | string[], + 'treeselect' +> { type: 'treeselect' value: string | string[] } /** Multi-select widget for selecting multiple options */ -export interface IMultiSelectWidget - extends IBaseWidget { +export interface IMultiSelectWidget extends IBaseWidget< + string[], + 'multiselect' +> { type: 'multiselect' value: string[] } @@ -207,19 +230,20 @@ export interface IGalleriaWidget extends IBaseWidget { } /** Image comparison widget for comparing two images side by side */ -export interface IImageCompareWidget - extends IBaseWidget { +export interface IImageCompareWidget extends IBaseWidget< + string[], + 'imagecompare' +> { type: 'imagecompare' value: string[] } /** Select button widget for selecting from a group of buttons */ -export interface ISelectButtonWidget - extends IBaseWidget< - string, - 'selectbutton', - RequiredProps, 'values'> - > { +export interface ISelectButtonWidget extends IBaseWidget< + string, + 'selectbutton', + RequiredProps, 'values'> +> { type: 'selectbutton' value: string } @@ -230,12 +254,27 @@ export interface ITextareaWidget extends IBaseWidget { value: string } -export interface IAssetWidget - extends IBaseWidget> { +export interface IAssetWidget extends IBaseWidget< + string, + 'asset', + IWidgetAssetOptions +> { type: 'asset' value: string } +/** Image crop widget for cropping image */ +export interface IImageCropWidget extends IBaseWidget { + type: 'imagecrop' + value: Bounds +} + +/** Bounding box widget for defining regions with numeric inputs */ +export interface IBoundingBoxWidget extends IBaseWidget { + type: 'boundingbox' + value: Bounds +} + /** * Valid widget types. TS cannot provide easily extensible type safety for this at present. * Override linkedWidgets[] @@ -267,6 +306,7 @@ export interface IBaseWidget< /** Widget type (see {@link TWidgetType}) */ type: TType value?: TValue + vueTrack?: () => void /** * Whether the widget value should be serialized on node serialization. @@ -323,7 +363,7 @@ export interface IBaseWidget< // TODO: Confirm this format callback?( - value: any, + value: unknown, canvas?: LGraphCanvas, node?: LGraphNode, pos?: Point, diff --git a/tests-ui/tests/litegraph/utils/spaceDistribution.test.ts b/src/lib/litegraph/src/utils/spaceDistribution.test.ts similarity index 90% rename from tests-ui/tests/litegraph/utils/spaceDistribution.test.ts rename to src/lib/litegraph/src/utils/spaceDistribution.test.ts index 4d029762f..254c13da6 100644 --- a/tests-ui/tests/litegraph/utils/spaceDistribution.test.ts +++ b/src/lib/litegraph/src/utils/spaceDistribution.test.ts @@ -1,9 +1,7 @@ import { describe, expect, it } from 'vitest' -import { - type SpaceRequest, - distributeSpace -} from '@/lib/litegraph/src/litegraph' +import { distributeSpace } from '@/lib/litegraph/src/litegraph' +import type { SpaceRequest } from '@/lib/litegraph/src/litegraph' describe('distributeSpace', () => { it('should distribute space according to minimum sizes when space is limited', () => { diff --git a/tests-ui/tests/litegraph/utils/textUtils.test.ts b/src/lib/litegraph/src/utils/textUtils.test.ts similarity index 100% rename from tests-ui/tests/litegraph/utils/textUtils.test.ts rename to src/lib/litegraph/src/utils/textUtils.test.ts diff --git a/src/lib/litegraph/src/utils/type.ts b/src/lib/litegraph/src/utils/type.ts index 57c45872a..84891fb7f 100644 --- a/src/lib/litegraph/src/utils/type.ts +++ b/src/lib/litegraph/src/utils/type.ts @@ -1,4 +1,6 @@ -import type { IColorable } from '@/lib/litegraph/src/interfaces' +import { without } from 'es-toolkit' + +import type { IColorable, ISlotType } from '@/lib/litegraph/src/interfaces' /** * Converts a plain object to a class instance if it is not already an instance of the class. @@ -26,3 +28,31 @@ export function isColorable(obj: unknown): obj is IColorable { 'getColorOption' in obj ) } + +export function commonType(...types: ISlotType[]): ISlotType | undefined { + if (!isStrings(types)) return undefined + + const withoutWildcards = without(types, '*') + if (withoutWildcards.length === 0) return '*' + + const typeLists: string[][] = withoutWildcards.map((type) => type.split(',')) + + const combinedTypes = intersection(...typeLists) + if (combinedTypes.length === 0) return undefined + + return combinedTypes.join(',') +} + +function intersection(...sets: string[][]): string[] { + const itemCounts: Record = {} + for (const set of sets) + for (const item of new Set(set)) + itemCounts[item] = (itemCounts[item] ?? 0) + 1 + return Object.entries(itemCounts) + .filter(([, count]) => count === sets.length) + .map(([key]) => key) +} + +function isStrings(types: unknown[]): types is string[] { + return types.every((t) => typeof t === 'string') +} diff --git a/tests-ui/tests/litegraph/utils/widget.test.ts b/src/lib/litegraph/src/utils/widget.test.ts similarity index 100% rename from tests-ui/tests/litegraph/utils/widget.test.ts rename to src/lib/litegraph/src/utils/widget.test.ts diff --git a/src/lib/litegraph/src/utils/widget.ts b/src/lib/litegraph/src/utils/widget.ts index e5d736901..8c61d7605 100644 --- a/src/lib/litegraph/src/utils/widget.ts +++ b/src/lib/litegraph/src/utils/widget.ts @@ -8,3 +8,18 @@ import type { IWidgetOptions } from '@/lib/litegraph/src/types/widgets' export function getWidgetStep(options: IWidgetOptions): number { return options.step2 || (options.step || 10) * 0.1 } + +export function evaluateInput(input: string): number | undefined { + // Check if v is a valid equation or a number + if (/^[\d\s.()*+/-]+$/.test(input)) { + // Solve the equation if possible + try { + input = eval(input) + } catch { + // Ignore eval errors + } + } + const newValue = Number(input) + if (isNaN(newValue)) return undefined + return newValue +} diff --git a/src/lib/litegraph/src/widgets/AssetWidget.ts b/src/lib/litegraph/src/widgets/AssetWidget.ts index bc60a76b4..d836cdabf 100644 --- a/src/lib/litegraph/src/widgets/AssetWidget.ts +++ b/src/lib/litegraph/src/widgets/AssetWidget.ts @@ -53,6 +53,6 @@ export class AssetWidget override onClick() { //Open Modal - this.callback?.(this.value) + this.options.openModal(this) } } diff --git a/src/lib/litegraph/src/widgets/BaseWidget.ts b/src/lib/litegraph/src/widgets/BaseWidget.ts index b5fc11984..9104c8a5b 100644 --- a/src/lib/litegraph/src/widgets/BaseWidget.ts +++ b/src/lib/litegraph/src/widgets/BaseWidget.ts @@ -1,3 +1,4 @@ +import { t } from '@/i18n' import { drawTextInArea } from '@/lib/litegraph/src/draw' import { Rectangle } from '@/lib/litegraph/src/infrastructure/Rectangle' import type { Point } from '@/lib/litegraph/src/interfaces' @@ -33,9 +34,9 @@ export interface WidgetEventOptions { canvas: LGraphCanvas } -export abstract class BaseWidget - implements IBaseWidget -{ +export abstract class BaseWidget< + TWidget extends IBaseWidget = IBaseWidget +> implements IBaseWidget { /** From node edge to widget edge */ static margin = 15 /** From widget edge to tip of arrow button */ @@ -78,7 +79,7 @@ export abstract class BaseWidget tooltip?: string element?: HTMLElement callback?( - value: any, + value: TWidget['value'], canvas?: LGraphCanvas, node?: LGraphNode, pos?: Point, @@ -141,6 +142,7 @@ export abstract class BaseWidget // @ts-expect-error Prevent naming conflicts with custom nodes. labelBaseline, promoted, + linkedWidgets, ...safeValues } = widget @@ -227,6 +229,41 @@ export abstract class BaseWidget if (showText && !this.computedDisabled) ctx.stroke() } + /** + * Draws a placeholder for widgets that only have a Vue implementation. + * @param ctx The canvas context + * @param options The options for drawing the widget + * @param label The label to display (e.g., "ImageCrop", "BoundingBox") + */ + protected drawVueOnlyWarning( + ctx: CanvasRenderingContext2D, + { width }: DrawWidgetOptions, + label: string + ): void { + const { y, height } = this + + ctx.save() + + ctx.fillStyle = this.background_color + ctx.fillRect(15, y, width - 30, height) + + ctx.strokeStyle = this.outline_color + ctx.strokeRect(15, y, width - 30, height) + + ctx.fillStyle = this.text_color + ctx.font = '11px monospace' + ctx.textAlign = 'center' + ctx.textBaseline = 'middle' + + ctx.fillText( + `${label}: ${t('widgets.node2only')}`, + width / 2, + y + height / 2 + ) + + ctx.restore() + } + /** * A shared routine for drawing a label and value as text, truncated * if they exceed the available width. diff --git a/src/lib/litegraph/src/widgets/BoundingBoxWidget.ts b/src/lib/litegraph/src/widgets/BoundingBoxWidget.ts new file mode 100644 index 000000000..d571f744f --- /dev/null +++ b/src/lib/litegraph/src/widgets/BoundingBoxWidget.ts @@ -0,0 +1,22 @@ +import type { IBoundingBoxWidget } from '../types/widgets' +import { BaseWidget } from './BaseWidget' +import type { DrawWidgetOptions, WidgetEventOptions } from './BaseWidget' + +/** + * Widget for defining bounding box regions. + * This widget only has a Vue implementation. + */ +export class BoundingBoxWidget + extends BaseWidget + implements IBoundingBoxWidget +{ + override type = 'boundingbox' as const + + drawWidget(ctx: CanvasRenderingContext2D, options: DrawWidgetOptions): void { + this.drawVueOnlyWarning(ctx, options, 'BoundingBox') + } + + onClick(_options: WidgetEventOptions): void { + // This widget only has a Vue implementation + } +} diff --git a/src/lib/litegraph/src/widgets/ButtonWidget.ts b/src/lib/litegraph/src/widgets/ButtonWidget.ts index 38bc74bc7..28d5952c6 100644 --- a/src/lib/litegraph/src/widgets/ButtonWidget.ts +++ b/src/lib/litegraph/src/widgets/ButtonWidget.ts @@ -65,7 +65,7 @@ export class ButtonWidget this.clicked = true canvas.setDirty(true) - // Call the callback with widget instance and other context - this.callback?.(this, canvas, node, pos, e) + // Call the callback with widget value + this.callback?.(this.value, canvas, node, pos, e) } } diff --git a/src/lib/litegraph/src/widgets/ChartWidget.ts b/src/lib/litegraph/src/widgets/ChartWidget.ts index 440ca4f08..1a32f1010 100644 --- a/src/lib/litegraph/src/widgets/ChartWidget.ts +++ b/src/lib/litegraph/src/widgets/ChartWidget.ts @@ -1,3 +1,5 @@ +import { t } from '@/i18n' + import type { IChartWidget } from '../types/widgets' import { BaseWidget } from './BaseWidget' import type { DrawWidgetOptions, WidgetEventOptions } from './BaseWidget' @@ -29,7 +31,7 @@ export class ChartWidget ctx.textAlign = 'center' ctx.textBaseline = 'middle' - const text = 'Chart: Vue-only' + const text = `Chart: ${t('widgets.node2only')}` ctx.fillText(text, width / 2, y + height / 2) Object.assign(ctx, { diff --git a/src/lib/litegraph/src/widgets/ColorWidget.ts b/src/lib/litegraph/src/widgets/ColorWidget.ts index a0b0b3496..f2c50083a 100644 --- a/src/lib/litegraph/src/widgets/ColorWidget.ts +++ b/src/lib/litegraph/src/widgets/ColorWidget.ts @@ -1,3 +1,5 @@ +import { t } from '@/i18n' + import type { IColorWidget } from '../types/widgets' import { BaseWidget } from './BaseWidget' import type { DrawWidgetOptions, WidgetEventOptions } from './BaseWidget' @@ -29,7 +31,7 @@ export class ColorWidget ctx.textAlign = 'center' ctx.textBaseline = 'middle' - const text = 'Color: Vue-only' + const text = `Color: ${t('widgets.node2only')}` ctx.fillText(text, width / 2, y + height / 2) Object.assign(ctx, { diff --git a/tests-ui/tests/lib/litegraph/src/widgets/ComboWidget.test.ts b/src/lib/litegraph/src/widgets/ComboWidget.test.ts similarity index 95% rename from tests-ui/tests/lib/litegraph/src/widgets/ComboWidget.test.ts rename to src/lib/litegraph/src/widgets/ComboWidget.test.ts index e3b9ef6cd..f080dbab7 100644 --- a/tests-ui/tests/lib/litegraph/src/widgets/ComboWidget.test.ts +++ b/src/lib/litegraph/src/widgets/ComboWidget.test.ts @@ -1,23 +1,16 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' +import type * as LGraphCanvasModule from '@/lib/litegraph/src/LGraphCanvas' import { LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph' import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events' import type { IComboWidget } from '@/lib/litegraph/src/types/widgets' import { ComboWidget } from '@/lib/litegraph/src/widgets/ComboWidget' -const { LGraphCanvas } = await vi.importActual< - typeof import('@/lib/litegraph/src/LGraphCanvas') ->('@/lib/litegraph/src/LGraphCanvas') +const { LGraphCanvas } = await vi.importActual( + '@/lib/litegraph/src/LGraphCanvas' +) type LGraphCanvasType = InstanceType -type ContextMenuInstance = { - addItem?: ( - name: string, - value: string, - options: { callback?: (value: string) => void; className?: string } - ) => void -} - interface MockWidgetConfig extends Omit { options: IComboWidget['options'] } @@ -465,10 +458,11 @@ describe('ComboWidget', () => { node.size = [200, 30] let capturedCallback: ((value: string) => void) | undefined - const mockContextMenu = vi.fn((_values, options) => { - capturedCallback = options.callback - return {} as ContextMenuInstance - }) + const mockContextMenu = vi + .fn() + .mockImplementation(function (_values, options) { + capturedCallback = options.callback + }) LiteGraph.ContextMenu = mockContextMenu as unknown as typeof LiteGraph.ContextMenu @@ -507,10 +501,11 @@ describe('ComboWidget', () => { node.size = [200, 30] let capturedCallback: ((value: string) => void) | undefined - const mockContextMenu = vi.fn((_values, options) => { - capturedCallback = options.callback - return {} as ContextMenuInstance - }) + const mockContextMenu = vi + .fn() + .mockImplementation(function (_values, options) { + capturedCallback = options.callback + }) LiteGraph.ContextMenu = mockContextMenu as unknown as typeof LiteGraph.ContextMenu @@ -653,7 +648,7 @@ describe('ComboWidget', () => { }) it('should handle getOptionLabel error gracefully', () => { - const mockGetOptionLabel = vi.fn().mockImplementation(() => { + const mockGetOptionLabel = vi.fn().mockImplementation(function () { throw new Error('Formatting failed') }) const consoleErrorSpy = vi @@ -768,10 +763,13 @@ describe('ComboWidget', () => { node.size = [200, 30] const mockAddItem = vi.fn() - const mockContextMenu = vi.fn(() => ({ addItem: mockAddItem })) + const mockContextMenu = vi + .fn() + .mockImplementation(function () { + this.addItem = mockAddItem + }) LiteGraph.ContextMenu = mockContextMenu as unknown as typeof LiteGraph.ContextMenu - widget.onClick({ e: mockEvent, node, canvas: mockCanvas }) // Should show formatted labels in dropdown @@ -827,10 +825,12 @@ describe('ComboWidget', () => { const mockAddItem = vi.fn() let capturedCallback: ((value: string) => void) | undefined - const mockContextMenu = vi.fn((_values, options) => { - capturedCallback = options.callback - return { addItem: mockAddItem } - }) + const mockContextMenu = vi + .fn() + .mockImplementation(function (_values, options) { + capturedCallback = options.callback + this.addItem = mockAddItem + }) LiteGraph.ContextMenu = mockContextMenu as unknown as typeof LiteGraph.ContextMenu @@ -879,10 +879,12 @@ describe('ComboWidget', () => { const mockAddItem = vi.fn() let capturedCallback: ((value: string) => void) | undefined - const mockContextMenu = vi.fn((_values, options) => { - capturedCallback = options.callback - return { addItem: mockAddItem } as ContextMenuInstance - }) + const mockContextMenu = vi + .fn() + .mockImplementation(function (_values, options) { + capturedCallback = options.callback + this.addItem = mockAddItem + }) LiteGraph.ContextMenu = mockContextMenu as unknown as typeof LiteGraph.ContextMenu @@ -931,7 +933,7 @@ describe('ComboWidget', () => { const mockGetOptionLabel = vi .fn() .mockReturnValueOnce('Beautiful Sunset.png') - .mockImplementationOnce(() => { + .mockImplementationOnce(function () { throw new Error('Formatting failed') }) @@ -957,9 +959,11 @@ describe('ComboWidget', () => { .mockImplementation(() => {}) const mockAddItem = vi.fn() - const mockContextMenu = vi.fn(() => { - return { addItem: mockAddItem } as ContextMenuInstance - }) + const mockContextMenu = vi + .fn() + .mockImplementation(function () { + this.addItem = mockAddItem + }) LiteGraph.ContextMenu = mockContextMenu as unknown as typeof LiteGraph.ContextMenu @@ -1007,7 +1011,7 @@ describe('ComboWidget', () => { node.pos = [50, 50] node.size = [200, 30] - const mockContextMenu = vi.fn() + const mockContextMenu = vi.fn() LiteGraph.ContextMenu = mockContextMenu as unknown as typeof LiteGraph.ContextMenu diff --git a/src/lib/litegraph/src/widgets/FileUploadWidget.ts b/src/lib/litegraph/src/widgets/FileUploadWidget.ts index f73de53f2..bffb9eb6c 100644 --- a/src/lib/litegraph/src/widgets/FileUploadWidget.ts +++ b/src/lib/litegraph/src/widgets/FileUploadWidget.ts @@ -1,3 +1,5 @@ +import { t } from '@/i18n' + import type { IFileUploadWidget } from '../types/widgets' import { BaseWidget } from './BaseWidget' import type { DrawWidgetOptions, WidgetEventOptions } from './BaseWidget' @@ -29,7 +31,7 @@ export class FileUploadWidget ctx.textAlign = 'center' ctx.textBaseline = 'middle' - const text = 'Fileupload: Vue-only' + const text = `Fileupload: ${t('widgets.node2only')}` ctx.fillText(text, width / 2, y + height / 2) Object.assign(ctx, { diff --git a/src/lib/litegraph/src/widgets/GalleriaWidget.ts b/src/lib/litegraph/src/widgets/GalleriaWidget.ts index 75770b2bb..767e652de 100644 --- a/src/lib/litegraph/src/widgets/GalleriaWidget.ts +++ b/src/lib/litegraph/src/widgets/GalleriaWidget.ts @@ -1,3 +1,5 @@ +import { t } from '@/i18n' + import type { IGalleriaWidget } from '../types/widgets' import { BaseWidget } from './BaseWidget' import type { DrawWidgetOptions, WidgetEventOptions } from './BaseWidget' @@ -29,7 +31,7 @@ export class GalleriaWidget ctx.textAlign = 'center' ctx.textBaseline = 'middle' - const text = 'Galleria: Vue-only' + const text = `Galleria: ${t('widgets.node2only')}` ctx.fillText(text, width / 2, y + height / 2) Object.assign(ctx, { diff --git a/src/lib/litegraph/src/widgets/ImageCompareWidget.ts b/src/lib/litegraph/src/widgets/ImageCompareWidget.ts index 3f593e0ee..d31e1b84c 100644 --- a/src/lib/litegraph/src/widgets/ImageCompareWidget.ts +++ b/src/lib/litegraph/src/widgets/ImageCompareWidget.ts @@ -1,3 +1,5 @@ +import { t } from '@/i18n' + import type { IImageCompareWidget } from '../types/widgets' import { BaseWidget } from './BaseWidget' import type { DrawWidgetOptions, WidgetEventOptions } from './BaseWidget' @@ -29,7 +31,7 @@ export class ImageCompareWidget ctx.textAlign = 'center' ctx.textBaseline = 'middle' - const text = 'ImageCompare: Vue-only' + const text = `ImageCompare: ${t('widgets.node2only')}` ctx.fillText(text, width / 2, y + height / 2) Object.assign(ctx, { diff --git a/src/lib/litegraph/src/widgets/ImageCropWidget.ts b/src/lib/litegraph/src/widgets/ImageCropWidget.ts new file mode 100644 index 000000000..a81cd580c --- /dev/null +++ b/src/lib/litegraph/src/widgets/ImageCropWidget.ts @@ -0,0 +1,22 @@ +import type { IImageCropWidget } from '../types/widgets' +import { BaseWidget } from './BaseWidget' +import type { DrawWidgetOptions, WidgetEventOptions } from './BaseWidget' + +/** + * Widget for displaying an image crop preview. + * This widget only has a Vue implementation. + */ +export class ImageCropWidget + extends BaseWidget + implements IImageCropWidget +{ + override type = 'imagecrop' as const + + drawWidget(ctx: CanvasRenderingContext2D, options: DrawWidgetOptions): void { + this.drawVueOnlyWarning(ctx, options, 'ImageCrop') + } + + onClick(_options: WidgetEventOptions): void { + // This widget only has a Vue implementation + } +} diff --git a/src/lib/litegraph/src/widgets/MarkdownWidget.ts b/src/lib/litegraph/src/widgets/MarkdownWidget.ts index 6ca6512fe..f995936e6 100644 --- a/src/lib/litegraph/src/widgets/MarkdownWidget.ts +++ b/src/lib/litegraph/src/widgets/MarkdownWidget.ts @@ -1,3 +1,5 @@ +import { t } from '@/i18n' + import type { IMarkdownWidget } from '../types/widgets' import { BaseWidget } from './BaseWidget' import type { DrawWidgetOptions, WidgetEventOptions } from './BaseWidget' @@ -29,7 +31,7 @@ export class MarkdownWidget ctx.textAlign = 'center' ctx.textBaseline = 'middle' - const text = 'Markdown: Vue-only' + const text = `Markdown: ${t('widgets.node2only')}` ctx.fillText(text, width / 2, y + height / 2) Object.assign(ctx, { diff --git a/src/lib/litegraph/src/widgets/MultiSelectWidget.ts b/src/lib/litegraph/src/widgets/MultiSelectWidget.ts index 5535820f3..25b59cc1f 100644 --- a/src/lib/litegraph/src/widgets/MultiSelectWidget.ts +++ b/src/lib/litegraph/src/widgets/MultiSelectWidget.ts @@ -1,3 +1,5 @@ +import { t } from '@/i18n' + import type { IMultiSelectWidget } from '../types/widgets' import { BaseWidget } from './BaseWidget' import type { DrawWidgetOptions, WidgetEventOptions } from './BaseWidget' @@ -29,7 +31,7 @@ export class MultiSelectWidget ctx.textAlign = 'center' ctx.textBaseline = 'middle' - const text = 'MultiSelect: Vue-only' + const text = `MultiSelect: ${t('widgets.node2only')}` ctx.fillText(text, width / 2, y + height / 2) Object.assign(ctx, { diff --git a/src/lib/litegraph/src/widgets/NumberWidget.ts b/src/lib/litegraph/src/widgets/NumberWidget.ts index 294bbb0b0..c142d8b4d 100644 --- a/src/lib/litegraph/src/widgets/NumberWidget.ts +++ b/src/lib/litegraph/src/widgets/NumberWidget.ts @@ -1,5 +1,5 @@ import type { INumericWidget } from '@/lib/litegraph/src/types/widgets' -import { getWidgetStep } from '@/lib/litegraph/src/utils/widget' +import { evaluateInput, getWidgetStep } from '@/lib/litegraph/src/utils/widget' import { BaseSteppedWidget } from './BaseSteppedWidget' import type { WidgetEventOptions } from './BaseWidget' @@ -68,19 +68,8 @@ export class NumberWidget 'Value', this.value, (v: string) => { - // Check if v is a valid equation or a number - if (/^[\d\s()*+/-]+|\d+\.\d+$/.test(v)) { - // Solve the equation if possible - try { - v = eval(v) - } catch { - // Ignore eval errors - } - } - const newValue = Number(v) - if (!isNaN(newValue)) { - this.setValue(newValue, { e, node, canvas }) - } + const parsed = evaluateInput(v) + if (parsed !== undefined) this.setValue(parsed, { e, node, canvas }) }, e ) diff --git a/src/lib/litegraph/src/widgets/SelectButtonWidget.ts b/src/lib/litegraph/src/widgets/SelectButtonWidget.ts index 463efe089..4739a9ff7 100644 --- a/src/lib/litegraph/src/widgets/SelectButtonWidget.ts +++ b/src/lib/litegraph/src/widgets/SelectButtonWidget.ts @@ -1,3 +1,5 @@ +import { t } from '@/i18n' + import type { ISelectButtonWidget } from '../types/widgets' import { BaseWidget } from './BaseWidget' import type { DrawWidgetOptions, WidgetEventOptions } from './BaseWidget' @@ -29,7 +31,7 @@ export class SelectButtonWidget ctx.textAlign = 'center' ctx.textBaseline = 'middle' - const text = 'SelectButton: Vue-only' + const text = `SelectButton: ${t('widgets.node2only')}` ctx.fillText(text, width / 2, y + height / 2) Object.assign(ctx, { diff --git a/src/lib/litegraph/src/widgets/TextareaWidget.ts b/src/lib/litegraph/src/widgets/TextareaWidget.ts index e93ee1c7e..c6b83e13f 100644 --- a/src/lib/litegraph/src/widgets/TextareaWidget.ts +++ b/src/lib/litegraph/src/widgets/TextareaWidget.ts @@ -3,8 +3,8 @@ import { BaseWidget } from './BaseWidget' import type { DrawWidgetOptions, WidgetEventOptions } from './BaseWidget' /** - * Widget for multi-line text input - * This is a widget that only has a Vue widgets implementation + * Widget for multi-line text input. + * This widget only has a Vue implementation. */ export class TextareaWidget extends BaseWidget @@ -13,35 +13,10 @@ export class TextareaWidget override type = 'textarea' as const drawWidget(ctx: CanvasRenderingContext2D, options: DrawWidgetOptions): void { - const { width } = options - const { y, height } = this - - const { fillStyle, strokeStyle, textAlign, textBaseline, font } = ctx - - ctx.fillStyle = this.background_color - ctx.fillRect(15, y, width - 30, height) - - ctx.strokeStyle = this.outline_color - ctx.strokeRect(15, y, width - 30, height) - - ctx.fillStyle = this.text_color - ctx.font = '11px monospace' - ctx.textAlign = 'center' - ctx.textBaseline = 'middle' - - const text = 'Textarea: Vue-only' - ctx.fillText(text, width / 2, y + height / 2) - - Object.assign(ctx, { - fillStyle, - strokeStyle, - textAlign, - textBaseline, - font - }) + this.drawVueOnlyWarning(ctx, options, 'Textarea') } onClick(_options: WidgetEventOptions): void { - // This is a widget that only has a Vue widgets implementation + // This widget only has a Vue implementation } } diff --git a/src/lib/litegraph/src/widgets/TreeSelectWidget.ts b/src/lib/litegraph/src/widgets/TreeSelectWidget.ts index 23ad440a3..78586cb6f 100644 --- a/src/lib/litegraph/src/widgets/TreeSelectWidget.ts +++ b/src/lib/litegraph/src/widgets/TreeSelectWidget.ts @@ -3,8 +3,8 @@ import { BaseWidget } from './BaseWidget' import type { DrawWidgetOptions, WidgetEventOptions } from './BaseWidget' /** - * Widget for hierarchical tree selection - * This is a widget that only has a Vue widgets implementation + * Widget for hierarchical tree selection. + * This widget only has a Vue implementation. */ export class TreeSelectWidget extends BaseWidget @@ -13,35 +13,10 @@ export class TreeSelectWidget override type = 'treeselect' as const drawWidget(ctx: CanvasRenderingContext2D, options: DrawWidgetOptions): void { - const { width } = options - const { y, height } = this - - const { fillStyle, strokeStyle, textAlign, textBaseline, font } = ctx - - ctx.fillStyle = this.background_color - ctx.fillRect(15, y, width - 30, height) - - ctx.strokeStyle = this.outline_color - ctx.strokeRect(15, y, width - 30, height) - - ctx.fillStyle = this.text_color - ctx.font = '11px monospace' - ctx.textAlign = 'center' - ctx.textBaseline = 'middle' - - const text = 'TreeSelect: Vue-only' - ctx.fillText(text, width / 2, y + height / 2) - - Object.assign(ctx, { - fillStyle, - strokeStyle, - textAlign, - textBaseline, - font - }) + this.drawVueOnlyWarning(ctx, options, 'TreeSelect') } onClick(_options: WidgetEventOptions): void { - // This is a widget that only has a Vue widgets implementation + // This widget only has a Vue implementation } } diff --git a/src/lib/litegraph/src/widgets/widgetMap.ts b/src/lib/litegraph/src/widgets/widgetMap.ts index 0e6a34fe5..37b906efb 100644 --- a/src/lib/litegraph/src/widgets/widgetMap.ts +++ b/src/lib/litegraph/src/widgets/widgetMap.ts @@ -11,6 +11,7 @@ import { toClass } from '@/lib/litegraph/src/utils/type' import { AssetWidget } from './AssetWidget' import { BaseWidget } from './BaseWidget' import { BooleanWidget } from './BooleanWidget' +import { BoundingBoxWidget } from './BoundingBoxWidget' import { ButtonWidget } from './ButtonWidget' import { ChartWidget } from './ChartWidget' import { ColorWidget } from './ColorWidget' @@ -18,6 +19,7 @@ import { ComboWidget } from './ComboWidget' import { FileUploadWidget } from './FileUploadWidget' import { GalleriaWidget } from './GalleriaWidget' import { ImageCompareWidget } from './ImageCompareWidget' +import { ImageCropWidget } from './ImageCropWidget' import { KnobWidget } from './KnobWidget' import { LegacyWidget } from './LegacyWidget' import { MarkdownWidget } from './MarkdownWidget' @@ -50,6 +52,8 @@ export type WidgetTypeMap = { selectbutton: SelectButtonWidget textarea: TextareaWidget asset: AssetWidget + imagecrop: ImageCropWidget + boundingbox: BoundingBoxWidget [key: string]: BaseWidget } @@ -120,6 +124,10 @@ export function toConcreteWidget( return toClass(TextareaWidget, narrowedWidget, node) case 'asset': return toClass(AssetWidget, narrowedWidget, node) + case 'imagecrop': + return toClass(ImageCropWidget, narrowedWidget, node) + case 'boundingbox': + return toClass(BoundingBoxWidget, narrowedWidget, node) default: { if (wrapLegacyWidgets) return toClass(LegacyWidget, widget, node) } diff --git a/src/locales/ar/commands.json b/src/locales/ar/commands.json index 79e3e9eba..1b342245e 100644 --- a/src/locales/ar/commands.json +++ b/src/locales/ar/commands.json @@ -1,4 +1,40 @@ { + "Comfy-Desktop_CheckForUpdates": { + "label": "التحقق من التحديثات" + }, + "Comfy-Desktop_Folders_OpenCustomNodesFolder": { + "label": "فتح مجلد العقد المخصصة" + }, + "Comfy-Desktop_Folders_OpenInputsFolder": { + "label": "فتح مجلد المدخلات" + }, + "Comfy-Desktop_Folders_OpenLogsFolder": { + "label": "فتح مجلد السجلات" + }, + "Comfy-Desktop_Folders_OpenModelConfig": { + "label": "فتح extra_model_paths.yaml" + }, + "Comfy-Desktop_Folders_OpenModelsFolder": { + "label": "فتح مجلد النماذج" + }, + "Comfy-Desktop_Folders_OpenOutputsFolder": { + "label": "فتح مجلد المخرجات" + }, + "Comfy-Desktop_OpenDevTools": { + "label": "فتح أدوات المطور" + }, + "Comfy-Desktop_OpenUserGuide": { + "label": "دليل مستخدم سطح المكتب" + }, + "Comfy-Desktop_Quit": { + "label": "خروج" + }, + "Comfy-Desktop_Reinstall": { + "label": "إعادة التثبيت" + }, + "Comfy-Desktop_Restart": { + "label": "إعادة التشغيل" + }, "Comfy_3DViewer_Open3DViewer": { "label": "فتح عارض ثلاثي الأبعاد (بيتا) للعقدة المحددة" }, @@ -155,18 +191,30 @@ "Comfy_Manager_ShowUpdateAvailablePacks": { "label": "التحقق من تحديثات العقد المخصصة" }, - "Comfy_Manager_ToggleManagerProgressDialog": { - "label": "تبديل شريط تقدم مدير العقد المخصصة" - }, "Comfy_MaskEditor_BrushSize_Decrease": { "label": "تقليل حجم الفرشاة في محرر القناع" }, "Comfy_MaskEditor_BrushSize_Increase": { "label": "زيادة حجم الفرشاة في محرر القناع" }, + "Comfy_MaskEditor_ColorPicker": { + "label": "فتح منتقي الألوان في محرر القناع" + }, + "Comfy_MaskEditor_Mirror_Horizontal": { + "label": "انعكاس أفقي في محرر القناع" + }, + "Comfy_MaskEditor_Mirror_Vertical": { + "label": "انعكاس عمودي في محرر القناع" + }, "Comfy_MaskEditor_OpenMaskEditor": { "label": "فتح محرر القناع للعقدة المحددة" }, + "Comfy_MaskEditor_Rotate_Left": { + "label": "تدوير لليسار في محرر القناع" + }, + "Comfy_MaskEditor_Rotate_Right": { + "label": "تدوير لليمين في محرر القناع" + }, "Comfy_Memory_UnloadModels": { "label": "تفريغ النماذج" }, @@ -197,12 +245,18 @@ "Comfy_QueueSelectedOutputNodes": { "label": "إدراج عقد الإخراج المحددة في قائمة الانتظار" }, + "Comfy_Queue_ToggleOverlay": { + "label": "تبديل سجل المهام" + }, "Comfy_Redo": { "label": "إعادة" }, "Comfy_RefreshNodeDefinitions": { "label": "تحديث تعريفات العقد" }, + "Comfy_RenameWorkflow": { + "label": "إعادة تسمية سير العمل" + }, "Comfy_SaveWorkflow": { "label": "حفظ سير العمل" }, @@ -221,6 +275,12 @@ "Comfy_ToggleHelpCenter": { "label": "مركز المساعدة" }, + "Comfy_ToggleLinear": { + "label": "تبديل الوضع الخطي" + }, + "Comfy_ToggleQPOV2": { + "label": "تبديل لوحة قائمة الانتظار V2" + }, "Comfy_ToggleTheme": { "label": "تبديل النمط (فاتح/داكن)" }, diff --git a/src/locales/ar/main.json b/src/locales/ar/main.json index c9a757323..8caf75166 100644 --- a/src/locales/ar/main.json +++ b/src/locales/ar/main.json @@ -1,6 +1,8 @@ { "actionbar": { - "dockToTop": "إلصق بالأعلى" + "dockToTop": "إلصق بالأعلى", + "feedback": "ملاحظات", + "feedbackTooltip": "إرسال ملاحظات" }, "apiNodesCostBreakdown": { "costPerRun": "التكلفة لكل تشغيل", @@ -18,23 +20,141 @@ "assetCard": "{name} - أصل {type}", "loadingAsset": "جاري تحميل الأصل" }, + "assetCollection": "مجموعة الأصول", "assets": "الأصول", "baseModels": "النماذج الأساسية", "browseAssets": "تصفح الأصول", + "byType": "حسب النوع", + "checkpoints": "نقاط التحقق", + "civitaiLinkExample": "{example} {link}", + "civitaiLinkExampleStrong": "مثال:", + "civitaiLinkExampleUrl": "https://civitai.com/models/10706/luisap-z-image-and-qwen-pixel-art-refiner?modelVersionId=2225295", + "civitaiLinkLabel": "رابط {download} نموذج Civitai", + "civitaiLinkLabelDownload": "تنزيل", + "civitaiLinkPlaceholder": "الصق الرابط هنا", + "confirmModelDetails": "تأكيد تفاصيل النموذج", "connectionError": "يرجى التحقق من اتصالك والمحاولة مرة أخرى", + "deletion": { + "body": "سيتم حذف هذا النموذج نهائيًا من مكتبتك.", + "complete": "تم حذف {assetName}.", + "failed": "تعذر حذف {assetName}.", + "header": "حذف هذا النموذج؟", + "inProgress": "جاري حذف {assetName}..." + }, + "download": { + "complete": "اكتمل التنزيل", + "failed": "فشل التنزيل", + "inProgress": "جاري تنزيل {assetName}..." + }, + "emptyImported": { + "canImport": "لا توجد نماذج مستوردة بعد. انقر على \"استيراد نموذج\" لإضافة نموذجك الخاص.", + "restricted": "النماذج الشخصية متاحة فقط لمستوى Creator وما فوق." + }, + "errorFileTooLarge": "الملف يتجاوز الحد الأقصى المسموح به للحجم", + "errorFormatNotAllowed": "يسمح فقط بصيغة SafeTensor", + "errorModelTypeNotSupported": "نوع النموذج هذا غير مدعوم", + "errorUnknown": "حدث خطأ غير متوقع", + "errorUnsafePickleScan": "اكتشف CivitAI رمزًا غير آمن محتملًا في هذا الملف", + "errorUnsafeVirusScan": "اكتشف CivitAI برمجيات خبيثة أو محتوى مشبوه في هذا الملف", + "errorUploadFailed": "فشل في استيراد الأصل. يرجى المحاولة مرة أخرى.", "failedToCreateNode": "فشل إنشاء العقدة. يرجى المحاولة مرة أخرى أو التحقق من وحدة التحكم للحصول على التفاصيل.", "fileFormats": "تنسيقات الملفات", + "fileName": "اسم الملف", + "fileSize": "حجم الملف", + "filterBy": "تصفية حسب", + "findInLibrary": "ابحث عنه في قسم {type} من مكتبة النماذج.", + "finish": "إنهاء", + "genericLinkPlaceholder": "الصق الرابط هنا", + "importAnother": "استيراد آخر", + "imported": "مستوردة", + "jobId": "معرّف المهمة", "loadingModels": "جارٍ تحميل {type}...", + "maxFileSize": "الحد الأقصى لحجم الملف: {size}", + "maxFileSizeValue": "1 جيجابايت", + "media": { + "audioPlaceholder": "صوت", + "threeDModelPlaceholder": "نموذج ثلاثي الأبعاد" + }, + "modelAssociatedWithLink": "النموذج المرتبط بالرابط الذي قدمته:", + "modelInfo": { + "addBaseModel": "أضف نموذجًا أساسيًا...", + "addTag": "أضف وسمًا...", + "additionalTags": "وسوم إضافية", + "baseModelUnknown": "النموذج الأساسي غير معروف", + "basicInfo": "معلومات أساسية", + "compatibleBaseModels": "نماذج أساسية متوافقة", + "description": "الوصف", + "descriptionNotSet": "لم يتم تعيين وصف", + "descriptionPlaceholder": "أضف وصفًا لهذا النموذج...", + "displayName": "اسم العرض", + "editDisplayName": "تعديل اسم العرض", + "fileName": "اسم الملف", + "modelDescription": "وصف النموذج", + "modelTagging": "تصنيف النموذج", + "modelType": "نوع النموذج", + "noAdditionalTags": "لا توجد وسوم إضافية", + "selectModelPrompt": "اختر نموذجًا لعرض معلوماته", + "selectModelType": "اختر نوع النموذج...", + "source": "المصدر", + "title": "معلومات النموذج", + "triggerPhrases": "عبارات التفعيل", + "viewOnSource": "عرض على {source}" + }, + "modelName": "اسم النموذج", + "modelNamePlaceholder": "أدخل اسمًا لهذا النموذج", + "modelTypeSelectorLabel": "ما نوع هذا النموذج؟", + "modelTypeSelectorPlaceholder": "اختر نوع النموذج", + "modelUploaded": "تم استيراد النموذج بنجاح.", "noAssetsFound": "لم يتم العثور على أصول", "noModelsInFolder": "لا توجد {type} متاحة في هذا المجلد", - "searchAssetsPlaceholder": "البحث في الأصول...", + "noValidSourceDetected": "لم يتم اكتشاف مصدر استيراد صالح", + "notSureLeaveAsIs": "لست متأكدًا؟ فقط اتركه كما هو", + "onlyCivitaiUrlsSupported": "يتم دعم روابط Civitai فقط", + "ownership": "الملكية", + "ownershipAll": "الكل", + "ownershipMyModels": "نماذجي", + "ownershipPublicModels": "نماذج عامة", + "processingModel": "بدأ التنزيل", + "processingModelDescription": "يمكنك إغلاق هذه النافذة. سيستمر التنزيل في الخلفية.", + "providerCivitai": "Civitai", + "providerHuggingFace": "Hugging Face", + "rename": { + "failed": "تعذر إعادة تسمية الأصل." + }, + "selectFrameworks": "اختر الأطر", + "selectModelType": "اختر نوع النموذج", + "selectProjects": "اختر المشاريع", "sortAZ": "أ-ي", "sortBy": "ترتيب حسب", "sortPopular": "الأكثر شعبية", "sortRecent": "الأحدث", "sortZA": "ي-أ", + "sortingType": "نوع الفرز", + "tags": "الوسوم", + "tagsHelp": "افصل بين الوسوم بفواصل", + "tagsPlaceholder": "مثال: نماذج، نقطة تحقق", "tryAdjustingFilters": "حاول تعديل البحث أو المرشحات", - "unknown": "غير معروف" + "unknown": "غير معروف", + "unsupportedUrlSource": "يتم دعم الروابط فقط من {sources}", + "upgradeFeatureDescription": "هذه الميزة متاحة فقط في خطط Creator أو Pro.", + "upgradeToUnlockFeature": "قم بالترقية لفتح هذه الميزة", + "upload": "استيراد", + "uploadFailed": "فشل الاستيراد", + "uploadModel": "استيراد", + "uploadModelDescription1": "الصق رابط تنزيل نموذج Civitai لإضافته إلى مكتبتك.", + "uploadModelDescription1Generic": "الصق رابط تنزيل النموذج لإضافته إلى مكتبتك.", + "uploadModelDescription2": "يتم دعم الروابط فقط من {link} في الوقت الحالي", + "uploadModelDescription2Generic": "يتم دعم الروابط فقط من المزودين التاليين:", + "uploadModelDescription2Link": "https://civitai.com/models", + "uploadModelDescription3": "الحد الأقصى لحجم الملف: {size}", + "uploadModelFailedToRetrieveMetadata": "فشل في جلب البيانات الوصفية. يرجى التحقق من الرابط والمحاولة مرة أخرى.", + "uploadModelFromCivitai": "استيراد نموذج من Civitai", + "uploadModelGeneric": "استيراد نموذج", + "uploadModelHelpFooterText": "تحتاج مساعدة في العثور على الروابط؟ انقر على أحد المزودين أدناه لمشاهدة فيديو تعليمي.", + "uploadModelHelpVideo": "فيديو مساعدة استيراد النموذج", + "uploadModelHowDoIFindThis": "كيف أجد هذا؟", + "uploadSuccess": "تم استيراد النموذج بنجاح!", + "uploadingModel": "جاري استيراد النموذج..." }, "auth": { "apiKey": { @@ -148,12 +268,19 @@ "title": "إنشاء حساب" } }, + "boundingBox": { + "height": "الارتفاع", + "width": "العرض", + "x": "س", + "y": "ص" + }, "breadcrumbsMenu": { "clearWorkflow": "مسح سير العمل", "deleteBlueprint": "حذف المخطط", "deleteWorkflow": "حذف سير العمل", "duplicate": "تكرار", - "enterNewName": "أدخل اسمًا جديدًا" + "enterNewName": "أدخل اسمًا جديدًا", + "missingNodesWarning": "يحتوي سير العمل على عقد غير مدعومة (مظللة باللون الأحمر)." }, "clipboard": { "errorMessage": "فشل النسخ إلى الحافظة", @@ -207,6 +334,7 @@ }, "retry": "حاول مرة أخرى", "retrying": "إعادة المحاولة...", + "skipToCloudApp": "تخطي إلى تطبيق السحابة", "start": { "desc": "لا حاجة لإعداد مسبق. يعمل على أي جهاز.", "download": "تنزيل ComfyUI", @@ -340,6 +468,8 @@ "Edit Subgraph Widgets": "تعديل عناصر الرسم البياني الفرعي", "Expand": "توسيع", "Expand Node": "توسيع العقدة", + "Extensions": "الإضافات", + "FavoriteWidget": "إضافة إلى المفضلة", "Horizontal": "أفقي", "Inputs": "المدخلات", "Left": "يسار", @@ -359,6 +489,7 @@ "Remove": "إزالة", "Remove Bypass": "إزالة التجاوز", "Rename": "إعادة تسمية", + "RenameWidget": "إعادة تسمية الأداة", "Resize": "تغيير الحجم", "Right": "يمين", "Run Branch": "تشغيل الفرع", @@ -369,6 +500,7 @@ "Shapes": "الأشكال", "Title": "العنوان", "Top": "أعلى", + "UnfavoriteWidget": "إزالة من المفضلة", "Unpack Subgraph": "فك الرسم البياني الفرعي", "Unpin": "إلغاء التثبيت", "Vertical": "عمودي", @@ -382,6 +514,7 @@ "additionalInfo": "معلومات إضافية", "apiPricing": "أسعار API", "credits": "الرصيد", + "creditsAvailable": "الرصيد المتاح", "details": "التفاصيل", "eventType": "نوع الحدث", "faqs": "الأسئلة المتكررة", @@ -390,15 +523,46 @@ "messageSupport": "مراسلة الدعم", "model": "النموذج", "purchaseCredits": "شراء رصيد", + "refreshes": "يتم التحديث في {date}", "time": "الوقت", "topUp": { + "addMoreCredits": "إضافة المزيد من الرصيد", + "addMoreCreditsToRun": "أضف المزيد من الرصيد للتشغيل", + "amountToPayLabel": "المبلغ المطلوب دفعه بالدولار", + "buy": "شراء", + "buyCredits": "المتابعة للدفع", "buyNow": "اشترِ الآن", + "contactUs": "اتصل بنا", + "creditsDescription": "يتم استخدام الرصيد لتشغيل سير العمل أو عقد الشركاء.", + "creditsPerDollar": "أرصدة لكل دولار", + "creditsToReceiveLabel": "الأرصدة المستلمة", + "howManyCredits": "كم رصيد ترغب في إضافته؟", "insufficientMessage": "ليس لديك رصيد كافٍ لتشغيل هذا الإجراء.", "insufficientTitle": "رصيد غير كافٍ", + "insufficientWorkflowMessage": "ليس لديك رصيد كافٍ لتشغيل هذا سير العمل.", + "maxAllowed": "الحد الأقصى {credits} أرصدة.", "maxAmount": "(الحد الأقصى 1000 دولار أمريكي)", + "maximumAmount": "الحد الأقصى ${amount}.", + "minRequired": "الحد الأدنى {credits} أرصدة", + "minimumPurchase": "الحد الأدنى ${amount} ({credits} أرصدة)", + "needMore": "تحتاج المزيد؟", + "purchaseError": "فشل الشراء", + "purchaseErrorDetail": "فشل شراء الرصيد: {error}", "quickPurchase": "شراء سريع", "seeDetails": "عرض التفاصيل", - "topUp": "شحن الرصيد" + "selectAmount": "اختر المبلغ", + "templateNote": "*تم الإنشاء باستخدام قالب Wan Fun Control", + "topUp": "شحن الرصيد", + "unknownError": "حدث خطأ غير معروف", + "usdAmount": "${amount} دولار أمريكي", + "videosEstimate": "~{count} فيديو", + "viewPricing": "عرض تفاصيل التسعير", + "youGet": "أرصدة", + "youPay": "المبلغ (دولار أمريكي)" + }, + "unified": { + "message": "تم توحيد الرصيد", + "tooltip": "لقد قمنا بتوحيد المدفوعات عبر Comfy. كل شيء يعمل الآن على رصيد Comfy:\n- عقد الشركاء (كانت تُسمى سابقًا عقد API)\n- سير العمل السحابي\n\nتم تحويل رصيد عقد الشركاء الحالي إلى رصيد." }, "yourCreditBalance": "رصيدك الحالي" }, @@ -414,6 +578,9 @@ "CLIP_VISION": "رؤية CLIP", "CLIP_VISION_OUTPUT": "خرج رؤية CLIP", "COMBO": "تركيب", + "COMFY_AUTOGROW_V3": "COMFY_AUTOGROW_V3", + "COMFY_DYNAMICCOMBO_V3": "COMFY_DYNAMICCOMBO_V3", + "COMFY_MATCHTYPE_V3": "COMFY_MATCHTYPE_V3", "CONDITIONING": "تكييف", "CONTROL_NET": "ControlNet", "FLOAT": "رقم عشري", @@ -424,18 +591,21 @@ "HOOKS": "معالجات", "HOOK_KEYFRAMES": "مفاتيح المعالجات", "IMAGE": "صورة", + "IMAGECOMPARE": "مقارنة الصور", "INT": "عدد صحيح", "LATENT": "كامِن", "LATENT_OPERATION": "عملية كامنة", + "LATENT_UPSCALE_MODEL": "نموذج تكبير latent", "LOAD3D_CAMERA": "كاميرا ثلاثية الأبعاد", "LOAD_3D": "تحميل ثلاثي الأبعاد", - "LOAD_3D_ANIMATION": "تحميل رسوم متحركة ثلاثية الأبعاد", "LORA_MODEL": "نموذج لورا", "LOSS_MAP": "خريطة الخسارة", "LUMA_CONCEPTS": "مفاهيم Luma", "LUMA_REF": "مرجع Luma", "MASK": "قناع", "MESH": "شبكة", + "MESHY_RIGGED_TASK_ID": "MESHY_RIGGED_TASK_ID", + "MESHY_TASK_ID": "MESHY_TASK_ID", "MODEL": "نموذج", "MODEL_PATCH": "ترقيع النموذج", "MODEL_TASK_ID": "معرّف مهمة النموذج", @@ -455,6 +625,7 @@ "STYLE_MODEL": "نموذج النمط", "SVG": "SVG", "TIMESTEPS_RANGE": "نطاق خطوات الزمن", + "TRACKS": "مسارات", "UPSCALE_MODEL": "نموذج التكبير", "VAE": "VAE", "VIDEO": "فيديو", @@ -523,14 +694,17 @@ "amount": "الكمية", "apply": "تطبيق", "architecture": "الهندسة المعمارية", + "asset": "{count} أصل | {count} أصل | {count} أصول", "audioFailedToLoad": "فشل تحميل الصوت", "audioProgress": "تقدم الصوت", "author": "المؤلف", "back": "رجوع", + "batchRename": "إعادة تسمية جماعية", "beta": "نسخة تجريبية", "bookmark": "حفظ في المكتبة", "calculatingDimensions": "جارٍ حساب الأبعاد", "cancel": "إلغاء", + "cancelled": "أُلغي", "capture": "التقاط", "category": "الفئة", "chart": "مخطط", @@ -540,6 +714,7 @@ "clearAll": "مسح الكل", "clearFilters": "مسح الفلاتر", "close": "إغلاق", + "closeDialog": "إغلاق الحوار", "color": "اللون", "comfy": "Comfy", "comfyOrgLogoAlt": "شعار ComfyOrg", @@ -556,13 +731,17 @@ "control_before_generate": "التحكم قبل التوليد", "copied": "تم النسخ", "copy": "نسخ", + "copyAll": "نسخ الكل", "copyJobId": "نسخ معرف المهمة", "copyToClipboard": "نسخ إلى الحافظة", "copyURL": "نسخ الرابط", + "core": "النواة", "currentUser": "المستخدم الحالي", + "custom": "مخصص", "customBackground": "خلفية مخصصة", "customize": "تخصيص", "customizeFolder": "تخصيص المجلد", + "decrement": "إنقاص", "defaultBanner": "لافتة افتراضية", "delete": "حذف", "deleteAudioFile": "حذف ملف الصوت", @@ -571,27 +750,35 @@ "description": "الوصف", "devices": "الأجهزة", "disableAll": "تعطيل الكل", + "disableSelected": "تعطيل المحدد", + "disableThirdParty": "تعطيل الطرف الثالث", "disabling": "جارٍ التعطيل", "dismiss": "تجاهل", "download": "تنزيل", "downloadImage": "تنزيل الصورة", "downloadVideo": "تنزيل الفيديو", + "downloading": "جارٍ التحميل", "dropYourFileOr": "أسقط ملفك أو", "duplicate": "تكرار", "edit": "تعديل", "editImage": "تحرير الصورة", "editOrMaskImage": "تعديل أو إخفاء الصورة", + "emDash": "—", "empty": "فارغ", "enableAll": "تمكين الكل", "enableOrDisablePack": "تفعيل أو تعطيل الحزمة", + "enableSelected": "تفعيل المحدد", "enabled": "ممكّن", "enabling": "جارٍ التمكين", + "enterBaseName": "أدخل الاسم الأساسي", + "enterNewName": "أدخل الاسم الجديد", "error": "خطأ", "errorLoadingImage": "حدث خطأ أثناء تحميل الصورة", "errorLoadingVideo": "حدث خطأ أثناء تحميل الفيديو", "experimental": "تجريبي", "export": "تصدير", "extensionName": "اسم الامتداد", + "failed": "فشل", "failedToCopyJobId": "فشل نسخ معرف المهمة", "failedToDownloadImage": "فشل في تنزيل الصورة", "failedToDownloadVideo": "فشل في تنزيل الفيديو", @@ -607,12 +794,15 @@ "goToNode": "الانتقال إلى العقدة", "graphNavigation": "التنقل في الرسم البياني", "halfSpeed": "0.5x", + "hideLeftPanel": "إخفاء اللوحة اليسرى", + "hideRightPanel": "إخفاء اللوحة اليمنى", "icon": "أيقونة", "imageFailedToLoad": "فشل تحميل الصورة", "imagePreview": "معاينة الصورة - استخدم مفاتيح الأسهم للتنقل بين الصور", "imageUrl": "رابط الصورة", "import": "استيراد", "inProgress": "جارٍ التنفيذ", + "increment": "زيادة", "info": "معلومات العقدة", "insert": "إدراج", "install": "تثبيت", @@ -620,7 +810,9 @@ "installing": "جارٍ التثبيت", "interrupted": "تمت المقاطعة", "itemSelected": "تم تحديد عنصر واحد", + "itemsCopiedToClipboard": "تم نسخ العناصر إلى الحافظة", "itemsSelected": "تم تحديد {selectedCount} عناصر", + "job": "مهمة", "jobIdCopied": "تم نسخ معرف المهمة إلى الحافظة", "keybinding": "اختصار لوحة المفاتيح", "keybindingAlreadyExists": "الاختصار موجود بالفعل في", @@ -638,14 +830,18 @@ "micPermissionDenied": "تم رفض إذن الميكروفون", "migrate": "ترحيل", "missing": "مفقود", + "more": "المزيد", "moreOptions": "خيارات إضافية", "moreWorkflows": "المزيد من سير العمل", "multiSelectDropdown": "قائمة منسدلة متعددة الاختيار", "name": "الاسم", "newFolder": "مجلد جديد", "next": "التالي", + "nightly": "NIGHTLY", "no": "لا", "noAudioRecorded": "لم يتم تسجيل أي صوت", + "noItems": "لا توجد عناصر", + "noResults": "لا توجد نتائج", "noResultsFound": "لم يتم العثور على نتائج", "noTasksFound": "لم يتم العثور على مهام", "noTasksFoundMessage": "لا توجد مهام في قائمة الانتظار.", @@ -656,26 +852,45 @@ "nodeSlotsError": "خطأ في فتحات العقدة", "nodeWidgetsError": "خطأ في عناصر واجهة العقدة", "nodes": "العُقَد", + "nodesCount": "{count} عقدة | {count} عقدة | {count} عقدة", "nodesRunning": "العُقَد قيد التشغيل", "none": "لا شيء", + "nothingToCopy": "لا يوجد ما يمكن نسخه", + "nothingToDelete": "لا يوجد ما يمكن حذفه", + "nothingToDuplicate": "لا يوجد ما يمكن نسخه", + "nothingToRename": "لا يوجد ما يمكن إعادة تسميته", "ok": "موافق", "openManager": "فتح المدير", "openNewIssue": "فتح مشكلة جديدة", + "or": "أو", "overwrite": "الكتابة فوق", + "playPause": "تشغيل/إيقاف مؤقت", "playRecording": "تشغيل التسجيل", "playbackSpeed": "سرعة التشغيل", "playing": "جاري التشغيل", "pressKeysForNewBinding": "اضغط على المفاتيح لربط جديد", "preview": "معاينة", + "profile": "الملف الشخصي", "progressCountOf": "من", + "queued": "في قائمة الانتظار", "ready": "جاهز", "reconnected": "تم الاتصال من جديد", "reconnecting": "إعادة الاتصال", "refresh": "تحديث", "refreshNode": "تحديث العقدة", + "relativeTime": { + "daysAgo": "منذ {count} يوم", + "hoursAgo": "منذ {count} ساعة", + "minutesAgo": "منذ {count} دقيقة", + "monthsAgo": "منذ {count} شهر", + "now": "الآن", + "weeksAgo": "منذ {count} أسبوع", + "yearsAgo": "منذ {count} سنة" + }, "releaseTitle": "إصدار {package} {version}", "reloadToApplyChanges": "أعد التحميل لتطبيق التغييرات", "removeImage": "إزالة الصورة", + "removeTag": "إزالة الوسم", "removeVideo": "إزالة الفيديو", "rename": "إعادة تسمية", "reportIssue": "إرسال تقرير", @@ -690,21 +905,31 @@ "resizeFromTopRight": "تغيير الحجم من الزاوية اليمنى العلوية", "restart": "إعادة التشغيل", "resultsCount": "تم العثور على {count} نتيجة", + "running": "يعمل", "save": "حفظ", "saving": "جارٍ الحفظ", + "scrollLeft": "التمرير لليسار", + "scrollRight": "التمرير لليمين", "search": "بحث", "searchExtensions": "بحث في الامتدادات", "searchFailedMessage": "لم نتمكن من العثور على أي إعدادات تطابق بحثك. حاول تعديل كلمات البحث.", "searchKeybindings": "بحث في اختصارات المفاتيح", "searchModels": "بحث في النماذج", "searchNodes": "بحث في العقد", + "searchPlaceholder": "بحث...", "searchSettings": "بحث في الإعدادات", "searchWorkflows": "بحث في سير العمل", "seeTutorial": "شاهد الدليل", + "selectItemsToCopy": "حدد العناصر للنسخ", + "selectItemsToDelete": "حدد العناصر للحذف", + "selectItemsToDuplicate": "حدد العناصر لعمل نسخة", + "selectItemsToRename": "حدد العناصر لإعادة التسمية", "selectedFile": "الملف المحدد", "setAsBackground": "تعيين كخلفية", "settings": "الإعدادات", + "showLeftPanel": "إظهار اللوحة اليسرى", "showReport": "عرض التقرير", + "showRightPanel": "إظهار اللوحة اليمنى", "singleSelectDropdown": "قائمة منسدلة اختيار واحد", "sort": "فرز", "source": "المصدر", @@ -712,12 +937,14 @@ "status": "الحالة", "stopPlayback": "إيقاف التشغيل", "stopRecording": "إيقاف التسجيل", + "submit": "إرسال", "success": "نجاح", "systemInfo": "معلومات النظام", "terminal": "الطرفية", "title": "العنوان", "triggerPhrase": "عبارة التشغيل", "unknownError": "خطأ غير معروف", + "untitled": "بدون عنوان", "update": "تحديث", "updateAvailable": "تحديث متاح", "updateFrontend": "تحديث الواجهة الأمامية", @@ -725,6 +952,7 @@ "updating": "جارٍ التحديث", "upload": "رفع", "usageHint": "تلميح الاستخدام", + "use": "استخدم", "user": "المستخدم", "versionMismatchWarning": "تحذير توافق الإصدارات", "versionMismatchWarningMessage": "{warning}: {detail} زر https://docs.comfy.org/installation/update_comfyui#common-update-issues للحصول على تعليمات التحديث.", @@ -732,11 +960,10 @@ "videoPreview": "معاينة الفيديو - استخدم مفاتيح الأسهم للتنقل بين الفيديوهات", "viewImageOfTotal": "عرض الصورة {index} من {total}", "viewVideoOfTotal": "عرض الفيديو {index} من {total}", - "vitePreloadErrorMessage": "تم إصدار نسخة جديدة من التطبيق. هل ترغب في إعادة التحميل؟\nإذا لم تفعل، قد لا تعمل بعض أجزاء التطبيق كما هو متوقع.\nيمكنك رفض وحفظ تقدمك قبل إعادة التحميل.", - "vitePreloadErrorTitle": "إصدار جديد متاح", "volume": "مستوى الصوت", "warning": "تحذير", - "workflow": "سير العمل" + "workflow": "سير العمل", + "you": "أنت" }, "graphCanvasMenu": { "fitView": "ملائمة العرض", @@ -758,12 +985,17 @@ "create": "إنشاء عقدة مجموعة", "enterName": "أدخل الاسم" }, + "help": { + "helpCenterMenu": "قائمة مركز المساعدة", + "recentReleases": "الإصدارات الأخيرة" + }, "helpCenter": { "clickToLearnMore": "اضغط لتعرف المزيد →", "desktopUserGuide": "دليل مستخدم سطح المكتب", "docs": "الوثائق", + "feedback": "إرسال ملاحظات", "github": "GitHub", - "helpFeedback": "المساعدة والتعليقات", + "help": "المساعدة والدعم", "loadingReleases": "جارٍ تحميل الإصدارات...", "managerExtension": "المدير الموسع", "more": "المزيد...", @@ -772,6 +1004,12 @@ "recentReleases": "الإصدارات الحديثة", "reinstall": "إعادة التثبيت", "updateAvailable": "تحديث", + "updateComfyUI": "تحديث ComfyUI", + "updateComfyUIFailed": "فشل تحديث ComfyUI. يرجى المحاولة مرة أخرى.", + "updateComfyUIStarted": "بدأ التحديث", + "updateComfyUIStartedDetail": "تمت إضافة تحديث ComfyUI إلى قائمة الانتظار. يرجى الانتظار...", + "updateComfyUISuccess": "اكتمل التحديث", + "updateComfyUISuccessDetail": "تم تحديث ComfyUI. جارٍ إعادة التشغيل...", "whatsNew": "ما الجديد؟" }, "icon": { @@ -785,6 +1023,18 @@ "inbox": "الوارد", "star": "نجمة" }, + "imageCompare": { + "noImages": "لا توجد صور للمقارنة" + }, + "imageCrop": { + "cropPreviewAlt": "معاينة الاقتصاص", + "loading": "جارٍ التحميل...", + "noInputImage": "لا توجد صورة إدخال متصلة" + }, + "importFailed": { + "copyError": "خطأ في النسخ", + "title": "فشل الاستيراد" + }, "install": { "appDataLocationTooltip": "دليل بيانات تطبيق ComfyUI. يحتوي على:\n- السجلات\n- إعدادات الخادم", "appPathLocationTooltip": "دليل أصول تطبيق ComfyUI. يحتوي على كود وأصول ComfyUI", @@ -798,6 +1048,8 @@ "failedToSelectDirectory": "فشل في اختيار الدليل", "gpu": "وحدة معالجة الرسومات (GPU)", "gpuPicker": { + "amdDescription": "استخدم بطاقة AMD GPU الخاصة بك مع تسريع ROCm™ لأفضل أداء.", + "amdSubtitle": "AMD ROCm™", "appleMetalDescription": "يستفيد من وحدة معالجة الرسومات في جهاز Mac الخاص بك لسرعة أكبر وتجربة أفضل بشكل عام", "cpuDescription": "استخدم وضع المعالج لضمان التوافق عندما لا تتوفر تسريع GPU", "cpuSubtitle": "وضع المعالج", @@ -824,6 +1076,8 @@ "selectGpuDescription": "اختر نوع وحدة معالجة الرسومات التي تملكها" }, "helpImprove": "يرجى المساعدة في تحسين ComfyUI", + "insideAppInstallDir": "هذا المجلد داخل حزمة تطبيق ComfyUI Desktop وسيتم حذفه أثناء التحديثات. اختر مجلدًا خارج مجلد التثبيت، مثل Documents/ComfyUI.", + "insideUpdaterCache": "هذا المجلد داخل ذاكرة التخزين المؤقت لمحدث ComfyUI، والتي يتم مسحها في كل تحديث. اختر موقعًا مختلفًا لبياناتك.", "installLocation": "موقع التثبيت", "installLocationDescription": "اختر الدليل الخاص ببيانات مستخدم ComfyUI. سيتم تثبيت بيئة بايثون في الموقع المحدد.", "installLocationTooltip": "دليل بيانات مستخدم ComfyUI. يحتوي على:\n- بيئة بايثون\n- النماذج\n- العقد المخصصة\n", @@ -898,6 +1152,16 @@ "issueReport": { "helpFix": "المساعدة في الإصلاح" }, + "linearMode": { + "beta": "تجريبي - أرسل ملاحظاتك", + "downloadAll": "تنزيل الكل", + "dragAndDropImage": "اسحب وأسقط صورة", + "graphMode": "وضع الرسم البياني", + "linearMode": "الوضع البسيط", + "rerun": "تشغيل مجدد", + "reuseParameters": "إعادة استخدام المعلمات", + "runCount": "عدد مرات التشغيل:" + }, "load3d": { "applyingTexture": "جارٍ تطبيق الخامة...", "backgroundColor": "لون الخلفية", @@ -924,20 +1188,24 @@ "lineart": "الرسم الخطي", "normal": "عادي", "original": "أصلي", + "pointCloud": "سحابة نقطية", "wireframe": "إطار سلكي" }, "model": "النموذج", "openIn3DViewer": "افتح في عارض ثلاثي الأبعاد", + "panoramaMode": "وضع البانوراما", "previewOutput": "معاينة المخرج", "reloadingModel": "جاري إعادة تحميل النموذج...", "removeBackgroundImage": "إزالة صورة الخلفية", "resizeNodeMatchOutput": "تغيير حجم العقدة لتتناسب مع المخرج", "scene": "المشهد", "showGrid": "عرض الشبكة", + "showSkeleton": "إظهار الهيكل العظمي", "startRecording": "بدء التسجيل", "stopRecording": "إيقاف التسجيل", "switchCamera": "تبديل الكاميرا", "switchingMaterialMode": "جارٍ تبديل وضع المادة...", + "tiledMode": "وضع التجانب", "unsupportedFileType": "نوع الملف غير مدعوم (يدعم .gltf و .glb و .obj و .fbx و .stl)", "upDirection": "اتجاه الأعلى", "upDirections": { @@ -958,6 +1226,11 @@ "title": "عارض ثلاثي الأبعاد (بيتا)" } }, + "loadWorkflowWarning": { + "coreNodesFromVersion": "العقد الأساسية من الإصدار {version}:", + "outdatedVersion": "تم إنشاء سير العمل هذا باستخدام إصدار أحدث من ComfyUI ({version}). قد لا تعمل بعض العقد بشكل صحيح.", + "outdatedVersionGeneric": "تم إنشاء سير العمل هذا باستخدام إصدار أحدث من ComfyUI. قد لا تعمل بعض العقد بشكل صحيح." + }, "maintenance": { "None": "لا شيء", "OK": "حسنًا", @@ -976,7 +1249,15 @@ "showManual": "عرض مهام الصيانة", "status": "الحالة", "terminalDefaultMessage": "عند تشغيل أمر استكشاف الأخطاء، سيتم عرض أي مخرجات هنا.", - "title": "الصيانة" + "title": "الصيانة", + "unsafeMigration": { + "action": "استخدم مهمة الصيانة \"المسار الأساسي\" أدناه لنقل ComfyUI إلى موقع آمن.", + "appInstallDir": "مسارك الأساسي داخل حزمة تطبيق ComfyUI Desktop. قد يتم حذف هذا المجلد أو الكتابة فوقه أثناء التحديثات. اختر دليلاً خارج مجلد التثبيت، مثل Documents/ComfyUI.", + "generic": "مسار ComfyUI الأساسي الحالي في موقع قد يتم حذفه أو تعديله أثناء التحديثات. لتجنب فقدان البيانات، انقله إلى مجلد آمن.", + "oneDrive": "مسارك الأساسي على OneDrive، مما قد يسبب مشاكل في المزامنة وفقدان البيانات عن طريق الخطأ. اختر مجلدًا محليًا غير مُدار بواسطة OneDrive.", + "title": "تم اكتشاف موقع تثبيت غير آمن", + "updaterCache": "مسارك الأساسي داخل ذاكرة التخزين المؤقت لمحدث ComfyUI، والتي يتم مسحها في كل تحديث. اختر موقعًا مختلفًا لبياناتك." + } }, "manager": { "allMissingNodesInstalled": "تم تثبيت جميع العقد المفقودة بنجاح", @@ -1077,6 +1358,8 @@ "totalNodes": "إجمالي العقد", "tryAgainLater": "يرجى المحاولة مرة أخرى لاحقاً.", "tryDifferentSearch": "يرجى تجربة استعلام بحث مختلف.", + "tryUpdate": "محاولة التحديث", + "tryUpdateTooltip": "اسحب آخر التغييرات من المستودع. قد تحتوي الإصدارات الليلية على تحديثات لا يمكن اكتشافها تلقائيًا.", "uninstall": "إلغاء التثبيت", "uninstallSelected": "إلغاء تثبيت المحدد", "uninstalling": "جاري إلغاء التثبيت", @@ -1087,31 +1370,110 @@ "version": "الإصدار" }, "maskEditor": { + "activateLayer": "تفعيل الطبقة", + "applyToWholeImage": "تطبيق على الصورة كاملة", + "baseImageLayer": "طبقة الصورة الأساسية", + "baseLayerPreview": "معاينة الطبقة الأساسية", + "black": "أسود", + "brushSettings": "إعدادات الفرشاة", + "brushShape": "شكل الفرشاة", + "clear": "مسح", + "clickToResetZoom": "انقر لإعادة تعيين التكبير", + "colorSelectSettings": "إعدادات اختيار اللون", + "colorSelector": "منتقي اللون", + "fillOpacity": "شفافية التعبئة", + "hardness": "الصلابة", + "imageLayer": "طبقة الصورة", + "invert": "عكس", + "layers": "الطبقات", + "livePreview": "معاينة مباشرة", + "maskBlendingOptions": "خيارات دمج القناع", + "maskLayer": "طبقة القناع", + "maskOpacity": "شفافية القناع", + "maskTolerance": "تسامح القناع", + "method": "طريقة", + "mirrorHorizontal": "انعكاس أفقي", + "mirrorVertical": "انعكاس عمودي", + "negative": "سلبي", + "opacity": "الشفافية", + "paintBucketSettings": "إعدادات دلو الطلاء", + "paintLayer": "طبقة الطلاء", + "redo": "إعادة", + "resetToDefault": "إعادة إلى الافتراضي", + "rotateLeft": "تدوير لليسار", + "rotateRight": "تدوير لليمين", + "selectionOpacity": "شفافية التحديد", + "smoothingPrecision": "دقة التنعيم", + "stepSize": "حجم الخطوة", + "stopAtMask": "توقف عند القناع", + "thickness": "السُمك", + "title": "محرر القناع", + "tolerance": "التسامح", + "undo": "تراجع", + "white": "أبيض" }, "mediaAsset": { + "actions": { + "copyJobId": "نسخ معرف المهمة", + "delete": "حذف", + "download": "تنزيل", + "exportWorkflow": "تصدير سير العمل", + "insertAsNodeInWorkflow": "إدراج كعقدة في سير العمل", + "inspect": "تفقد الأصل", + "more": "خيارات أخرى", + "moreOptions": "خيارات أخرى", + "openWorkflow": "فتح كسير عمل في علامة تبويب جديدة", + "seeMoreOutputs": "عرض المزيد من النتائج", + "zoom": "تكبير" + }, "assetDeletedSuccessfully": "تم حذف الأصل بنجاح", "deleteAssetDescription": "سيتم إزالة هذا الأصل بشكل دائم.", "deleteAssetTitle": "حذف هذا الأصل؟", "deleteSelectedDescription": "سيتم إزالة {count} أصل(أصول) بشكل دائم.", "deleteSelectedTitle": "حذف الأصول المحددة؟", "deletingImportedFilesCloudOnly": "حذف الملفات المستوردة مدعوم فقط في النسخة السحابية", + "failedToCreateNode": "فشل في إنشاء العقدة", "failedToDeleteAsset": "فشل في حذف الأصل", + "failedToExportWorkflow": "فشل في تصدير سير العمل", "jobIdToast": { "copied": "تم النسخ", "error": "خطأ", "jobIdCopied": "تم نسخ معرف المهمة إلى الحافظة", "jobIdCopyFailed": "فشل في نسخ معرف المهمة" }, + "noJobIdFound": "لم يتم العثور على معرف مهمة لهذا الأصل", + "noWorkflowDataFound": "لا توجد بيانات سير عمل في هذا الأصل", + "nodeAddedToWorkflow": "تمت إضافة عقدة {nodeType} إلى سير العمل", + "nodeTypeNotFound": "نوع العقدة {nodeType} غير موجود", "selection": { "assetsDeletedSuccessfully": "تم حذف {count} أصل(أصول) بنجاح", "deleteSelected": "حذف", + "deleteSelectedAll": "حذف الكل", "deselectAll": "إلغاء تحديد الكل", "downloadSelected": "تحميل", + "downloadSelectedAll": "تحميل الكل", "downloadStarted": "جاري تحميل {count} ملف(ملفات)...", "downloadsStarted": "بدأ تنزيل {count} ملف(ملفات)", + "exportWorkflowAll": "تصدير جميع سير العمل", + "failedToAddNodes": "فشل في إضافة العقد إلى سير العمل", "failedToDeleteAssets": "فشل في حذف الأصول المحددة", - "selectedCount": "الأصول المحددة: {count}" - } + "insertAllAssetsAsNodes": "إدراج جميع الأصول كعقد", + "multipleSelectedAssets": "تم تحديد عدة ملفات", + "noWorkflowsFound": "لم يتم العثور على بيانات سير العمل في الأصول المحددة", + "noWorkflowsToExport": "لم يتم العثور على بيانات سير العمل للتصدير", + "nodesAddedToWorkflow": "تمت إضافة {count} عقدة إلى سير العمل", + "openWorkflowAll": "فتح جميع سير العمل", + "partialAddNodesSuccess": "تمت إضافة {succeeded} بنجاح، وفشل {failed}", + "partialDeleteSuccess": "{succeeded} تم حذفها بنجاح، {failed} فشلت", + "partialWorkflowsExported": "تم تصدير {succeeded} بنجاح، وفشل {failed}", + "partialWorkflowsOpened": "تم فتح {succeeded} سير عمل، وفشل {failed}", + "selectedCount": "الأصول المحددة: {count}", + "workflowsExported": "تم تصدير {count} سير عمل بنجاح", + "workflowsOpened": "تم فتح {count} سير عمل في علامات تبويب جديدة" + }, + "unsupportedFileType": "نوع الملف غير مدعوم لعقدة التحميل", + "workflowExportedSuccessfully": "تم تصدير سير العمل بنجاح", + "workflowOpenedInNewTab": "تم فتح سير العمل في علامة تبويب جديدة" }, "menu": { "autoQueue": "الانتظار التلقائي", @@ -1119,11 +1481,13 @@ "batchCountTooltip": "عدد المرات التي يجب فيها وضع توليد سير العمل في قائمة الانتظار", "clear": "مسح سير العمل", "clipspace": "فتح Clipspace", + "customNodesManager": "مدير العقد المخصصة", "dark": "داكن", "disabled": "معطل", "disabledTooltip": "لن يتم وضع سير العمل في قائمة الانتظار تلقائيًا", "execute": "تنفيذ", "help": "مساعدة", + "helpAndFeedback": "المساعدة والتعليقات", "hideMenu": "إخفاء القائمة", "instant": "فوري", "instantTooltip": "سيتم وضع سير العمل في قائمة الانتظار فور انتهاء التوليد", @@ -1137,6 +1501,7 @@ "resetView": "إعادة تعيين عرض اللوحة", "run": "تشغيل", "runWorkflow": "تشغيل سير العمل (Shift للانتظار في البداية)", + "runWorkflowDisabled": "يحتوي سير العمل على عقد غير مدعومة (مظللة باللون الأحمر). يرجى إزالتها لتشغيل سير العمل.", "runWorkflowFront": "تشغيل سير العمل (انتظار في البداية)", "settings": "الإعدادات", "showMenu": "عرض القائمة", @@ -1152,6 +1517,7 @@ "Canvas Performance": "أداء اللوحة", "Canvas Toggle Lock": "تبديل قفل اللوحة", "Check for Custom Node Updates": "التحقق من تحديثات العقد المخصصة", + "Check for Updates": "التحقق من التحديثات", "Clear Pending Tasks": "مسح المهام المعلقة", "Clear Workflow": "مسح سير العمل", "Clipspace": "مساحة القص", @@ -1168,13 +1534,14 @@ "Custom Nodes Manager": "مدير العقد المخصصة", "Decrease Brush Size in MaskEditor": "تقليل حجم الفرشاة في محرر القناع", "Delete Selected Items": "حذف العناصر المحددة", + "Desktop User Guide": "دليل المستخدم لسطح المكتب", "Duplicate Current Workflow": "نسخ سير العمل الحالي", "Edit": "تحرير", "Edit Subgraph Widgets": "تحرير عناصر واجهة المستخدم للرسم البياني الفرعي", "Exit Subgraph": "الخروج من الرسم الفرعي", "Experimental: Browse Model Assets": "تجريبي: استعراض أصول النماذج", "Experimental: Enable AssetAPI": "تجريبي: تمكين AssetAPI", - "Experimental: Enable Vue Nodes": "تجريبي: تمكين عقد Vue", + "Experimental: Enable Nodes 2_0": "تجريبي: تفعيل Nodes 2.0", "Export": "تصدير", "Export (API)": "تصدير (API)", "File": "ملف", @@ -1186,12 +1553,15 @@ "Increase Brush Size in MaskEditor": "زيادة حجم الفرشاة في محرر القناع", "Install Missing Custom Nodes": "تثبيت العقد المخصصة المفقودة", "Interrupt": "إيقاف مؤقت", + "Job History": "سجل المهام", "Load Default Workflow": "تحميل سير العمل الافتراضي", "Lock Canvas": "قفل اللوحة", "Manage group nodes": "إدارة عقد المجموعة", "Manager": "المدير", "Manager Menu (Legacy)": "قائمة المدير (قديم)", "Minimap": "خريطة مصغرة", + "Mirror Horizontal in MaskEditor": "انعكاس أفقي في محرر القناع", + "Mirror Vertical in MaskEditor": "انعكاس عمودي في محرر القناع", "Model Library": "مكتبة النماذج", "Move Selected Nodes Down": "تحريك العقد المحددة للأسفل", "Move Selected Nodes Left": "تحريك العقد المحددة لليسار", @@ -1204,8 +1574,16 @@ "Node Links": "روابط العقد", "Open": "فتح", "Open 3D Viewer (Beta) for Selected Node": "فتح عارض ثلاثي الأبعاد (بيتا) للعقدة المحددة", + "Open Color Picker in MaskEditor": "فتح منتقي الألوان في محرر القناع", + "Open Custom Nodes Folder": "فتح مجلد العقد المخصصة", + "Open DevTools": "فتح أدوات المطور", + "Open Inputs Folder": "فتح مجلد المدخلات", + "Open Logs Folder": "فتح مجلد السجلات", "Open Mask Editor for Selected Node": "فتح محرر القناع للعقدة المحددة", + "Open Models Folder": "فتح مجلد النماذج", + "Open Outputs Folder": "فتح مجلد المخرجات", "Open Sign In Dialog": "فتح نافذة تسجيل الدخول", + "Open extra_model_paths_yaml": "فتح ملف extra_model_paths.yaml", "Pin/Unpin Selected Items": "تثبيت/إلغاء تثبيت العناصر المحددة", "Pin/Unpin Selected Nodes": "تثبيت/إلغاء تثبيت العقد المحددة", "Previous Opened Workflow": "سير العمل السابق المفتوح", @@ -1213,10 +1591,16 @@ "Queue Prompt": "قائمة انتظار التعليمات", "Queue Prompt (Front)": "قائمة انتظار التعليمات (أمامي)", "Queue Selected Output Nodes": "قائمة انتظار عقد المخرجات المحددة", + "Quit": "خروج", "Redo": "إعادة", "Refresh Node Definitions": "تحديث تعريفات العقد", + "Reinstall": "إعادة التثبيت", + "Rename": "إعادة التسمية", "Reset View": "إعادة تعيين العرض", "Resize Selected Nodes": "تغيير حجم العقد المحددة", + "Restart": "إعادة التشغيل", + "Rotate Left in MaskEditor": "تدوير لليسار في محرر القناع", + "Rotate Right in MaskEditor": "تدوير لليمين في محرر القناع", "Save": "حفظ", "Save As": "حفظ باسم", "Show Keybindings Dialog": "عرض مربع حوار اختصارات لوحة المفاتيح", @@ -1225,12 +1609,13 @@ "Sign Out": "تسجيل خروج", "Toggle Essential Bottom Panel": "تبديل لوحة العناصر الأساسية السفلية", "Toggle Logs Bottom Panel": "تبديل لوحة السجلات السفلية", + "Toggle Queue Panel V2": "تبديل لوحة قائمة الانتظار V2", "Toggle Search Box": "تبديل مربع البحث", + "Toggle Simple Mode": "تبديل وضع البسيط", "Toggle Terminal Bottom Panel": "تبديل لوحة الطرفية السفلية", "Toggle Theme (Dark/Light)": "تبديل السمة (داكن/فاتح)", "Toggle View Controls Bottom Panel": "تبديل لوحة عناصر التحكم في العرض السفلية", "Toggle promotion of hovered widget": "تبديل ترقية عنصر واجهة المستخدم المحدد", - "Toggle the Custom Nodes Manager Progress Bar": "تبديل شريط تقدم مدير العقد المخصصة", "Undo": "تراجع", "Ungroup selected group nodes": "فك تجميع عقد المجموعة المحددة", "Unload Models": "إلغاء تحميل النماذج", @@ -1255,30 +1640,56 @@ "missingModels": "نماذج مفقودة", "missingModelsMessage": "عند تحميل الرسم البياني، لم يتم العثور على النماذج التالية" }, + "missingNodes": { + "cloud": { + "description": "يستخدم سير العمل هذا عقد مخصصة غير مدعومة في إصدار السحابة بعد.", + "gotIt": "حسنًا، فهمت", + "learnMore": "اعرف المزيد", + "priorityMessage": "لقد قمنا تلقائيًا بوضع علامة على هذه العقد حتى نعطيها أولوية للإضافة.", + "replacementInstruction": "في الوقت الحالي، استبدل هذه العقد (المظللة باللون الأحمر على اللوحة) بعقد مدعومة إذا أمكن، أو جرب سير عمل مختلف.", + "title": "هذه العقد غير متوفرة على Comfy Cloud بعد" + }, + "oss": { + "description": "يستخدم سير العمل هذا عقد مخصصة لم تقم بتثبيتها بعد.", + "replacementInstruction": "قم بتثبيت هذه العقد لتشغيل سير العمل، أو استبدلها ببدائل مثبتة. العقد المفقودة مظللة باللون الأحمر على اللوحة.", + "title": "سير العمل هذا يحتوي على عقد مفقودة" + } + }, + "nightly": { + "badge": { + "label": "إصدار معاينة", + "tooltip": "أنت تستخدم إصدارًا ليليًا من ComfyUI. يرجى استخدام زر الملاحظات لمشاركة آرائك حول هذه الميزات." + } + }, "nodeCategories": { + "": "", "3d": "ثلاثي الأبعاد", "3d_models": "نماذج ثلاثية الأبعاد", "BFL": "BFL", + "Bria": "Bria", "ByteDance": "بايت دانس", "Gemini": "جيميني", "Ideogram": "إيديوغرام", "Kling": "Kling", "LTXV": "LTXV", "Luma": "Luma", + "Meshy": "Meshy", "MiniMax": "MiniMax", "Moonvalley Marey": "مون فالي ماري", "OpenAI": "OpenAI", - "Pika": "Pika", "PixVerse": "PixVerse", "Recraft": "Recraft", "Rodin": "رودان", "Runway": "رن واي", "Sora": "سورا", "Stability AI": "Stability AI", + "Tencent": "Tencent", + "Topaz": "Topaz", "Tripo": "تريبو", "Veo": "Veo", "Vidu": "فيدو", "Wan": "وان", + "WaveSpeed": "WaveSpeed", "_for_testing": "_للاختبار", "advanced": "متقدم", "animation": "الرسوم المتحركة", @@ -1299,6 +1710,7 @@ "controlnet": "كونترول نت", "create": "إنشاء", "custom_sampling": "تجميع مخصص", + "dataset": "مجموعة بيانات", "debug": "تصحيح", "deprecated": "مهمل", "edit_models": "تحرير النماذج", @@ -1310,8 +1722,10 @@ "image": "صورة", "inpaint": "التلوين الداخلي", "instructpix2pix": "instructpix2pix", + "kandinsky5": "kandinsky5", "latent": "كامِن", "loaders": "التحميلات", + "logic": "منطق", "lotus": "lotus", "ltxv": "ltxv", "mask": "قناع", @@ -1345,7 +1759,15 @@ "upscaling": "تكبير", "utils": "أدوات مساعدة", "video": "فيديو", - "video_models": "نماذج الفيديو" + "video_models": "نماذج الفيديو", + "zimage": "zimage" + }, + "nodeErrors": { + "content": "خطأ في محتوى العقدة", + "header": "خطأ في رأس العقدة", + "render": "خطأ في عرض العقدة", + "slots": "خطأ في فتحات العقدة", + "widgets": "خطأ في عناصر العقدة" }, "nodeHelpPage": { "documentationPage": "صفحة التوثيق", @@ -1362,6 +1784,7 @@ "notSupported": { "continue": "المتابعة", "continueTooltip": "أنا متأكد أن جهازي مدعوم", + "illustrationAlt": "رسم توضيحي لفتاة حزينة", "learnMore": "اعرف المزيد", "message": "الأجهزة المدعومة فقط هي:", "reportIssue": "أبلغ عن مشكلة", @@ -1371,12 +1794,136 @@ }, "title": "جهازك غير مدعوم" }, + "progressToast": { + "allDownloadsCompleted": "اكتملت جميع التنزيلات", + "downloadingModel": "جاري تنزيل النموذج...", + "downloadsFailed": "{count} فشل في التنزيل | {count} فشل في التنزيل | {count} فشل في التنزيل", + "failed": "فشل", + "filter": { + "all": "الكل", + "completed": "مكتمل", + "failed": "فشل" + }, + "finished": "انتهى", + "importingModels": "جاري استيراد النماذج", + "noImportsInQueue": "لا يوجد {filter} في قائمة الانتظار", + "pending": "قيد الانتظار", + "progressCount": "{completed} من {total}" + }, + "queue": { + "completedIn": "انتهى في {duration}", + "inQueue": "في قائمة الانتظار...", + "initializingAlmostReady": "جاري التهيئة - قريباً جاهز", + "jobAddedToQueue": "تمت إضافة المهمة إلى قائمة الانتظار", + "jobDetails": { + "computeHoursUsed": "ساعات الحوسبة المستخدمة", + "errorMessage": "رسالة الخطأ", + "estimatedFinishIn": "من المتوقع أن ينتهي خلال", + "estimatedStartIn": "من المتوقع أن يبدأ خلال", + "eta": { + "minutes": "~{count} دقيقة | ~{count} دقائق", + "minutesRange": "~{lo}-{hi} دقيقة", + "seconds": "~{count} ثانية | ~{count} ثوانٍ", + "secondsRange": "~{lo}-{hi} ثانية" + }, + "failedAfter": "فشل بعد", + "generatedOn": "تم التوليد في", + "header": "تفاصيل المهمة", + "jobId": "معرف المهمة", + "queuePosition": "موضع في قائمة الانتظار", + "queuePositionValue": "~{count} مهمة قبلك | ~{count} مهام قبلك", + "queuedAt": "تمت إضافتها في", + "report": "إبلاغ", + "timeElapsed": "الوقت المنقضي", + "totalGenerationTime": "إجمالي وقت التوليد", + "workflow": "مسار العمل" + }, + "jobHistory": "سجل المهام", + "jobList": { + "sortComputeHoursUsed": "ساعات الحوسبة المستخدمة (الأكثر أولاً)", + "sortMostRecent": "الأحدث", + "sortTotalGenerationTime": "إجمالي وقت التوليد (الأطول أولاً)", + "undated": "بدون تاريخ" + }, + "jobMenu": { + "addToCurrentWorkflow": "إضافة إلى مسار العمل الحالي", + "cancelJob": "إلغاء المهمة", + "copyErrorMessage": "نسخ رسالة الخطأ", + "copyJobId": "نسخ معرف المهمة", + "delete": "حذف", + "deleteAsset": "حذف الأصل", + "download": "تنزيل", + "exportWorkflow": "تصدير مسار العمل", + "inspectAsset": "فحص الأصل", + "openAsWorkflowNewTab": "فتح كمسار عمل في تبويب جديد", + "openWorkflowNewTab": "فتح مسار العمل في تبويب جديد", + "removeJob": "إزالة المهمة", + "reportError": "الإبلاغ عن خطأ" + }, + "toggleJobHistory": "تبديل سجل المهام" + }, "releaseToast": { + "description": "اطلع على أحدث التحسينات والميزات في هذا التحديث.", "newVersionAvailable": "الإصدار الجديد متوفر!", "skip": "تخطي", "update": "تحديث", "whatsNew": "ما الجديد؟" }, + "rightSidePanel": { + "addFavorite": "إضافة إلى المفضلة", + "advancedInputs": "مدخلات متقدمة", + "bypass": "تجاوز", + "color": "لون العقدة", + "fallbackGroupTitle": "مجموعة", + "fallbackNodeTitle": "عقدة", + "favorites": "المدخلات المفضلة", + "favoritesNone": "لا توجد مدخلات مفضلة", + "favoritesNoneDesc": "ستظهر المدخلات التي تضعها في المفضلة هنا", + "favoritesNoneTooltip": "قم بوضع نجمة على الأدوات للوصول السريع إليها دون اختيار العقد", + "globalSettings": { + "canvas": "اللوحة", + "connectionLinks": "روابط الاتصال", + "gridSpacing": "تباعد الشبكة", + "linkShape": "شكل الرابط", + "nodes": "العقد", + "nodes2": "العقد 2.0", + "searchPlaceholder": "بحث في الإعدادات السريعة...", + "showAdvanced": "إظهار المعلمات المتقدمة", + "showAdvancedTooltip": "هذا إعداد مهم، عند تفعيله، يعرض جميع المعلمات المتقدمة للعقد", + "showConnectedLinks": "إظهار الروابط المتصلة", + "showInfoBadges": "إظهار شارات المعلومات", + "showToolbox": "إظهار صندوق الأدوات عند التحديد", + "snapNodesToGrid": "محاذاة العقد مع الشبكة", + "title": "الإعدادات العامة", + "viewAllSettings": "عرض جميع الإعدادات" + }, + "groupSettings": "إعدادات المجموعة", + "groups": "المجموعات", + "hideAdvancedInputsButton": "إخفاء المدخلات المتقدمة", + "hideInput": "إخفاء المدخل", + "info": "معلومات", + "inputs": "المدخلات", + "inputsNone": "لا توجد مدخلات", + "inputsNoneTooltip": "العقدة ليس لديها مدخلات", + "locateNode": "تحديد موقع العقدة على اللوحة", + "mute": "كتم", + "noSelection": "حدد عقدة لعرض خصائصها ومعلوماتها.", + "nodeState": "حالة العقدة", + "nodes": "العقد", + "nodesNoneDesc": "لا توجد عقد", + "noneSearchDesc": "لا توجد عناصر مطابقة لبحثك", + "normal": "عادي", + "parameters": "المعلمات", + "pinned": "مثبت", + "properties": "الخصائص", + "removeFavorite": "إزالة من المفضلة", + "settings": "الإعدادات", + "showAdvancedInputsButton": "إظهار المدخلات المتقدمة", + "showInput": "إظهار المدخل", + "title": "لا توجد عقد محددة | عقدة واحدة محددة | {count} عقد محددة", + "togglePanel": "تبديل لوحة الخصائص", + "workflowOverview": "نظرة عامة على سير العمل" + }, "selectionToolbox": { "Bypass Group Nodes": "تجاوز عقد المجموعة", "Set Group Nodes to Always": "تعيين عقد المجموعة إلى دائمًا", @@ -1389,6 +1936,8 @@ "serverConfig": { "modifiedConfigs": "لقد قمت بتعديل إعدادات الخادم التالية. يرجى إعادة التشغيل لتطبيق التغييرات.", "restart": "إعادة التشغيل", + "restartRequiredToastDetail": "أعد تشغيل التطبيق لتطبيق تغييرات إعدادات الخادم.", + "restartRequiredToastSummary": "إعادة التشغيل مطلوبة", "revertChanges": "التراجع عن التغييرات" }, "serverConfigCategories": { @@ -1457,6 +2006,10 @@ "enable-cors-header": { "name": "تمكين ترويسة CORS: استخدم \"*\" لجميع النطاقات أو حدد نطاقًا" }, + "enable-manager-legacy-ui": { + "name": "استخدام واجهة مدير قديمة", + "tooltip": "يستخدم واجهة ComfyUI-Manager القديمة بدلاً من الواجهة الجديدة." + }, "fast": { "name": "تمكين بعض التحسينات غير المختبرة والتي قد تؤثر على الجودة." }, @@ -1558,6 +2111,7 @@ "CustomColorPalettes": "لوحات ألوان مخصصة", "DevMode": "وضع المطور", "EditTokenWeight": "تعديل وزن الرمز", + "Execution": "التنفيذ", "Extension": "الإضافة", "General": "عام", "Graph": "الرسم البياني", @@ -1572,12 +2126,14 @@ "Mask Editor": "محرر القناع", "Menu": "القائمة", "ModelLibrary": "مكتبة النماذج", - "NewEditor": "المحرر الجديد", "Node": "العقدة", "Node Search Box": "مربع بحث العقد", "Node Widget": "أداة العقدة", "NodeLibrary": "مكتبة العقد", + "Nodes 2_0": "Nodes 2.0", "Notification Preferences": "تفضيلات الإشعارات", + "Other": "أخرى", + "PLY": "PLY", "PlanCredits": "الخطة والاعتمادات", "Pointer": "المؤشر", "Queue": "قائمة الانتظار", @@ -1596,7 +2152,8 @@ "Vue Nodes": "عقد Vue", "VueNodes": "عقد Vue", "Window": "النافذة", - "Workflow": "سير العمل" + "Workflow": "سير العمل", + "Workspace": "مساحة العمل" }, "shape": { "CARD": "بطاقة", @@ -1622,11 +2179,14 @@ "viewControls": "عناصر تحكم العرض" }, "sideToolbar": { + "activeJobStatus": "المهمة النشطة: {status}", "assets": "الأصول", "backToAssets": "العودة إلى جميع الأصول", "browseTemplates": "تصفح القوالب المثال", "downloads": "التنزيلات", + "generatedAssetsHeader": "الأصول المُولدة", "helpCenter": "مركز المساعدة", + "importedAssetsHeader": "الأصول المستوردة", "labels": { "assets": "الأصول", "console": "وحدة التحكم", @@ -1669,6 +2229,47 @@ }, "openWorkflow": "فتح سير العمل من نظام الملفات المحلي", "queue": "قائمة الانتظار", + "queueProgressOverlay": { + "activeJobs": "{count} مهمة نشطة | {count} مهام نشطة", + "activeJobsShort": "{count} نشط | {count} نشط", + "activeJobsSuffix": "مهام نشطة", + "cancelJobTooltip": "إلغاء المهمة", + "clearHistory": "مسح سجل قائمة الانتظار", + "clearHistoryDialogAssetsNote": "لن يتم حذف الأصول التي تم إنشاؤها بواسطة هذه المهام ويمكنك دائمًا عرضها من لوحة الأصول.", + "clearHistoryDialogDescription": "سيتم حذف جميع المهام المنتهية أو الفاشلة أدناه من لوحة قائمة المهام.", + "clearHistoryDialogTitle": "مسح سجل قائمة المهام؟", + "clearQueueTooltip": "مسح القائمة", + "clearQueued": "مسح قائمة الانتظار", + "colonPercent": ": {percent}", + "currentNode": "العقدة الحالية:", + "expandCollapsedQueue": "توسيع قائمة المهام", + "filterAllWorkflows": "جميع سير العمل", + "filterBy": "تصفية حسب", + "filterCurrentWorkflow": "سير العمل الحالي", + "filterJobs": "تصفية المهام", + "interruptAll": "إيقاف جميع المهام الجارية", + "jobQueue": "قائمة المهام", + "jobsCompleted": "{count} مهمة مكتملة | {count} مهام مكتملة", + "jobsFailed": "{count} مهمة فشلت | {count} مهام فشلت", + "moreOptions": "خيارات إضافية", + "noActiveJobs": "لا توجد مهام نشطة", + "preview": "معاينة", + "queuedSuffix": "في الانتظار", + "running": "قيد التشغيل", + "showAssets": "عرض الأصول", + "showAssetsPanel": "عرض لوحة الأصول", + "sortBy": "ترتيب حسب", + "sortJobs": "ترتيب المهام", + "stubClipTextEncode": "CLIP ترميز النص:", + "title": "تقدم قائمة الانتظار", + "total": "الإجمالي: {percent}", + "viewAllJobs": "عرض جميع المهام", + "viewGrid": "عرض الشبكة", + "viewJobHistory": "عرض سجل المهام", + "viewList": "عرض القائمة" + }, + "searchAssets": "بحث في الأصول", + "sidebar": "الشريط الجانبي", "templates": "القوالب", "themeToggle": "تبديل المظهر", "workflowTab": { @@ -1711,24 +2312,58 @@ "subscription": { "addApiCredits": "إضافة رصيد API", "addCredits": "إضافة رصيد", + "addCreditsLabel": "أضف المزيد من الرصيد في أي وقت", "benefits": { "benefit1": "رصيد شهري للعقد الشريكة - تجديد عند الحاجة", "benefit2": "حتى 30 دقيقة وقت تشغيل لكل مهمة" }, "beta": "نسخة تجريبية", + "billedMonthly": "يتم الفوترة شهريًا", + "billedYearly": "{total} يتم الفوترة سنويًا", + "billingComingSoon": { + "message": "سيتم إطلاق الفوترة الجماعية قريباً. ستتمكن من الاشتراك في خطة لمساحة العمل الخاصة بك مع تسعير لكل مستخدم. ترقبوا التحديثات.", + "title": "قريباً" + }, + "cancelSubscription": "إلغاء الاشتراك", + "changeTo": "تغيير إلى {plan}", "comfyCloud": "Comfy Cloud", + "comfyCloudLogo": "شعار Comfy Cloud", + "contactOwnerToSubscribe": "يرجى التواصل مع مالك مساحة العمل للاشتراك", + "contactUs": "تواصل معنا", + "creditsRemainingThisMonth": "الرصيد المتبقي لهذا الشهر", + "creditsRemainingThisYear": "الرصيد المتبقي لهذا العام", + "creditsYouveAdded": "الرصيد الذي أضفته", + "currentPlan": "الخطة الحالية", + "customLoRAsLabel": "استيراد LoRAs الخاصة بك", + "description": "اختر الخطة الأنسب لك", "expiresDate": "ينتهي في {date}", + "gpuLabel": "RTX 6000 Pro (ذاكرة 96GB VRAM)", + "haveQuestions": "هل لديك أسئلة أو ترغب في معرفة المزيد عن المؤسسات؟", "invoiceHistory": "سجل الفواتير", "learnMore": "معرفة المزيد", + "managePayment": "إدارة الدفع", + "managePlan": "إدارة الخطة", "manageSubscription": "إدارة الاشتراك", + "maxDuration": { + "creator": "30 دقيقة", + "founder": "30 دقيقة", + "pro": "ساعة واحدة", + "standard": "30 دقيقة" + }, + "maxDurationLabel": "الحد الأقصى لمدة تشغيل كل سير عمل", "messageSupport": "مراسلة الدعم", + "monthly": "شهري", "monthlyBonusDescription": "مكافأة الرصيد الشهرية", + "monthlyCreditsInfo": "يتم تحديث هذا الرصيد شهريًا ولا ينتقل للشهر التالي", + "monthlyCreditsLabel": "الرصيد الشهري", "monthlyCreditsRollover": "سيتم ترحيل هذا الرصيد إلى الشهر التالي", + "mostPopular": "الأكثر شيوعًا", "nextBillingCycle": "دورة الفوترة التالية", "partnerNodesBalance": "رصيد \"عُقَد الشريك\"", "partnerNodesCredits": "رصيد العقد الشريكة", "partnerNodesDescription": "لتشغيل النماذج التجارية/المملوكة", "perMonth": "دولار أمريكي / شهر", + "plansAndPricing": "الخطط والأسعار", "prepaidCreditsInfo": "رصيد تم شراؤه بشكل منفصل ولا ينتهي صلاحيته", "prepaidDescription": "رصيد مسبق الدفع", "renewsDate": "تجديد في {date}", @@ -1738,14 +2373,46 @@ "waitingForSubscription": "أكمل اشتراكك في علامة التبويب الجديدة. سنكتشف تلقائيًا عند الانتهاء!" }, "subscribeNow": "اشترك الآن", + "subscribeTo": "اشترك في {plan}", "subscribeToComfyCloud": "الاشتراك في Comfy Cloud", "subscribeToRun": "اشتراك", "subscribeToRunFull": "الاشتراك للتشغيل", + "subscriptionRequiredMessage": "الاشتراك مطلوب للأعضاء لتشغيل سير العمل على السحابة", + "tierNameYearly": "{name} سنوي", + "tiers": { + "creator": { + "name": "المُبدع" + }, + "founder": { + "name": "إصدار المؤسس" + }, + "pro": { + "name": "احترافي" + }, + "standard": { + "name": "قياسي" + } + }, "title": "الاشتراك", "titleUnsubscribed": "اشترك في Comfy Cloud", "totalCredits": "إجمالي الرصيد", + "upgrade": "ترقية", + "upgradePlan": "ترقية الخطة", + "upgradeTo": "الترقية إلى {plan}", + "usdPerMonth": "دولار أمريكي / شهريًا", + "videoEstimateExplanation": "هذه التقديرات مبنية على قالب Wan 2.2 لتحويل الصورة إلى فيديو باستخدام الإعدادات الافتراضية (5 ثوانٍ، 640x640، 16 إطار/ثانية، 4 خطوات أخذ عينات).", + "videoEstimateHelp": "مزيد من التفاصيل حول هذا القالب", + "videoEstimateLabel": "العدد التقريبي لمقاطع الفيديو 5 ثوانٍ التي يتم إنشاؤها باستخدام قالب Wan 2.2 لتحويل الصورة إلى فيديو", + "videoEstimateTryTemplate": "جرّب هذا القالب", + "videoTemplateBasedCredits": "مقاطع الفيديو التي تم إنشاؤها باستخدام Wan 2.2 لتحويل الصورة إلى فيديو", + "viewEnterprise": "عرض المؤسسات", "viewMoreDetails": "عرض المزيد من التفاصيل", + "viewMoreDetailsPlans": "عرض المزيد من التفاصيل حول الخطط والأسعار", "viewUsageHistory": "عرض سجل الاستخدام", + "workspaceNotSubscribed": "هذه مساحة العمل ليست مشتركة", + "yearly": "سنوي", + "yearlyCreditsLabel": "إجمالي الرصيد السنوي", + "yearlyDiscount": "خصم 20%", "yourPlanIncludes": "خطتك تشمل:" }, "tabMenu": { @@ -1757,8 +2424,14 @@ "duplicateTab": "تكرار التبويب", "removeFromBookmarks": "إزالة من العلامات" }, + "templateWidgets": { + "sort": { + "searchPlaceholder": "بحث..." + } + }, "templateWorkflows": { "activeFilters": "المرشحات:", + "allTemplates": "جميع القوالب", "categories": "الفئات", "category": { "3D": "ثلاثي الأبعاد", @@ -1785,6 +2458,7 @@ "error": { "templateNotFound": "النموذج \"{templateName}\" غير موجود" }, + "licenseFilter": "الرخصة", "loading": "جارٍ تحميل القوالب...", "loadingMore": "تحميل المزيد من القوالب...", "modelFilter": "مرشح النماذج", @@ -1801,12 +2475,14 @@ "default": "الافتراضي", "modelSizeLowToHigh": "حجم النموذج (من الأقل إلى الأعلى)", "newest": "الأحدث", + "popular": "الأكثر شيوعًا", "recommended": "موصى به", "searchPlaceholder": "بحث...", "vramLowToHigh": "استخدام VRAM (من الأقل إلى الأعلى)" }, "sorting": "ترتيب حسب", "title": "ابدأ باستخدام قالب", + "useCaseFilter": "المهام", "useCasesSelected": "{count} حالات استخدام" }, "toastMessages": { @@ -1835,9 +2511,22 @@ "failedToLoadModel": "فشل في تحميل النموذج ثلاثي الأبعاد", "failedToPurchaseCredits": "فشل في شراء الرصيد: {error}", "failedToQueue": "فشل في الإضافة إلى قائمة الانتظار", + "failedToToggleCamera": "فشل في تبديل الكاميرا", + "failedToToggleGrid": "فشل في تبديل الشبكة", + "failedToUpdateBackgroundColor": "فشل في تحديث لون الخلفية", + "failedToUpdateBackgroundImage": "فشل في تحديث صورة الخلفية", + "failedToUpdateBackgroundRenderMode": "فشل في تحديث وضع عرض الخلفية إلى {mode}", + "failedToUpdateEdgeThreshold": "فشل في تحديث عتبة الحافة", + "failedToUpdateFOV": "فشل في تحديث مجال الرؤية", + "failedToUpdateLightIntensity": "فشل في تحديث شدة الإضاءة", + "failedToUpdateMaterialMode": "فشل في تحديث وضع المادة", + "failedToUpdateUpDirection": "فشل في تحديث اتجاه الأعلى", + "failedToUploadBackgroundImage": "فشل في رفع صورة الخلفية", "fileLoadError": "غير قادر على إيجاد سير العمل في {fileName}", + "fileTooLarge": "الملف كبير جدًا ({size} ميجابايت). الحد الأقصى المدعوم هو {maxSize} ميجابايت", "fileUploadFailed": "فشل رفع الملف", "interrupted": "تم إيقاف التنفيذ", + "legacyMaskEditorDeprecated": "محرر القناع القديم لم يعد مدعوماً وسيتم إزالته قريباً.", "migrateToLitegraphReroute": "سيتم إزالة عقد إعادة التوجيه في الإصدارات المستقبلية. انقر للترحيل إلى إعادة التوجيه الأصلية في Litegraph.", "modelLoadedSuccessfully": "تم تحميل النموذج ثلاثي الأبعاد بنجاح", "no3dScene": "لا يوجد مشهد ثلاثي الأبعاد لتطبيق الخامة", @@ -1864,12 +2553,14 @@ "selectUser": "اختر مستخدم" }, "userSettings": { + "accountSettings": "إعدادات الحساب", "email": "البريد الإلكتروني", "name": "الاسم", "notSet": "غير محدد", "provider": "مزود تسجيل الدخول", "title": "إعدادات المستخدم", - "updatePassword": "تحديث كلمة المرور" + "updatePassword": "تحديث كلمة المرور", + "workspaceSettings": "إعدادات مساحة العمل" }, "validation": { "descriptionRequired": "الوصف مطلوب", @@ -1898,22 +2589,32 @@ "updateFrontend": "تحديث الواجهة الأمامية" }, "vueNodesBanner": { - "message": "العُقد حصلت على مظهر جديد", + "desc": "– سير عمل أكثر مرونة، أدوات جديدة قوية، مصمم للتوسعة", + "title": "تقديم Nodes 2.0", "tryItOut": "جربه" }, "vueNodesMigration": { "button": "فتح الإعدادات", "message": "هل تفضل تصميم العُقد الكلاسيكي؟" }, + "vueNodesMigrationMainMenu": { + "message": "يمكنك العودة إلى Nodes 2.0 في أي وقت من القائمة الرئيسية." + }, "welcome": { "getStarted": "ابدأ الآن", "title": "مرحباً بك في ComfyUI" }, "whatsNewPopup": { + "later": "لاحقًا", "learnMore": "اعرف المزيد", "noReleaseNotes": "لا توجد ملاحظات إصدار متاحة." }, + "widgetFileUpload": { + "browseFiles": "تصفح الملفات", + "dropPrompt": "أسقط ملفك أو" + }, "widgets": { + "node2only": "فقط Node 2.0", "selectModel": "اختر نموذج", "uploadSelect": { "placeholder": "اختر...", @@ -1922,6 +2623,26 @@ "placeholderModel": "اختر نموذج...", "placeholderUnknown": "اختر وسائط...", "placeholderVideo": "اختر فيديو..." + }, + "valueControl": { + "decrement": "إنقاص القيمة", + "decrementDesc": "يطرح 1 من القيمة أو يختار الخيار السابق", + "editSettings": "تعديل إعدادات التحكم", + "fixed": "قيمة ثابتة", + "fixedDesc": "يترك القيمة بدون تغيير", + "header": { + "after": "بعد", + "before": "قبل", + "postfix": "تشغيل سير العمل:", + "prefix": "تحديث القيمة تلقائيًا" + }, + "increment": "زيادة القيمة", + "incrementDesc": "يضيف 1 إلى القيمة أو يختار الخيار التالي", + "linkToGlobal": "ربط بـ", + "linkToGlobalDesc": "قيمة فريدة مرتبطة بإعداد التحكم في القيمة العامة", + "linkToGlobalSeed": "القيمة العامة", + "randomize": "توليد قيمة عشوائية", + "randomizeDesc": "يتم خلط القيمة عشوائيًا بعد كل توليد" } }, "workflowService": { @@ -1929,6 +2650,146 @@ "exportWorkflow": "تصدير سير العمل", "saveWorkflow": "حفظ سير العمل" }, + "workspace": { + "addedToWorkspace": "تمت إضافتك إلى {workspaceName}", + "inviteAccepted": "تم قبول الدعوة", + "inviteFailed": "فشل في قبول الدعوة", + "unsavedChanges": { + "message": "لديك تغييرات غير محفوظة. هل تريد تجاهلها والانتقال إلى مساحة عمل أخرى؟", + "title": "تغييرات غير محفوظة" + } + }, + "workspaceAuth": { + "errors": { + "accessDenied": "ليس لديك صلاحية الوصول إلى هذه مساحة العمل", + "invalidFirebaseToken": "فشل التحقق من الهوية. يرجى محاولة تسجيل الدخول مرة أخرى.", + "notAuthenticated": "يجب تسجيل الدخول للوصول إلى مساحات العمل", + "tokenExchangeFailed": "فشل التحقق من مساحة العمل: {error}", + "workspaceNotFound": "لم يتم العثور على مساحة العمل" + } + }, + "workspacePanel": { + "createWorkspaceDialog": { + "create": "إنشاء", + "message": "تتيح مساحات العمل للأعضاء مشاركة رصيد واحد. ستصبح المالك بعد الإنشاء.", + "nameLabel": "اسم مساحة العمل*", + "namePlaceholder": "أدخل اسم مساحة العمل", + "title": "إنشاء مساحة عمل جديدة" + }, + "dashboard": { + "placeholder": "إعدادات مساحة عمل لوحة التحكم" + }, + "deleteDialog": { + "message": "سيتم فقدان أي أرصدة غير مستخدمة أو أصول غير محفوظة. لا يمكن التراجع عن هذا الإجراء.", + "messageWithName": "حذف \"{name}\"؟ سيتم فقدان أي أرصدة غير مستخدمة أو أصول غير محفوظة. لا يمكن التراجع عن هذا الإجراء.", + "title": "حذف هذه المساحة؟" + }, + "editWorkspaceDialog": { + "nameLabel": "اسم مساحة العمل", + "save": "حفظ", + "title": "تعديل تفاصيل مساحة العمل" + }, + "invite": "دعوة", + "inviteLimitReached": "لقد وصلت إلى الحد الأقصى وهو ٥٠ عضواً", + "inviteMember": "دعوة عضو", + "inviteMemberDialog": { + "createLink": "إنشاء الرابط", + "linkCopied": "تم النسخ", + "linkCopyFailed": "فشل في نسخ الرابط", + "linkStep": { + "copyLink": "نسخ الرابط", + "done": "تم", + "message": "تأكد من أن حسابه يستخدم هذا البريد الإلكتروني.", + "title": "أرسل هذا الرابط إلى الشخص" + }, + "message": "أنشئ رابط دعوة قابل للمشاركة لإرساله إلى شخص ما", + "placeholder": "أدخل بريد الشخص الإلكتروني", + "title": "دعوة شخص إلى هذه المساحة" + }, + "leaveDialog": { + "leave": "مغادرة", + "message": "لن تتمكن من الانضمام مرة أخرى إلا إذا تواصلت مع مالك مساحة العمل.", + "title": "مغادرة هذه المساحة؟" + }, + "members": { + "actions": { + "copyLink": "نسخ رابط الدعوة", + "removeMember": "إزالة العضو", + "revokeInvite": "إلغاء الدعوة" + }, + "columns": { + "expiryDate": "تاريخ الانتهاء", + "inviteDate": "تاريخ الدعوة", + "joinDate": "تاريخ الانضمام" + }, + "createNewWorkspace": "أنشئ واحدة جديدة.", + "membersCount": "{count}/٥٠ عضواً", + "noInvites": "لا توجد دعوات معلقة", + "noMembers": "لا يوجد أعضاء", + "pendingInvitesCount": "{count} دعوة معلقة | {count} دعوات معلقة", + "personalWorkspaceMessage": "لا يمكنك دعوة أعضاء آخرين إلى مساحة العمل الشخصية حالياً. لإضافة أعضاء إلى مساحة عمل،", + "tabs": { + "active": "نشط", + "pendingCount": "معلق ({count})" + } + }, + "menu": { + "deleteWorkspace": "حذف مساحة العمل", + "deleteWorkspaceDisabledTooltip": "يرجى إلغاء الاشتراك النشط لمساحة العمل أولاً", + "editWorkspace": "تعديل تفاصيل مساحة العمل", + "leaveWorkspace": "مغادرة مساحة العمل" + }, + "removeMemberDialog": { + "error": "فشل في إزالة العضو", + "message": "سيتم إزالة هذا العضو من مساحة العمل الخاصة بك. لن يتم استرداد الأرصدة التي استخدمها.", + "remove": "إزالة العضو", + "success": "تمت إزالة العضو", + "title": "إزالة هذا العضو؟" + }, + "revokeInviteDialog": { + "message": "لن يتمكن هذا العضو من الانضمام إلى مساحة العمل الخاصة بك بعد الآن. سيتم إبطال رابط الدعوة الخاص به.", + "revoke": "إلغاء الدعوة", + "title": "إلغاء دعوة هذا الشخص؟" + }, + "tabs": { + "dashboard": "لوحة التحكم", + "membersCount": "الأعضاء ({count})", + "planCredits": "الخطة والأرصدة" + }, + "toast": { + "failedToCreateWorkspace": "فشل في إنشاء مساحة العمل", + "failedToDeleteWorkspace": "فشل في حذف مساحة العمل", + "failedToFetchWorkspaces": "فشل في تحميل مساحات العمل", + "failedToLeaveWorkspace": "فشل في مغادرة مساحة العمل", + "failedToUpdateWorkspace": "فشل في تحديث مساحة العمل", + "workspaceCreated": { + "message": "اشترك في خطة، وادعُ زملاءك، وابدأ التعاون.", + "subscribe": "اشترك", + "title": "تم إنشاء مساحة العمل" + }, + "workspaceDeleted": { + "message": "تم حذف مساحة العمل نهائياً.", + "title": "تم حذف مساحة العمل" + }, + "workspaceLeft": { + "message": "لقد غادرت مساحة العمل.", + "title": "تمت مغادرة مساحة العمل" + }, + "workspaceUpdated": { + "message": "تم حفظ تفاصيل مساحة العمل.", + "title": "تم تحديث مساحة العمل" + } + } + }, + "workspaceSwitcher": { + "createWorkspace": "إنشاء مساحة عمل جديدة", + "maxWorkspacesReached": "يمكنك امتلاك ١٠ مساحات عمل فقط. احذف واحدة لإنشاء مساحة جديدة.", + "personal": "شخصي", + "roleMember": "عضو", + "roleOwner": "المالك", + "subscribe": "اشترك", + "switchWorkspace": "تبديل مساحة العمل" + }, "zoomControls": { "hideMinimap": "إخفاء الخريطة المصغرة", "label": "عناصر التحكم في التكبير", diff --git a/src/locales/ar/nodeDefs.json b/src/locales/ar/nodeDefs.json index a56db8f2c..8f146969d 100644 --- a/src/locales/ar/nodeDefs.json +++ b/src/locales/ar/nodeDefs.json @@ -39,6 +39,87 @@ "sigmas": { "name": "سيغما" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AddTextPrefix": { + "display_name": "إضافة بادئة نصية", + "inputs": { + "prefix": { + "name": "البادئة", + "tooltip": "البادئة التي ستتم إضافتها." + }, + "texts": { + "name": "النصوص", + "tooltip": "النص المراد معالجته." + } + }, + "outputs": { + "0": { + "name": "النصوص", + "tooltip": "النصوص المعالجة" + } + } + }, + "AddTextSuffix": { + "display_name": "إضافة لاحقة نصية", + "inputs": { + "suffix": { + "name": "اللاحقة", + "tooltip": "اللاحقة التي ستتم إضافتها." + }, + "texts": { + "name": "النصوص", + "tooltip": "النص المراد معالجته." + } + }, + "outputs": { + "0": { + "name": "النصوص", + "tooltip": "النصوص المعالجة" + } + } + }, + "AdjustBrightness": { + "display_name": "ضبط السطوع", + "inputs": { + "factor": { + "name": "عامل السطوع", + "tooltip": "عامل السطوع. 1.0 = بدون تغيير، <1.0 = أغمق، >1.0 = أكثر سطوعًا." + }, + "images": { + "name": "الصور", + "tooltip": "الصورة المراد معالجتها." + } + }, + "outputs": { + "0": { + "name": "الصور", + "tooltip": "الصور المعالجة" + } + } + }, + "AdjustContrast": { + "display_name": "ضبط التباين", + "inputs": { + "factor": { + "name": "عامل التباين", + "tooltip": "عامل التباين. 1.0 = بدون تغيير، <1.0 = تباين أقل، >1.0 = تباين أكثر." + }, + "images": { + "name": "الصور", + "tooltip": "الصورة المراد معالجتها." + } + }, + "outputs": { + "0": { + "name": "الصور", + "tooltip": "الصور المعالجة" + } } }, "AlignYourStepsScheduler": { @@ -70,6 +151,11 @@ "name": "مستوى الصوت", "tooltip": "ضبط مستوى الصوت بالديسيبل (dB). 0 = لا تغيير، +6 = مضاعفة، -6 = النصف، إلخ" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "AudioConcat": { @@ -86,6 +172,11 @@ "name": "الاتجاه", "tooltip": "ما إذا كان سيتم إلحاق الصوت2 بعد أو قبل الصوت1." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "AudioEncoderEncode": { @@ -131,6 +222,11 @@ "name": "طريقة الدمج", "tooltip": "الطريقة المستخدمة لدمج الموجات الصوتية." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BasicGuider": { @@ -142,6 +238,11 @@ "model": { "name": "النموذج" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BasicScheduler": { @@ -159,6 +260,50 @@ "steps": { "name": "الخطوات" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchImagesNode": { + "display_name": "تجميع الصور", + "inputs": { + "images": { + "name": "الصور" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchLatentsNode": { + "display_name": "تجميع الكامنات", + "inputs": { + "latents": { + "name": "الكامنات" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchMasksNode": { + "display_name": "تجميع الأقنعة", + "inputs": { + "masks": { + "name": "الأقنعة" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BetaSamplingScheduler": { @@ -176,6 +321,73 @@ "steps": { "name": "الخطوات" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BriaImageEditNode": { + "description": "حرر الصور باستخدام أحدث نموذج من Bria", + "display_name": "تحرير صورة Bria", + "inputs": { + "control_after_generate": { + "name": "التحكم بعد التوليد" + }, + "guidance_scale": { + "name": "مقياس التوجيه", + "tooltip": "القيمة الأعلى تجعل الصورة تتبع التوجيه بشكل أدق." + }, + "image": { + "name": "الصورة" + }, + "mask": { + "name": "القناع", + "tooltip": "إذا لم يتم تحديده، سيتم تطبيق التحرير على الصورة بالكامل." + }, + "model": { + "name": "النموذج" + }, + "moderation": { + "name": "الإشراف", + "tooltip": "إعدادات الإشراف" + }, + "moderation_prompt_content_moderation": { + "name": "إشراف محتوى التوجيه" + }, + "moderation_visual_input_moderation": { + "name": "إشراف الإدخال البصري" + }, + "moderation_visual_output_moderation": { + "name": "إشراف الإخراج البصري" + }, + "negative_prompt": { + "name": "توجيه سلبي" + }, + "prompt": { + "name": "التوجيه", + "tooltip": "تعليمات لتحرير الصورة" + }, + "seed": { + "name": "البذرة" + }, + "steps": { + "name": "الخطوات" + }, + "structured_prompt": { + "name": "توجيه منظم", + "tooltip": "سلسلة نصية تحتوي على توجيه التحرير المنظم بصيغة JSON. استخدم هذا بدلاً من التوجيه المعتاد للتحكم الدقيق والبرمجي." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "توجيه منظم", + "tooltip": null + } } }, "ByteDanceFirstLastFrameNode": { @@ -201,13 +413,16 @@ "name": "الإطار_الأول", "tooltip": "الإطار الأول الذي سيتم استخدامه للفيديو." }, + "generate_audio": { + "name": "توليد الصوت", + "tooltip": "يتم تجاهل هذا المعامل لجميع النماذج باستثناء seedance-1-5-pro." + }, "last_frame": { "name": "الإطار_الأخير", "tooltip": "الإطار الأخير الذي سيتم استخدامه للفيديو." }, "model": { - "name": "النموذج", - "tooltip": "اسم النموذج" + "name": "النموذج" }, "prompt": { "name": "المطالبة النصية", @@ -248,8 +463,7 @@ "tooltip": "الصورة الأساسية للتحرير" }, "model": { - "name": "النموذج", - "tooltip": "اسم النموذج" + "name": "النموذج" }, "prompt": { "name": "المطالبة النصية", @@ -286,8 +500,7 @@ "tooltip": "الارتفاع المخصص للصورة. القيمة تعمل فقط إذا تم ضبط `size_preset` على `Custom`" }, "model": { - "name": "النموذج", - "tooltip": "اسم النموذج" + "name": "النموذج" }, "prompt": { "name": "النص الموجه", @@ -336,8 +549,7 @@ "tooltip": "من صورة إلى أربع صور." }, "model": { - "name": "النموذج", - "tooltip": "اسم النموذج" + "name": "النموذج" }, "prompt": { "name": "prompt", @@ -381,13 +593,16 @@ "name": "duration", "tooltip": "مدة الفيديو الناتج بالثواني." }, + "generate_audio": { + "name": "توليد الصوت", + "tooltip": "يتم تجاهل هذا المعامل لجميع النماذج باستثناء seedance-1-5-pro." + }, "image": { "name": "image", "tooltip": "الإطار الأول الذي سيتم استخدامه للفيديو." }, "model": { - "name": "model", - "tooltip": "اسم النموذج" + "name": "model" }, "prompt": { "name": "prompt", @@ -489,9 +704,12 @@ "name": "المدة", "tooltip": "مدة الفيديو الناتج بالثواني." }, + "generate_audio": { + "name": "توليد الصوت", + "tooltip": "يتم تجاهل هذا المعامل لجميع النماذج باستثناء seedance-1-5-pro." + }, "model": { - "name": "النموذج", - "tooltip": "اسم النموذج" + "name": "النموذج" }, "prompt": { "name": "النص الموجه", @@ -531,6 +749,11 @@ "positive": { "name": "إيجابي" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "CFGNorm": { @@ -769,6 +992,25 @@ } } }, + "CLIPTextEncodeKandinsky5": { + "display_name": "CLIPTextEncodeKandinsky5", + "inputs": { + "clip": { + "name": "clip" + }, + "clip_l": { + "name": "clip_l" + }, + "qwen25_7b": { + "name": "qwen25_7b" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "CLIPTextEncodeLumina2": { "description": "يقوم بترميز أمر النظام وأمر المستخدم باستخدام نموذج CLIP إلى تمثيل مضمَّن يمكن استخدامه لتوجيه نموذج الانتشار نحو إنشاء صور محددة.", "display_name": "ترميز نص CLIP لـ Lumina2", @@ -959,6 +1201,29 @@ } } }, + "CenterCropImages": { + "display_name": "قص الصور من المنتصف", + "inputs": { + "height": { + "name": "الارتفاع", + "tooltip": "ارتفاع القص." + }, + "images": { + "name": "الصور", + "tooltip": "الصورة المراد معالجتها." + }, + "width": { + "name": "العرض", + "tooltip": "عرض القص." + } + }, + "outputs": { + "0": { + "name": "الصور", + "tooltip": "الصور المعالجة" + } + } + }, "CheckpointLoader": { "display_name": "تحميل نقطة التحقق مع الإعدادات (متوقف)", "inputs": { @@ -1095,6 +1360,26 @@ } } }, + "ComfySwitchNode": { + "display_name": "مفتاح التحويل", + "inputs": { + "on_false": { + "name": "عند التعطيل" + }, + "on_true": { + "name": "عند التفعيل" + }, + "switch": { + "name": "مفتاح التحويل" + } + }, + "outputs": { + "0": { + "name": "المخرجات", + "tooltip": null + } + } + }, "ConditioningAverage": { "display_name": "متوسط التهيئة", "inputs": { @@ -1327,14 +1612,14 @@ "name": "إجمالي الثواني" } }, - "outputs": { - "0": { - "name": "إيجابي" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "سلبي" + { + "tooltip": null } - } + ] }, "ConditioningTimestepsRange": { "display_name": "نطاق الخطوات الزمنية", @@ -1391,6 +1676,10 @@ "name": "dim", "tooltip": "البعد المراد تطبيق نوافذ السياق عليه." }, + "freenoise": { + "name": "ضجيج حر", + "tooltip": "ما إذا كان سيتم تطبيق خلط ضجيج FreeNoise، يحسن دمج النوافذ." + }, "fuse_method": { "name": "fuse_method", "tooltip": "الطريقة المستخدمة لدمج نوافذ السياق." @@ -1791,8 +2080,32 @@ "y": { "name": "ص" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, + "CustomCombo": { + "display_name": "توليفة مخصصة", + "inputs": { + "choice": { + "name": "اختيار" + }, + "index": { + }, + "option1": { + } + }, + "outputs": [ + null, + { + "name": "الفهرس", + "tooltip": null + } + ] + }, "DiffControlNetLoader": { "display_name": "تحميل نموذج ControlNet (فرق)", "inputs": { @@ -1829,7 +2142,12 @@ } }, "DisableNoise": { - "display_name": "تعطيل الضجيج" + "display_name": "تعطيل الضجيج", + "outputs": { + "0": { + "tooltip": null + } + } }, "DualCFGGuider": { "display_name": "موجّه CFG مزدوج", @@ -1855,6 +2173,11 @@ "style": { "name": "النمط" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "DualCLIPLoader": { @@ -1938,6 +2261,11 @@ "name": "معدل العينات", "tooltip": "معدل العينات لمقطع الصوت الفارغ." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyChromaRadianceLatentImage": { @@ -1981,6 +2309,25 @@ } } }, + "EmptyFlux2LatentImage": { + "display_name": "صورة كامنة فارغة من Flux 2", + "inputs": { + "batch_size": { + "name": "حجم الدفعة" + }, + "height": { + "name": "الارتفاع" + }, + "width": { + "name": "العرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptyHunyuanImageLatent": { "display_name": "EmptyHunyuanImageLatent", "inputs": { @@ -2022,6 +2369,28 @@ } } }, + "EmptyHunyuanVideo15Latent": { + "display_name": "فيديو Hunyuan 1.5 كامن فارغ", + "inputs": { + "batch_size": { + "name": "حجم الدفعة" + }, + "height": { + "name": "الارتفاع" + }, + "length": { + "name": "الطول" + }, + "width": { + "name": "العرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptyImage": { "display_name": "صورة فارغة", "inputs": { @@ -2071,6 +2440,11 @@ "seconds": { "name": "الثواني" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyLatentHunyuan3Dv2": { @@ -2083,6 +2457,11 @@ "resolution": { "name": "الدقة" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyLatentImage": { @@ -2130,6 +2509,28 @@ } } }, + "EmptyQwenImageLayeredLatentImage": { + "display_name": "صورة Qwen الطبقية الكامنة الفارغة", + "inputs": { + "batch_size": { + "name": "حجم الدفعة" + }, + "height": { + "name": "الارتفاع" + }, + "layers": { + "name": "الطبقات" + }, + "width": { + "name": "العرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptySD3LatentImage": { "display_name": "صورة SD3 كامنة فارغة", "inputs": { @@ -2177,6 +2578,11 @@ "steps": { "name": "الخطوات" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ExtendIntermediateSigmas": { @@ -2197,6 +2603,11 @@ "steps": { "name": "الخطوات" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FeatherMask": { @@ -2217,6 +2628,11 @@ "top": { "name": "الأعلى" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FlipSigmas": { @@ -2225,6 +2641,102 @@ "sigmas": { "name": "قيم_السيغما" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2MaxImageNode": { + "description": "ينشئ الصور بشكل متزامن بناءً على النص والوَضوح.", + "display_name": "Flux.2 [max] صورة", + "inputs": { + "control_after_generate": { + "name": "التحكم بعد الإنشاء" + }, + "height": { + "name": "الارتفاع" + }, + "images": { + "name": "الصور", + "tooltip": "حتى 9 صور يمكن استخدامها كمراجع." + }, + "prompt": { + "name": "النص", + "tooltip": "النص المستخدم لإنشاء أو تعديل الصورة" + }, + "prompt_upsampling": { + "name": "رفع دقة النص", + "tooltip": "هل يتم رفع دقة النص؟ إذا كان نشطًا، سيتم تعديل النص تلقائيًا لإنتاج صور أكثر إبداعًا." + }, + "seed": { + "name": "البذرة", + "tooltip": "البذرة العشوائية المستخدمة لإنشاء الضوضاء." + }, + "width": { + "name": "العرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2ProImageNode": { + "description": "ينشئ الصور بشكل متزامن بناءً على النص والوَضوح.", + "display_name": "Flux.2 [pro] صورة", + "inputs": { + "control_after_generate": { + "name": "التحكم بعد الإنشاء" + }, + "height": { + "name": "الارتفاع" + }, + "images": { + "name": "الصور", + "tooltip": "حتى 9 صور يمكن استخدامها كمراجع." + }, + "prompt": { + "name": "النص", + "tooltip": "النص المستخدم لإنشاء أو تعديل الصورة" + }, + "prompt_upsampling": { + "name": "رفع دقة النص", + "tooltip": "هل يتم رفع دقة النص؟ إذا كان نشطًا، سيتم تعديل النص تلقائيًا لإنتاج صور أكثر إبداعًا." + }, + "seed": { + "name": "البذرة", + "tooltip": "البذرة العشوائية المستخدمة لإنشاء الضوضاء." + }, + "width": { + "name": "العرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2Scheduler": { + "display_name": "Flux2Scheduler", + "inputs": { + "height": { + "name": "الارتفاع" + }, + "steps": { + "name": "الخطوات" + }, + "width": { + "name": "العرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FluxDisableGuidance": { @@ -2547,6 +3059,11 @@ "s2": { "name": "s2" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FreeU_V2": { @@ -2567,6 +3084,11 @@ "s2": { "name": "s2" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "GITSScheduler": { @@ -2625,6 +3147,58 @@ } } }, + "GeminiImage2Node": { + "description": "توليد أو تعديل الصور بشكل متزامن عبر Google Vertex API.", + "display_name": "Nano Banana Pro (Google Gemini Image)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "إذا تم تعيينها إلى 'auto'، ستطابق نسبة أبعاد الصورة المدخلة؛ إذا لم يتم توفير صورة، يتم عادةً توليد صورة مربعة بنسبة 16:9." + }, + "control_after_generate": { + "name": "control after generate" + }, + "files": { + "name": "files", + "tooltip": "ملف (ملفات) اختيارية لاستخدامها كسياق للنموذج. يقبل مدخلات من عقدة Gemini Generate Content Input Files." + }, + "images": { + "name": "images", + "tooltip": "صورة (صور) مرجعية اختيارية. لإضافة عدة صور، استخدم عقدة Batch Images (حتى 14 صورة)." + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "وصف نصي للصورة المراد توليدها أو التعديلات المطلوب تطبيقها. أضف أي قيود أو أنماط أو تفاصيل يجب على النموذج اتباعها." + }, + "resolution": { + "name": "resolution", + "tooltip": "دقة الإخراج المستهدفة. بالنسبة لـ 2K/4K يتم استخدام أداة التكبير الأصلية لـ Gemini." + }, + "response_modalities": { + "name": "response_modalities", + "tooltip": "اختر 'IMAGE' لإخراج صورة فقط، أو 'IMAGE+TEXT' لإرجاع كل من الصورة المولدة واستجابة نصية." + }, + "seed": { + "name": "seed", + "tooltip": "عند تثبيت قيمة البذرة، يحاول النموذج تقديم نفس الاستجابة للطلبات المتكررة. لا يتم ضمان إخراج حتمي. أيضًا، تغيير النموذج أو إعدادات المعلمات مثل درجة الحرارة قد يؤدي إلى اختلافات في الاستجابة حتى عند استخدام نفس قيمة البذرة. بشكل افتراضي، يتم استخدام قيمة بذرة عشوائية." + }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "تعليمات أساسية تحدد سلوك الذكاء الاصطناعي." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "tooltip": null + } + } + }, "GeminiImageNode": { "description": "تحرير الصور بشكل متزامن عبر واجهة برمجة تطبيقات Google.", "display_name": "صورة Google Gemini", @@ -2652,9 +3226,17 @@ "name": "النص الموجه", "tooltip": "النص الموجه للتوليد" }, + "response_modalities": { + "name": "response_modalities", + "tooltip": "اختر 'IMAGE' لإخراج صورة فقط، أو 'IMAGE+TEXT' لإرجاع كل من الصورة المولدة واستجابة نصية." + }, "seed": { "name": "البذرة", "tooltip": "عند تثبيت البذرة على قيمة محددة، يبذل النموذج قصارى جهده لتقديم نفس الاستجابة للطلبات المتكررة. لا يتم ضمان الإخراج الحتمي. أيضًا، تغيير النموذج أو إعدادات المعاملات، مثل درجة الحرارة، يمكن أن يسبب اختلافات في الاستجابة حتى عند استخدام نفس قيمة البذرة. افتراضيًا، يتم استخدام قيمة بذرة عشوائية." + }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "تعليمات أساسية تحدد سلوك الذكاء الاصطناعي." } }, "outputs": { @@ -2716,6 +3298,10 @@ "name": "بذرة", "tooltip": "عند تثبيت البذرة على قيمة محددة، يبذل النموذج قصارى جهده لتقديم نفس الاستجابة للطلبات المتكررة. لا يتم ضمان الإخراج الحتمي. أيضًا، تغيير النموذج أو إعدادات المعاملات، مثل درجة الحرارة، يمكن أن يسبب اختلافات في الاستجابة حتى عند استخدام نفس قيمة البذرة. افتراضيًا، تُستخدم قيمة بذرة عشوائية." }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "تعليمات أساسية تحدد سلوك الذكاء الاصطناعي." + }, "video": { "name": "فيديو", "tooltip": "فيديو اختياري لاستخدامه كسياق للنموذج." @@ -2727,6 +3313,72 @@ } } }, + "GenerateTracks": { + "display_name": "توليد المسارات", + "inputs": { + "bezier": { + "name": "منحنى بيزيه", + "tooltip": "تفعيل مسار منحنى بيزيه باستخدام نقطة المنتصف كنقطة تحكم." + }, + "end_x": { + "name": "إحداثي X للنهاية", + "tooltip": "إحداثي X مُطَبَّع (0-1) لموضع النهاية." + }, + "end_y": { + "name": "إحداثي Y للنهاية", + "tooltip": "إحداثي Y مُطَبَّع (0-1) لموضع النهاية." + }, + "height": { + "name": "الارتفاع" + }, + "interpolation": { + "name": "الاستيفاء", + "tooltip": "يتحكم في توقيت/سرعة الحركة على طول المسار." + }, + "mid_x": { + "name": "إحداثي X للمنتصف", + "tooltip": "نقطة تحكم X مُطَبَّعة لمنحنى بيزيه. تُستخدم فقط عند تفعيل 'منحنى بيزيه'." + }, + "mid_y": { + "name": "إحداثي Y للمنتصف", + "tooltip": "نقطة تحكم Y مُطَبَّعة لمنحنى بيزيه. تُستخدم فقط عند تفعيل 'منحنى بيزيه'." + }, + "num_frames": { + "name": "عدد الإطارات" + }, + "num_tracks": { + "name": "عدد المسارات" + }, + "start_x": { + "name": "إحداثي X للبداية", + "tooltip": "إحداثي X مُطَبَّع (0-1) لموضع البداية." + }, + "start_y": { + "name": "إحداثي Y للبداية", + "tooltip": "إحداثي Y مُطَبَّع (0-1) لموضع البداية." + }, + "track_mask": { + "name": "قناع المسار", + "tooltip": "قناع اختياري لتحديد الإطارات المرئية." + }, + "track_spread": { + "name": "توزيع المسارات", + "tooltip": "المسافة المُطَبَّعة بين المسارات. يتم توزيع المسارات بشكل عمودي على اتجاه الحركة." + }, + "width": { + "name": "العرض" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "طول المسار", + "tooltip": null + } + } + }, "GetImageSize": { "description": "يعرض عرض وارتفاع الصورة، ويمررها دون تغيير.", "display_name": "الحصول على حجم الصورة", @@ -2735,17 +3387,17 @@ "name": "صورة" } }, - "outputs": { - "0": { - "name": "العرض" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "الارتفاع" + { + "tooltip": null }, - "2": { - "name": "حجم الدُفعة" + { + "tooltip": null } - } + ] }, "GetVideoComponents": { "description": "يستخرج جميع المكونات من الفيديو: الإطارات، الصوت، ومعدل الإطارات.", @@ -2783,6 +3435,11 @@ "tapered_corners": { "name": "زوايا_منحدرة" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Hunyuan3Dv2Conditioning": { @@ -2792,14 +3449,14 @@ "name": "مخرج_clip_الرؤية" } }, - "outputs": { - "0": { - "name": "إيجابي" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "سلبي" + { + "tooltip": null } - } + ] }, "Hunyuan3Dv2ConditioningMultiView": { "display_name": "Hunyuan3Dv2التكييف متعدد الرؤى", @@ -2817,14 +3474,14 @@ "name": "الأيمن" } }, - "outputs": { - "0": { - "name": "إيجابي" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "سلبي" + { + "tooltip": null } - } + ] }, "HunyuanImageToVideo": { "display_name": "Hunyuan صورة إلى فيديو", @@ -2896,6 +3553,120 @@ } } }, + "HunyuanVideo15ImageToVideo": { + "display_name": "HunyuanVideo15ImageToVideo", + "inputs": { + "batch_size": { + "name": "حجم الدفعة" + }, + "clip_vision_output": { + "name": "مخرج clip للرؤية" + }, + "height": { + "name": "الارتفاع" + }, + "length": { + "name": "الطول" + }, + "negative": { + "name": "سلبي" + }, + "positive": { + "name": "إيجابي" + }, + "start_image": { + "name": "صورة البداية" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "العرض" + } + }, + "outputs": { + "0": { + "name": "إيجابي", + "tooltip": null + }, + "1": { + "name": "سلبي", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "HunyuanVideo15LatentUpscaleWithModel": { + "display_name": "Hunyuan Video 15 تكبير latent بالنموذج", + "inputs": { + "crop": { + "name": "اقتصاص" + }, + "height": { + "name": "الارتفاع" + }, + "model": { + "name": "النموذج" + }, + "samples": { + "name": "العينات" + }, + "upscale_method": { + "name": "طريقة التكبير" + }, + "width": { + "name": "العرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "HunyuanVideo15SuperResolution": { + "display_name": "HunyuanVideo15SuperResolution", + "inputs": { + "clip_vision_output": { + "name": "clip_vision_output" + }, + "latent": { + "name": "كامِن" + }, + "negative": { + "name": "سلبي" + }, + "noise_augmentation": { + "name": "تعزيز الضوضاء" + }, + "positive": { + "name": "إيجابي" + }, + "start_image": { + "name": "صورة البداية" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "إيجابي", + "tooltip": null + }, + "1": { + "name": "سلبي", + "tooltip": null + }, + "2": { + "name": "كامِن", + "tooltip": null + } + } + }, "HyperTile": { "display_name": "HyperTile", "inputs": { @@ -3100,6 +3871,11 @@ "strength": { "name": "القوة" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageBatch": { @@ -3163,6 +3939,26 @@ "image": { "name": "الصورة" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageCompare": { + "description": "يقارن صورتين جنبًا إلى جنب باستخدام شريط تمرير.", + "display_name": "مقارنة الصور", + "inputs": { + "compare_view": { + "name": "عرض المقارنة" + }, + "image_a": { + "name": "الصورة أ" + }, + "image_b": { + "name": "الصورة ب" + } } }, "ImageCompositeMasked": { @@ -3186,6 +3982,11 @@ "y": { "name": "إحداثي Y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageCrop": { @@ -3206,6 +4007,30 @@ "y": { "name": "إحداثي Y" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageDeduplication": { + "display_name": "إزالة تكرار الصور", + "inputs": { + "images": { + "name": "الصور", + "tooltip": "قائمة الصور للمعالجة." + }, + "similarity_threshold": { + "name": "عتبة التشابه", + "tooltip": "عتبة التشابه (0-1). كلما زادت القيمة زاد التشابه. الصور التي تتجاوز هذه العتبة تعتبر مكررة." + } + }, + "outputs": { + "0": { + "name": "الصور", + "tooltip": "الصور المعالجة" + } } }, "ImageFlip": { @@ -3217,6 +4042,11 @@ "image": { "name": "صورة" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageFromBatch": { @@ -3231,6 +4061,42 @@ "length": { "name": "الطول" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageGrid": { + "display_name": "شبكة الصور", + "inputs": { + "cell_height": { + "name": "ارتفاع الخلية", + "tooltip": "ارتفاع كل خلية في الشبكة." + }, + "cell_width": { + "name": "عرض الخلية", + "tooltip": "عرض كل خلية في الشبكة." + }, + "columns": { + "name": "الأعمدة", + "tooltip": "عدد الأعمدة في الشبكة." + }, + "images": { + "name": "الصور", + "tooltip": "قائمة الصور للمعالجة." + }, + "padding": { + "name": "المسافة الفاصلة", + "tooltip": "المسافة بين الصور." + } + }, + "outputs": { + "0": { + "name": "الصور", + "tooltip": "الصور المعالجة" + } } }, "ImageInvert": { @@ -3339,6 +4205,11 @@ "rotation": { "name": "الدوران" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageScale": { @@ -3387,6 +4258,11 @@ "upscale_method": { "name": "طريقة_التكبير" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageScaleToTotalPixels": { @@ -3398,6 +4274,9 @@ "megapixels": { "name": "الميغابكسل" }, + "resolution_steps": { + "name": "خطوات الدقة" + }, "upscale_method": { "name": "طريقة التكبير" } @@ -3452,6 +4331,11 @@ "spacing_width": { "name": "عرض_التباعد" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageToMask": { @@ -3463,6 +4347,11 @@ "image": { "name": "الصورة" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageUpscaleWithModel": { @@ -3572,6 +4461,29 @@ "mask": { "name": "قناع" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "JoinAudioChannels": { + "description": "يضم القناتين الصوتيتين الأحادية (اليمنى واليسرى) في صوت ستيريو.", + "display_name": "دمج قنوات الصوت", + "inputs": { + "audio_left": { + "name": "الصوت الأيسر" + }, + "audio_right": { + "name": "الصوت الأيمن" + } + }, + "outputs": { + "0": { + "name": "الصوت", + "tooltip": null + } } }, "JoinImageWithAlpha": { @@ -3697,6 +4609,58 @@ "sampler_name": { "name": "sampler_name" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Kandinsky5ImageToVideo": { + "display_name": "Kandinsky5ImageToVideo", + "inputs": { + "batch_size": { + "name": "حجم الدفعة" + }, + "height": { + "name": "الارتفاع" + }, + "length": { + "name": "الطول" + }, + "negative": { + "name": "سلبي" + }, + "positive": { + "name": "إيجابي" + }, + "start_image": { + "name": "صورة البداية" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "العرض" + } + }, + "outputs": { + "0": { + "name": "إيجابي", + "tooltip": null + }, + "1": { + "name": "سلبي", + "tooltip": null + }, + "2": { + "name": "كامِن", + "tooltip": "كامِن فيديو فارغ" + }, + "3": { + "name": "كامِن_مشروط", + "tooltip": "صور بداية مشفرة ونظيفة، تُستخدم لاستبدال البداية الضوضائية لمخرجات كامِن النموذج" + } } }, "KarrasScheduler": { @@ -3714,6 +4678,11 @@ "steps": { "name": "خطوات" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "KlingCameraControlI2VNode": { @@ -3869,7 +4838,6 @@ } }, "KlingImage2VideoNode": { - "description": "عقدة تحويل الصورة إلى فيديو في كليغ", "display_name": "كليغ صورة إلى فيديو", "inputs": { "aspect_ratio": { @@ -3957,6 +4925,35 @@ } } }, + "KlingImageToVideoWithAudio": { + "display_name": "تحويل صورة Kling (الإطار الأول) إلى فيديو مع صوت", + "inputs": { + "duration": { + "name": "المدة" + }, + "generate_audio": { + "name": "توليد صوت" + }, + "mode": { + "name": "الوضع" + }, + "model_name": { + "name": "اسم النموذج" + }, + "prompt": { + "name": "الموجه", + "tooltip": "موجه نصي إيجابي." + }, + "start_frame": { + "name": "الإطار الأول" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingLipSyncAudioToVideoNode": { "description": "عقدة مزامنة شفاه كليغ من الصوت إلى الفيديو. تزامن حركة الفم في فيديو مع محتوى صوتي.", "display_name": "مزامنة شفاه كليغ مع الصوت", @@ -4018,6 +5015,227 @@ } } }, + "KlingMotionControl": { + "display_name": "تحكم حركة Kling", + "inputs": { + "character_orientation": { + "name": "اتجاه الشخصية", + "tooltip": "يتحكم في مصدر اتجاه/توجه الشخصية.\nفيديو: الحركات، التعابير، تحركات الكاميرا، والاتجاه يتبع فيديو الحركة المرجعي (تفاصيل أخرى عبر الموجه).\nصورة: الحركات والتعابير تتبع فيديو الحركة المرجعي، لكن اتجاه الشخصية يطابق الصورة المرجعية (الكاميرا/تفاصيل أخرى عبر الموجه)." + }, + "keep_original_sound": { + "name": "الاحتفاظ بالصوت الأصلي" + }, + "mode": { + "name": "الوضع" + }, + "prompt": { + "name": "الموجه" + }, + "reference_image": { + "name": "صورة مرجعية" + }, + "reference_video": { + "name": "فيديو مرجعي", + "tooltip": "فيديو مرجعي للحركة يُستخدم لتحريك/تعبير الشخصية.\nحدود المدة تعتمد على اتجاه الشخصية:\n - صورة: ٣–١٠ ثوانٍ (حد أقصى ١٠ ثوانٍ)\n - فيديو: ٣–٣٠ ثانية (حد أقصى ٣٠ ثانية)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProEditVideoNode": { + "description": "حرر فيديو موجود باستخدام أحدث نموذج من Kling.", + "display_name": "تحرير فيديو Kling Omni (احترافي)", + "inputs": { + "keep_original_sound": { + "name": "الاحتفاظ بالصوت الأصلي" + }, + "model_name": { + "name": "اسم النموذج" + }, + "prompt": { + "name": "الموجه", + "tooltip": "موجه نصي يصف محتوى الفيديو. يمكن أن يتضمن أوصافًا إيجابية وسلبية." + }, + "reference_images": { + "name": "صور مرجعية", + "tooltip": "حتى ٤ صور مرجعية إضافية." + }, + "resolution": { + "name": "الدقة" + }, + "video": { + "name": "فيديو", + "tooltip": "الفيديو للتحرير. سيكون طول الفيديو الناتج هو نفسه." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProFirstLastFrameNode": { + "description": "استخدم إطار بداية، وإطار نهاية اختياري، أو صور مرجعية مع أحدث نموذج من Kling.", + "display_name": "Kling Omni من الإطار الأول إلى الأخير إلى فيديو (احترافي)", + "inputs": { + "duration": { + "name": "duration" + }, + "end_frame": { + "name": "end_frame", + "tooltip": "إطار نهاية اختياري للفيديو. لا يمكن استخدامه مع 'reference_images' في نفس الوقت." + }, + "first_frame": { + "name": "first_frame" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "وصف نصي لمحتوى الفيديو. يمكن أن يتضمن أوصافًا إيجابية وسلبية." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "حتى 6 صور مرجعية إضافية." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageNode": { + "description": "أنشئ أو عدّل الصور باستخدام أحدث نموذج من Kling.", + "display_name": "Kling Omni صورة (احترافي)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "وصف نصي لمحتوى الصورة. يمكن أن يتضمن أوصافًا إيجابية وسلبية." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "حتى 10 صور مرجعية إضافية." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageToVideoNode": { + "description": "استخدم حتى 7 صور مرجعية لإنشاء فيديو باستخدام أحدث نموذج من Kling.", + "display_name": "Kling Omni من صورة إلى فيديو (احترافي)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "وصف نصي لمحتوى الفيديو. يمكن أن يتضمن أوصافًا إيجابية وسلبية." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "حتى 7 صور مرجعية." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProTextToVideoNode": { + "description": "استخدم أوامر نصية لإنشاء فيديوهات باستخدام أحدث نموذج من Kling.", + "display_name": "Kling Omni من نص إلى فيديو (احترافي)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "وصف نصي لمحتوى الفيديو. يمكن أن يتضمن أوصافًا إيجابية وسلبية." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProVideoToVideoNode": { + "description": "استخدم فيديو وما يصل إلى 4 صور مرجعية لإنشاء فيديو باستخدام أحدث نموذج Kling.", + "display_name": "Kling Omni فيديو إلى فيديو (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "keep_original_sound": { + "name": "keep_original_sound" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "وصف نصي لمحتوى الفيديو. يمكن أن يتضمن أوصافًا إيجابية وسلبية." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "حتى 4 صور مرجعية إضافية." + }, + "reference_video": { + "name": "reference_video", + "tooltip": "فيديو للاستخدام كمرجع." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingSingleImageVideoEffectNode": { "description": "تحقيق تأثيرات خاصة مختلفة عند توليد فيديو بناءً على مشهد التأثير.", "display_name": "تأثيرات فيديو كليغ", @@ -4132,6 +5350,35 @@ } } }, + "KlingTextToVideoWithAudio": { + "display_name": "Kling تحويل النص إلى فيديو مع صوت", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "generate_audio": { + "name": "generate_audio" + }, + "mode": { + "name": "mode" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "وصف نصي إيجابي." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingVideoExtendNode": { "description": "عقدة تمديد الفيديو من كليينج. تمديد الفيديوهات المصنوعة بواسطة عقد كليينج الأخرى. يتم إنشاء video_id باستخدام عقد كليينج الأخرى.", "display_name": "كليينج تمديد الفيديو", @@ -4186,6 +5433,26 @@ } } }, + "LTXAVTextEncoderLoader": { + "description": "[الوصفات]\n\nltxav: gemma 3 12B", + "display_name": "محمل مشفر نصوص LTXV الصوتي", + "inputs": { + "ckpt_name": { + "name": "ckpt_name" + }, + "device": { + "name": "device" + }, + "text_encoder": { + "name": "text_encoder" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LTXVAddGuide": { "display_name": "إضافة دليل LTXV", "inputs": { @@ -4228,6 +5495,76 @@ } } }, + "LTXVAudioVAEDecode": { + "display_name": "فك تشفير LTXV Audio VAE", + "inputs": { + "audio_vae": { + "name": "audio_vae", + "tooltip": "نموذج Audio VAE المستخدم لفك تشفير الفضاء الكامن." + }, + "samples": { + "name": "samples", + "tooltip": "الفضاء الكامن المراد فك تشفيره." + } + }, + "outputs": { + "0": { + "name": "Audio", + "tooltip": null + } + } + }, + "LTXVAudioVAEEncode": { + "display_name": "ترميز LTXV Audio VAE", + "inputs": { + "audio": { + "name": "audio", + "tooltip": "الصوت المراد ترميزه." + }, + "audio_vae": { + "name": "audio_vae", + "tooltip": "نموذج Audio VAE المستخدم للترميز." + } + }, + "outputs": { + "0": { + "name": "Audio Latent", + "tooltip": null + } + } + }, + "LTXVAudioVAELoader": { + "display_name": "محمل LTXV Audio VAE", + "inputs": { + "ckpt_name": { + "name": "ckpt_name", + "tooltip": "نقطة تحقق Audio VAE للتحميل." + } + }, + "outputs": { + "0": { + "name": "Audio VAE", + "tooltip": null + } + } + }, + "LTXVConcatAVLatent": { + "display_name": "LTXVConcatAVLatent", + "inputs": { + "audio_latent": { + "name": "audio_latent" + }, + "video_latent": { + "name": "video_latent" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, "LTXVConditioning": { "display_name": "تهيئة LTXV", "inputs": { @@ -4280,6 +5617,33 @@ } } }, + "LTXVEmptyLatentAudio": { + "display_name": "LTXV Empty Latent Audio", + "inputs": { + "audio_vae": { + "name": "audio_vae", + "tooltip": "نموذج Audio VAE المستخدم للحصول على الإعدادات." + }, + "batch_size": { + "name": "batch_size", + "tooltip": "عدد عينات الصوت الكامن في الدفعة." + }, + "frame_rate": { + "name": "frame_rate", + "tooltip": "عدد الإطارات في الثانية." + }, + "frames_number": { + "name": "frames_number", + "tooltip": "عدد الإطارات." + } + }, + "outputs": { + "0": { + "name": "Latent", + "tooltip": null + } + } + }, "LTXVImgToVideo": { "display_name": "LTXV صورة إلى فيديو", "inputs": { @@ -4326,6 +5690,47 @@ } } }, + "LTXVImgToVideoInplace": { + "display_name": "LTXVImgToVideoInplace", + "inputs": { + "bypass": { + "name": "تجاوز", + "tooltip": "تجاوز التكييف." + }, + "image": { + "name": "صورة" + }, + "latent": { + "name": "كامنة" + }, + "strength": { + "name": "القوة" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "كامنة", + "tooltip": null + } + } + }, + "LTXVLatentUpsampler": { + "display_name": "LTXVLatentUpsampler", + "inputs": { + "samples": { + "name": "عينات" + }, + "upscale_model": { + "name": "نموذج التكبير" + }, + "vae": { + "name": "vae" + } + } + }, "LTXVPreprocess": { "display_name": "LTXV المعالجة المسبقة", "inputs": { @@ -4374,6 +5779,25 @@ } } }, + "LTXVSeparateAVLatent": { + "description": "LTXV فصل الكامنة الصوتية والمرئية", + "display_name": "LTXVSeparateAVLatent", + "inputs": { + "av_latent": { + "name": "كامنة صوتية ومرئية" + } + }, + "outputs": { + "0": { + "name": "كامنة الفيديو", + "tooltip": null + }, + "1": { + "name": "كامنة الصوت", + "tooltip": null + } + } + }, "LaplaceScheduler": { "display_name": "جدول لاپلاس", "inputs": { @@ -4392,6 +5816,11 @@ "steps": { "name": "الخطوات" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LatentAdd": { @@ -4529,6 +5958,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LatentConcat": { @@ -4592,6 +6026,25 @@ } } }, + "LatentCutToBatch": { + "display_name": "LatentCutToBatch", + "inputs": { + "dim": { + "name": "dim" + }, + "samples": { + "name": "samples" + }, + "slice_size": { + "name": "slice_size" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LatentFlip": { "display_name": "قلب الكامن", "inputs": { @@ -4745,6 +6198,19 @@ } } }, + "LatentUpscaleModelLoader": { + "display_name": "تحميل نموذج تكبير latent", + "inputs": { + "model_name": { + "name": "model_name" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LazyCache": { "description": "نسخة محلية الصنع من EasyCache - نسخة 'أسهل' من EasyCache للتنفيذ. تعمل بشكل عام أسوأ من EasyCache، ولكن أفضل في بعض الحالات النادرة ومتوافقة عالميًا مع كل شيء في ComfyUI.", "display_name": "ذاكرة_التخزين_المؤقت_الكسولة", @@ -4792,70 +6258,32 @@ }, "upload 3d model": { }, - "width": { - "name": "العرض" - } - }, - "outputs": { - "0": { - "name": "صورة" - }, - "1": { - "name": "قناع" - }, - "2": { - "name": "مسار الشبكة" - }, - "3": { - "name": "المعتاد" - }, - "4": { - "name": "الخطوط" - }, - "5": { - "name": "معلومات الكاميرا" - }, - "6": { - "name": "تسجيل_فيديو" - } - } - }, - "Load3DAnimation": { - "display_name": "تحميل ثلاثي الأبعاد - حركة", - "inputs": { - "height": { - "name": "الارتفاع" - }, - "image": { - "name": "صورة" - }, - "model_file": { - "name": "ملف النموذج" + "upload extra resources": { }, "width": { "name": "العرض" } }, - "outputs": { - "0": { - "name": "صورة" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "قناع" + { + "tooltip": null }, - "2": { - "name": "مسار الشبكة" + { + "tooltip": null }, - "3": { - "name": "المعتاد" + { + "tooltip": null }, - "4": { - "name": "معلومات الكاميرا" + { + "tooltip": null }, - "5": { - "name": "تسجيل_فيديو" + { + "tooltip": null } - } + ] }, "LoadAudio": { "display_name": "تحميل الصوت", @@ -4869,6 +6297,11 @@ "upload": { "name": "اختر ملف للتحميل" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LoadImage": { @@ -4882,6 +6315,21 @@ } } }, + "LoadImageDataSetFromFolder": { + "display_name": "تحميل مجموعة بيانات الصور من مجلد", + "inputs": { + "folder": { + "name": "folder", + "tooltip": "المجلد الذي سيتم تحميل الصور منه." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "قائمة الصور المحملة" + } + } + }, "LoadImageMask": { "display_name": "تحميل صورة (كقناع)", "inputs": { @@ -4900,6 +6348,8 @@ "description": "تحميل صورة من مجلد المخرجات. عند الضغط على زر التحديث، سيقوم العقدة بتحديث قائمة الصور واختيار أول صورة تلقائياً لتسهيل التكرار.", "display_name": "تحميل صورة (من المخرجات)", "inputs": { + "Auto-refresh after generation": { + }, "image": { "name": "صورة" }, @@ -4910,41 +6360,22 @@ } } }, - "LoadImageSetFromFolderNode": { - "description": "يقوم بتحميل مجموعة من الصور من مجلد للتدريب.", - "display_name": "تحميل مجموعة بيانات الصور من المجلد", + "LoadImageTextDataSetFromFolder": { + "display_name": "تحميل مجموعة بيانات الصور والنصوص من مجلد", "inputs": { "folder": { - "name": "مجلد", + "name": "folder", "tooltip": "المجلد الذي سيتم تحميل الصور منه." - }, - "resize_method": { - "name": "طريقة_تغيير_الحجم" } - } - }, - "LoadImageTextSetFromFolderNode": { - "description": "يقوم بتحميل مجموعة من الصور والتسميات التوضيحية من مجلد للتدريب.", - "display_name": "تحميل مجموعة بيانات الصور والنص من المجلد", - "inputs": { - "clip": { - "name": "clip", - "tooltip": "نموذج CLIP المستخدم لتشفير النص." + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "قائمة الصور المحملة" }, - "folder": { - "name": "مجلد", - "tooltip": "المجلد الذي سيتم تحميل الصور منه." - }, - "height": { - "name": "الارتفاع", - "tooltip": "الارتفاع الذي سيتم تغيير حجم الصور إليه. -1 يعني استخدام الارتفاع الأصلي." - }, - "resize_method": { - "name": "طريقة_تغيير_الحجم" - }, - "width": { - "name": "العرض", - "tooltip": "العرض الذي سيتم تغيير حجم الصور إليه. -1 يعني استخدام العرض الأصلي." + "1": { + "name": "texts", + "tooltip": "قائمة التسميات التوضيحية للنص" } } }, @@ -4956,6 +6387,25 @@ } } }, + "LoadTrainingDataset": { + "display_name": "تحميل مجموعة بيانات التدريب", + "inputs": { + "folder_name": { + "name": "folder_name", + "tooltip": "اسم المجلد الذي يحتوي على مجموعة البيانات المحفوظة (داخل دليل الإخراج)." + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "قائمة قواميس الفضاء الكامن" + }, + "1": { + "name": "conditioning", + "tooltip": "قائمة قوائم التكييف" + } + } + }, "LoadVideo": { "display_name": "تحميل فيديو", "inputs": { @@ -5027,7 +6477,6 @@ } }, "LoraModelLoader": { - "description": "تحميل أوزان LoRA المدربة من عقدة Train LoRA.", "display_name": "تحميل نموذج LoRA", "inputs": { "lora": { @@ -5043,11 +6492,11 @@ "tooltip": "مدى قوة تعديل نموذج الانتشار. يمكن أن تكون هذه القيمة سالبة." } }, - "outputs": { - "0": { - "tooltip": "نموذج الانتشار المعدل." + "outputs": [ + { + "name": "model" } - } + ] }, "LoraSave": { "display_name": "استخراج وحفظ LoRA", @@ -5075,14 +6524,15 @@ } }, "LossGraphNode": { - "description": "يرسم مخطط الخسارة ويحفظه في دليل الإخراج.", "display_name": "رسم بياني للخسارة", "inputs": { "filename_prefix": { - "name": "بادئة_اسم_الملف" + "name": "بادئة_اسم_الملف", + "tooltip": "بادئة اسم ملف صورة رسم الخسارة المحفوظة." }, "loss": { - "name": "خسارة" + "name": "خسارة", + "tooltip": "خريطة الخسارة من عقدة التدريب." } } }, @@ -5388,6 +6838,50 @@ } } }, + "MakeTrainingDataset": { + "display_name": "إنشاء مجموعة بيانات تدريبية", + "inputs": { + "clip": { + "name": "clip", + "tooltip": "نموذج CLIP لترميز النص إلى التكييف." + }, + "images": { + "name": "صور", + "tooltip": "قائمة الصور للترميز." + }, + "texts": { + "name": "نصوص", + "tooltip": "قائمة التسميات التوضيحية للنص. يمكن أن تكون بطول n (مطابقة للصور)، أو 1 (مكررة للجميع)، أو محذوفة (يتم استخدام سلسلة فارغة)." + }, + "vae": { + "name": "vae", + "tooltip": "نموذج VAE لترميز الصور إلى الكامنات." + } + }, + "outputs": { + "0": { + "name": "كامنات", + "tooltip": "قائمة القواميس الكامنة" + }, + "1": { + "name": "تكييف", + "tooltip": "قائمة قوائم التكييف" + } + } + }, + "ManualSigmas": { + "display_name": "ManualSigmas", + "inputs": { + "sigmas": { + "name": "سيغما" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "MaskComposite": { "display_name": "تركيب القناع", "inputs": { @@ -5406,6 +6900,11 @@ "y": { "name": "ص" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "MaskPreview": { @@ -5423,6 +6922,315 @@ "mask": { "name": "قناع" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MergeImageLists": { + "display_name": "دمج قوائم الصور", + "inputs": { + "images": { + "name": "صور", + "tooltip": "قائمة الصور للمعالجة." + } + }, + "outputs": { + "0": { + "name": "صور", + "tooltip": "الصور المعالجة" + } + } + }, + "MergeTextLists": { + "display_name": "دمج قوائم النصوص", + "inputs": { + "texts": { + "name": "نصوص", + "tooltip": "قائمة النصوص للمعالجة." + } + }, + "outputs": { + "0": { + "name": "نصوص", + "tooltip": "النصوص المعالجة" + } + } + }, + "MeshyAnimateModelNode": { + "description": "تطبيق حركة أنيميشن محددة على شخصية تم تجهيزها مسبقًا.", + "display_name": "Meshy: تحريك النموذج", + "inputs": { + "action_id": { + "name": "action_id", + "tooltip": "قم بزيارة https://docs.meshy.ai/en/api/animation-library للاطلاع على قائمة القيم المتاحة." + }, + "rig_task_id": { + "name": "rig_task_id" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + } + } + }, + "MeshyImageToModelNode": { + "display_name": "Meshy: من صورة إلى نموذج", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "image": { + "name": "image" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "حدد وضعية النموذج الناتج." + }, + "seed": { + "name": "seed", + "tooltip": "تتحكم البذرة فيما إذا كان يجب إعادة تشغيل العقدة؛ النتائج غير حتمية بغض النظر عن البذرة." + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "عند التعيين إلى false، يتم إرجاع شبكة مثلثية غير معالجة." + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "should_texture": { + "name": "should_texture", + "tooltip": "يحدد ما إذا كان سيتم توليد الخامات. إذا تم تعيينه إلى false، يتم تخطي مرحلة الخامات وإرجاع شبكة بدون خامات." + }, + "should_texture_enable_pbr": { + "name": "enable_pbr" + }, + "should_texture_texture_prompt": { + "name": "texture_prompt" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyMultiImageToModelNode": { + "display_name": "Meshy: من صور متعددة إلى نموذج", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "حدد وضعية النموذج الناتج." + }, + "seed": { + "name": "seed", + "tooltip": "تتحكم البذرة فيما إذا كان يجب إعادة تشغيل العقدة؛ النتائج غير حتمية بغض النظر عن البذرة." + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "عند التعيين إلى false، يتم إرجاع شبكة مثلثية غير معالجة." + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "should_texture": { + "name": "should_texture", + "tooltip": "يحدد ما إذا كان سيتم توليد الخامات. إذا تم تعيينه إلى false، يتم تخطي مرحلة الخامات وإرجاع شبكة بدون خامات." + }, + "should_texture_enable_pbr": { + "name": "enable_pbr" + }, + "should_texture_texture_prompt": { + "name": "texture_prompt" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRefineNode": { + "description": "تحسين نموذج أولي تم إنشاؤه مسبقًا.", + "display_name": "Meshy: تحسين النموذج الأولي", + "inputs": { + "enable_pbr": { + "name": "enable_pbr", + "tooltip": "توليد خرائط PBR (معدنية، خشونة، عادية) بالإضافة إلى اللون الأساسي. ملاحظة: يجب ضبط هذا الخيار على 'غير مفعل' عند استخدام نمط Sculpture، حيث أن نمط Sculpture يولد مجموعته الخاصة من خرائط PBR." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "model": { + "name": "model" + }, + "texture_image": { + "name": "texture_image", + "tooltip": "يمكن استخدام أحد الخيارين فقط: 'texture_image' أو 'texture_prompt' في نفس الوقت." + }, + "texture_prompt": { + "name": "texture_prompt", + "tooltip": "أدخل نصًا لتوجيه عملية الإكساء. الحد الأقصى ٦٠٠ حرف. لا يمكن استخدامه مع 'texture_image' في نفس الوقت." + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRigModelNode": { + "description": "يوفر شخصية مجهزة بالحركة بصيغ قياسية. التجهيز التلقائي غير مناسب حاليًا للنماذج غير المكسوة، أو الأصول غير البشرية، أو الأصول البشرية ذات البنية غير الواضحة للأطراف والجسم.", + "display_name": "Meshy: تجهيز النموذج بالحركة", + "inputs": { + "height_meters": { + "name": "height_meters", + "tooltip": "الارتفاع التقريبي لنموذج الشخصية بالمتر. يساعد ذلك في دقة القياس والتجهيز." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "texture_image": { + "name": "texture_image", + "tooltip": "صورة الإكساء الأساسية (UV-unwrapped) للنموذج." + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "rig_task_id", + "tooltip": null + } + } + }, + "MeshyTextToModelNode": { + "display_name": "Meshy: تحويل النص إلى نموذج", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "حدد وضعية النموذج المُنتج." + }, + "prompt": { + "name": "prompt" + }, + "seed": { + "name": "seed", + "tooltip": "الرقم العشوائي (seed) يتحكم في ما إذا كان يجب إعادة تشغيل العقدة؛ النتائج غير حتمية بغض النظر عن قيمة seed." + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "عند ضبطه على 'غير مفعل'، يتم إرجاع شبكة مثلثية غير معالجة." + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "style": { + "name": "style" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyTextureNode": { + "display_name": "Meshy: نموذج النسيج", + "inputs": { + "enable_original_uv": { + "name": "تفعيل UV الأصلي", + "tooltip": "استخدم UV الأصلي للنموذج بدلاً من إنشاء UV جديد. عند التفعيل، يحتفظ Meshy بالخامات الأصلية من النموذج المرفوع. إذا لم يكن لدى النموذج UV أصلي، قد لا تكون جودة الناتج جيدة." + }, + "image_style": { + "name": "نمط الصورة", + "tooltip": "صورة ثنائية الأبعاد لتوجيه عملية النسيج. لا يمكن استخدامها مع 'text_style_prompt' في نفس الوقت." + }, + "meshy_task_id": { + "name": "معرّف مهمة Meshy" + }, + "model": { + "name": "النموذج" + }, + "pbr": { + "name": "PBR" + }, + "text_style_prompt": { + "name": "وصف نمط النسيج بالنص", + "tooltip": "صف نمط النسيج المطلوب للكائن باستخدام النص. الحد الأقصى ٦٠٠ حرف. لا يمكن استخدامه مع 'image_style' في نفس الوقت." + } + }, + "outputs": { + "0": { + "name": "ملف النموذج", + "tooltip": null + }, + "1": { + "name": "معرّف مهمة Meshy", + "tooltip": null + } } }, "MinimaxHailuoVideoNode": { @@ -7866,6 +9674,52 @@ } } }, + "NormalizeImages": { + "display_name": "تطبيع الصور", + "inputs": { + "images": { + "name": "صور", + "tooltip": "الصورة للمعالجة." + }, + "mean": { + "name": "المتوسط", + "tooltip": "قيمة المتوسط للتطبيع." + }, + "std": { + "name": "الانحراف المعياري", + "tooltip": "الانحراف المعياري للتطبيع." + } + }, + "outputs": { + "0": { + "name": "صور", + "tooltip": "الصور المعالجة" + } + } + }, + "NormalizeVideoLatentStart": { + "description": "يقوم بتطبيع الإطارات الأولية من latent الفيديو لمطابقة المتوسط والانحراف المعياري لإطارات مرجعية لاحقة. يساعد في تقليل الفروقات بين الإطارات الأولى وباقي الفيديو.", + "display_name": "NormalizeVideoLatentStart", + "inputs": { + "latent": { + "name": "latent" + }, + "reference_frame_count": { + "name": "reference_frame_count", + "tooltip": "عدد إطارات latent بعد الإطارات الأولى لاستخدامها كمرجع" + }, + "start_frame_count": { + "name": "start_frame_count", + "tooltip": "عدد إطارات latent المطلوب تطبيعها، محسوبة من البداية" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, "OpenAIChatConfig": { "description": "يسمح بتحديد خيارات التكوين المتقدمة لعقد الدردشة الخاصة بـ OpenAI.", "display_name": "خيارات OpenAI ChatGPT المتقدمة", @@ -8015,6 +9869,9 @@ "name": "القناع", "tooltip": "قناع اختياري للرسم الداخلي (سيتم استبدال المناطق البيضاء)" }, + "model": { + "name": "model" + }, "n": { "name": "عدد الصور", "tooltip": "كم عدد الصور التي يتم إنشاؤها" @@ -8373,265 +10230,6 @@ } } }, - "PikaImageToVideoNode2_2": { - "description": "يرسل صورة ونص وصف إلى واجهة برمجة تطبيقات بيك v2.2 لإنشاء فيديو.", - "display_name": "تحويل صورة إلى فيديو بيك", - "inputs": { - "control_after_generate": { - "name": "التحكم بعد الإنشاء" - }, - "duration": { - "name": "المدة" - }, - "image": { - "name": "الصورة", - "tooltip": "الصورة المراد تحويلها إلى فيديو" - }, - "negative_prompt": { - "name": "الوصف السلبي" - }, - "prompt_text": { - "name": "نص الوصف" - }, - "resolution": { - "name": "الدقة" - }, - "seed": { - "name": "البذرة" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaScenesV2_2": { - "description": "ادمج صورك لإنشاء فيديو يحتوي على الكائنات الموجودة فيها. قم برفع عدة صور كمكونات وأنشئ فيديو عالي الجودة يدمج جميعها.", - "display_name": "مشاهد بيك (تكوين فيديو من الصور)", - "inputs": { - "aspect_ratio": { - "name": "نسبة العرض إلى الارتفاع", - "tooltip": "نسبة العرض إلى الارتفاع (العرض / الارتفاع)" - }, - "control_after_generate": { - "name": "التحكم بعد الإنشاء" - }, - "duration": { - "name": "المدة" - }, - "image_ingredient_1": { - "name": "مكون الصورة 1", - "tooltip": "الصورة التي ستُستخدم كمكون لإنشاء الفيديو." - }, - "image_ingredient_2": { - "name": "مكون الصورة 2", - "tooltip": "الصورة التي ستُستخدم كمكون لإنشاء الفيديو." - }, - "image_ingredient_3": { - "name": "مكون الصورة 3", - "tooltip": "الصورة التي ستُستخدم كمكون لإنشاء الفيديو." - }, - "image_ingredient_4": { - "name": "مكون الصورة 4", - "tooltip": "الصورة التي ستُستخدم كمكون لإنشاء الفيديو." - }, - "image_ingredient_5": { - "name": "مكون الصورة 5", - "tooltip": "الصورة التي ستُستخدم كمكون لإنشاء الفيديو." - }, - "ingredients_mode": { - "name": "وضع المكونات" - }, - "negative_prompt": { - "name": "الوصف السلبي" - }, - "prompt_text": { - "name": "نص الوصف" - }, - "resolution": { - "name": "الدقة" - }, - "seed": { - "name": "البذرة" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaStartEndFrameNode2_2": { - "description": "أنشئ فيديوً بدمج أول وآخر إطارين. قم برفع صورتين لتحديد نقاط البداية والنهاية، ودع الذكاء الاصطناعي ينشئ انتقالاً سلساً بينهما.", - "display_name": "إطارات بداية ونهاية بيك إلى فيديو", - "inputs": { - "control_after_generate": { - "name": "التحكم بعد الإنشاء" - }, - "duration": { - "name": "المدة" - }, - "image_end": { - "name": "صورة النهاية", - "tooltip": "الصورة الأخيرة للدمج." - }, - "image_start": { - "name": "صورة البداية", - "tooltip": "الصورة الأولى للدمج." - }, - "negative_prompt": { - "name": "الوصف السلبي" - }, - "prompt_text": { - "name": "نص الوصف" - }, - "resolution": { - "name": "الدقة" - }, - "seed": { - "name": "البذرة" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaTextToVideoNode2_2": { - "description": "يرسل نص المطالبة إلى واجهة برمجة تطبيقات بيكا الإصدار 2.2 لتوليد فيديو.", - "display_name": "بيكا نص إلى فيديو", - "inputs": { - "aspect_ratio": { - "name": "نسبة العرض إلى الارتفاع", - "tooltip": "نسبة العرض إلى الارتفاع (العرض / الارتفاع)" - }, - "control_after_generate": { - "name": "التحكم بعد التوليد" - }, - "duration": { - "name": "المدة" - }, - "negative_prompt": { - "name": "نص المطالبة السلبية" - }, - "prompt_text": { - "name": "نص المطالبة" - }, - "resolution": { - "name": "الدقة" - }, - "seed": { - "name": "البذرة" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikadditions": { - "description": "أضف أي كائن أو صورة إلى الفيديو الخاص بك. قم برفع فيديو وحدد ما تريد إضافته لإنشاء نتيجة مدمجة بسلاسة.", - "display_name": "إضافات بيك (إدخال كائن فيديو)", - "inputs": { - "control_after_generate": { - "name": "التحكم بعد الإنشاء" - }, - "image": { - "name": "الصورة", - "tooltip": "الصورة التي تريد إضافتها إلى الفيديو." - }, - "negative_prompt": { - "name": "الوصف السلبي" - }, - "prompt_text": { - "name": "نص الوصف" - }, - "seed": { - "name": "البذرة" - }, - "video": { - "name": "الفيديو", - "tooltip": "الفيديو الذي تريد إضافة صورة إليه." - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikaffects": { - "description": "أنشئ فيديو مع تأثير بيك محدد. التأثيرات المدعومة: تزيين الكيك، التفتيت، السحق، القطع، الانكماش، الذوبان، الانفجار، بروز العين، النفخ، التعليق، الذوبان، التقشير، الوخز، السحق، تعبير المفاجأة، التمزق", - "display_name": "تأثيرات بيك (تأثيرات الفيديو)", - "inputs": { - "control_after_generate": { - "name": "التحكم بعد الإنشاء" - }, - "image": { - "name": "الصورة", - "tooltip": "الصورة المرجعية لتطبيق التأثير عليها." - }, - "negative_prompt": { - "name": "الوصف السلبي" - }, - "pikaffect": { - "name": "تأثير بيك" - }, - "prompt_text": { - "name": "نص الوصف" - }, - "seed": { - "name": "البذرة" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikaswaps": { - "description": "استبدل أي كائن أو منطقة في الفيديو الخاص بك بصورة أو كائن جديد. عرّف المناطق التي تريد استبدالها إما بقناع أو بإحداثيات.", - "display_name": "بيكا سوابس (استبدال كائن الفيديو)", - "inputs": { - "control_after_generate": { - "name": "التحكم بعد التوليد" - }, - "image": { - "name": "الصورة", - "tooltip": "الصورة المستخدمة لاستبدال الكائن المقنع في الفيديو." - }, - "mask": { - "name": "القناع", - "tooltip": "استخدم القناع لتحديد المناطق التي سيتم استبدالها في الفيديو." - }, - "negative_prompt": { - "name": "نص المطالبة السلبية" - }, - "prompt_text": { - "name": "نص المطالبة" - }, - "region_to_modify": { - "name": "المنطقة المراد تعديلها", - "tooltip": "وصف نصي بسيط للكائن / المنطقة المراد تعديلها." - }, - "seed": { - "name": "البذرة" - }, - "video": { - "name": "الفيديو", - "tooltip": "الفيديو الذي سيتم استبدال كائن فيه." - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, "PixverseImageToVideoNode": { "description": "ينتج فيديوهات بشكل متزامن بناءً على النص المطلوب وحجم المخرج.", "display_name": "بيكسفيرس صورة إلى فيديو", @@ -8786,6 +10384,11 @@ "steps": { "name": "الخطوات" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "PorterDuffImageComposite": { @@ -8819,6 +10422,9 @@ "Preview3D": { "display_name": "معاينة ثلاثية الأبعاد", "inputs": { + "bg_image": { + "name": "bg_image" + }, "camera_info": { "name": "معلومات الكاميرا" }, @@ -8830,22 +10436,13 @@ } } }, - "Preview3DAnimation": { - "display_name": "معاينة ثلاثية الأبعاد - حركة", - "inputs": { - "camera_info": { - "name": "معلومات الكاميرا" - }, - "model_file": { - "name": "ملف النموذج" - } - } - }, "PreviewAny": { "display_name": "معاينة أي", "inputs": { "preview": { }, + "previewMode": { + }, "source": { "name": "المصدر" } @@ -8985,6 +10582,36 @@ } } }, + "RandomCropImages": { + "display_name": "قص عشوائي للصور", + "inputs": { + "control_after_generate": { + "name": "التحكم بعد التوليد" + }, + "height": { + "name": "height", + "tooltip": "ارتفاع القص." + }, + "images": { + "name": "images", + "tooltip": "الصورة للمعالجة." + }, + "seed": { + "name": "seed", + "tooltip": "بذرة عشوائية." + }, + "width": { + "name": "width", + "tooltip": "عرض القص." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "الصور المعالجة" + } + } + }, "RandomNoise": { "display_name": "ضجيج عشوائية", "inputs": { @@ -8994,6 +10621,11 @@ "noise_seed": { "name": "بذرة الضجيج" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RebatchImages": { @@ -9034,6 +10666,11 @@ "audio": { "name": "الصوت" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RecraftColorRGB": { @@ -9538,6 +11175,11 @@ "image": { "name": "الصورة" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RepeatLatentBatch": { @@ -9551,6 +11193,51 @@ } } }, + "ReplaceText": { + "display_name": "استبدال النص", + "inputs": { + "find": { + "name": "بحث", + "tooltip": "النص المطلوب إيجاده." + }, + "replace": { + "name": "استبدال", + "tooltip": "النص الذي سيتم الاستبدال به." + }, + "texts": { + "name": "النصوص", + "tooltip": "النصوص للمعالجة." + } + }, + "outputs": { + "0": { + "name": "النصوص", + "tooltip": "النصوص المعالجة" + } + } + }, + "ReplaceVideoLatentFrames": { + "display_name": "استبدال إطارات latent للفيديو", + "inputs": { + "destination": { + "name": "الوجهة", + "tooltip": "latent الوجهة حيث سيتم استبدال الإطارات." + }, + "index": { + "name": "الفهرس", + "tooltip": "فهرس إطار latent الابتدائي في latent الوجهة حيث سيتم وضع إطارات latent المصدر. القيم السالبة تعني العد من النهاية." + }, + "source": { + "name": "المصدر", + "tooltip": "latent المصدر الذي يوفر الإطارات لإدراجها في latent الوجهة. إذا لم يتم توفيره، سيتم إرجاع latent الوجهة بدون تغيير." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "RescaleCFG": { "display_name": "إعادة تحجيم CFG", "inputs": { @@ -9580,6 +11267,104 @@ "target_width": { "name": "العرض_الهدف" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ResizeImageMaskNode": { + "description": "تغيير حجم صورة أو قناع باستخدام طرق تحجيم مختلفة.", + "display_name": "تغيير حجم الصورة/القناع", + "inputs": { + "input": { + "name": "الإدخال" + }, + "resize_type": { + "name": "نوع تغيير الحجم", + "tooltip": "اختر طريقة تغيير الحجم: حسب الأبعاد الدقيقة، عامل التحجيم، مطابقة صورة أخرى، إلخ." + }, + "resize_type_crop": { + "name": "قص" + }, + "resize_type_height": { + "name": "الارتفاع" + }, + "resize_type_width": { + "name": "العرض" + }, + "scale_method": { + "name": "طريقة التحجيم", + "tooltip": "خوارزمية الاستيفاء. 'area' هي الأفضل لتصغير الحجم، و'lanczos' لتكبير الحجم، و'nearest-exact' لفن البكسل." + } + }, + "outputs": { + "0": { + "name": "بحجم معدل", + "tooltip": null + } + } + }, + "ResizeImagesByLongerEdge": { + "display_name": "تغيير حجم الصور حسب الحافة الأطول", + "inputs": { + "images": { + "name": "images", + "tooltip": "الصورة المراد معالجتها." + }, + "longer_edge": { + "name": "longer_edge", + "tooltip": "الطول المستهدف للحافة الأطول." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "الصور المعالجة" + } + } + }, + "ResizeImagesByShorterEdge": { + "display_name": "تغيير حجم الصور حسب الحافة الأقصر", + "inputs": { + "images": { + "name": "images", + "tooltip": "الصورة المراد معالجتها." + }, + "shorter_edge": { + "name": "shorter_edge", + "tooltip": "الطول المستهدف للحافة الأقصر." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "الصور المعالجة" + } + } + }, + "ResolutionBucket": { + "display_name": "تجميع الدقة", + "inputs": { + "conditioning": { + "name": "conditioning", + "tooltip": "قائمة قوائم conditioning (يجب أن تطابق طول latents)." + }, + "latents": { + "name": "latents", + "tooltip": "قائمة قواميس latent للتجميع حسب الدقة." + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "قائمة قواميس latent المجمعة، واحدة لكل تجميع دقة." + }, + "1": { + "name": "conditioning", + "tooltip": "قائمة قوائم conditioning، واحدة لكل تجميع دقة." + } } }, "Rodin3D_Detail": { @@ -9833,6 +11618,11 @@ "steps": { "name": "الخطوات" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SD_4XUpscale_Conditioning": { @@ -9986,14 +11776,14 @@ "name": "سيغما" } }, - "outputs": { - "0": { - "name": "المخرج" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "المخرج المنقح" + { + "tooltip": null } - } + ] }, "SamplerCustomAdvanced": { "display_name": "المُعين المخصص المتقدم", @@ -10014,14 +11804,14 @@ "name": "سيغما" } }, - "outputs": { - "0": { - "name": "المخرج" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "المخرج المنقح" + { + "tooltip": null } - } + ] }, "SamplerDPMAdaptative": { "display_name": "المُعين DPM التكيفي", @@ -10056,6 +11846,11 @@ "s_noise": { "name": "ضجيج s" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_2M_SDE": { @@ -10073,6 +11868,11 @@ "solver_type": { "name": "نوع المُحلل" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_2S_Ancestral": { @@ -10084,6 +11884,11 @@ "s_noise": { "name": "ضجيج s" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_3M_SDE": { @@ -10098,6 +11903,11 @@ "s_noise": { "name": "ضجيج s" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_SDE": { @@ -10115,6 +11925,11 @@ "s_noise": { "name": "ضجيج s" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerER_SDE": { @@ -10133,6 +11948,11 @@ "solver_type": { "name": "نوع الحل" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerAncestral": { @@ -10144,6 +11964,11 @@ "s_noise": { "name": "ضجيج s" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerAncestralCFGPP": { @@ -10155,6 +11980,11 @@ "s_noise": { "name": "ضجيج s" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerCFGpp": { @@ -10195,6 +12025,11 @@ "order": { "name": "الترتيب" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerSASolver": { @@ -10227,6 +12062,37 @@ "use_pece": { "name": "استخدام PECE" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerSEEDS2": { + "description": "يمكن لعقدة السامبلر هذه تمثيل عدة سامبلرات:\n\nseeds_2\n- الإعداد الافتراضي\n\nexp_heun_2_x0\n- solver_type=phi_2, r=1.0, eta=0.0\n\nexp_heun_2_x0_sde\n- solver_type=phi_2, r=1.0, eta=1.0, s_noise=1.0", + "display_name": "SamplerSEEDS2", + "inputs": { + "eta": { + "name": "eta", + "tooltip": "قوة العشوائية" + }, + "r": { + "name": "r", + "tooltip": "حجم الخطوة النسبية للمرحلة المتوسطة (عقدة c2)" + }, + "s_noise": { + "name": "s_noise", + "tooltip": "معامل ضوضاء SDE" + }, + "solver_type": { + "name": "solver_type" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplingPercentToSigma": { @@ -10243,11 +12109,11 @@ "name": "نسبة أخذ العينات" } }, - "outputs": { - "0": { - "name": "قيمة سيجما" + "outputs": [ + { + "tooltip": null } - } + ] }, "SaveAnimatedPNG": { "display_name": "حفظ PNG متحرك", @@ -10365,6 +12231,44 @@ } } }, + "SaveImageDataSetToFolder": { + "display_name": "حفظ مجموعة بيانات الصور في مجلد", + "inputs": { + "filename_prefix": { + "name": "filename_prefix", + "tooltip": "بادئة أسماء ملفات الصور المحفوظة." + }, + "folder_name": { + "name": "folder_name", + "tooltip": "اسم المجلد لحفظ الصور بداخله (داخل دليل الإخراج)." + }, + "images": { + "name": "images", + "tooltip": "قائمة الصور المراد حفظها." + } + } + }, + "SaveImageTextDataSetToFolder": { + "display_name": "حفظ مجموعة بيانات الصور والنصوص في مجلد", + "inputs": { + "filename_prefix": { + "name": "filename_prefix", + "tooltip": "بادئة أسماء ملفات الصور المحفوظة." + }, + "folder_name": { + "name": "folder_name", + "tooltip": "اسم المجلد لحفظ الصور بداخله (داخل دليل الإخراج)." + }, + "images": { + "name": "images", + "tooltip": "قائمة الصور المراد حفظها." + }, + "texts": { + "name": "texts", + "tooltip": "قائمة التسميات التوضيحية النصية المراد حفظها." + } + } + }, "SaveImageWebsocket": { "display_name": "حفظ صورة عبر Websocket", "inputs": { @@ -10384,7 +12288,7 @@ } } }, - "SaveLoRANode": { + "SaveLoRA": { "display_name": "حفظ أوزان LoRA", "inputs": { "lora": { @@ -10392,11 +12296,11 @@ "tooltip": "نموذج LoRA المراد حفظه. لا تستخدم النموذج مع طبقات LoRA." }, "prefix": { - "name": "بادئة", + "name": "prefix", "tooltip": "البادئة المستخدمة لملف LoRA المحفوظ." }, "steps": { - "name": "خطوات", + "name": "steps", "tooltip": "اختياري: عدد الخطوات التي تم تدريب LoRA عليها، تُستخدم لتسمية الملف المحفوظ." } } @@ -10414,6 +12318,27 @@ } } }, + "SaveTrainingDataset": { + "display_name": "حفظ مجموعة بيانات التدريب", + "inputs": { + "conditioning": { + "name": "conditioning", + "tooltip": "قائمة قوائم التكييف من MakeTrainingDataset." + }, + "folder_name": { + "name": "folder_name", + "tooltip": "اسم المجلد لحفظ مجموعة البيانات (داخل دليل الإخراج)." + }, + "latents": { + "name": "latents", + "tooltip": "قائمة القيم الكامنة من MakeTrainingDataset." + }, + "shard_size": { + "name": "shard_size", + "tooltip": "عدد العينات في كل ملف جزء." + } + } + }, "SaveVideo": { "description": "يحفظ الصور المدخلة في مجلد مخرجات ComfyUI الخاص بك.", "display_name": "حفظ الفيديو", @@ -10534,6 +12459,11 @@ "sigmas": { "name": "Sigmas" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SetHookKeyframes": { @@ -10574,6 +12504,58 @@ } } }, + "ShuffleDataset": { + "display_name": "تبديل ترتيب مجموعة بيانات الصور", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images", + "tooltip": "قائمة الصور لمعالجتها." + }, + "seed": { + "name": "seed", + "tooltip": "بذرة عشوائية." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "الصور المعالجة" + } + } + }, + "ShuffleImageTextDataset": { + "display_name": "تبديل ترتيب مجموعة بيانات الصور والنصوص", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images", + "tooltip": "قائمة الصور لتبديل ترتيبها." + }, + "seed": { + "name": "seed", + "tooltip": "بذرة عشوائية." + }, + "texts": { + "name": "texts", + "tooltip": "قائمة النصوص لتبديل ترتيبها." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "الصور بعد تبديل الترتيب" + }, + "1": { + "name": "texts", + "tooltip": "النصوص بعد تبديل الترتيب" + } + } + }, "SkipLayerGuidanceDiT": { "description": "نسخة عامة من عقدة SkipLayerGuidance يمكن استخدامها مع كل نموذج DiT.", "display_name": "توجيه تخطي الطبقة DiT", @@ -10670,6 +12652,11 @@ "width": { "name": "العرض" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SplitAudioChannels": { @@ -10680,14 +12667,14 @@ "name": "صوت" } }, - "outputs": { - "0": { - "name": "يسار" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "يمين" + { + "tooltip": null } - } + ] }, "SplitImageWithAlpha": { "display_name": "فصل الصورة مع ألفا", @@ -10715,14 +12702,14 @@ "name": "الخطوة" } }, - "outputs": { - "0": { - "name": "Sigmas عالية" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "Sigmas منخفضة" + { + "tooltip": null } - } + ] }, "SplitSigmasDenoise": { "display_name": "فصل Sigmas إزالة التشويش", @@ -10734,14 +12721,14 @@ "name": "Sigmas" } }, - "outputs": { - "0": { - "name": "Sigmas عالية" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "Sigmas منخفضة" + { + "tooltip": null } - } + ] }, "StabilityAudioInpaint": { "description": "يحول جزءًا من عينة الصوت الحالية باستخدام تعليمات نصية.", @@ -11343,6 +13330,21 @@ } } }, + "StripWhitespace": { + "display_name": "إزالة الفراغات", + "inputs": { + "texts": { + "name": "النصوص", + "tooltip": "النص المراد معالجته." + } + }, + "outputs": { + "0": { + "name": "النصوص", + "tooltip": "النصوص المعالجة" + } + } + }, "StyleModelApply": { "display_name": "تطبيق نموذج النمط", "inputs": { @@ -11428,6 +13430,84 @@ } } }, + "TencentImageToModelNode": { + "display_name": "Hunyuan3D: من صورة إلى نموذج (احترافي)", + "inputs": { + "control_after_generate": { + "name": "التحكم بعد التوليد" + }, + "face_count": { + "name": "عدد الأوجه" + }, + "generate_type": { + "name": "نوع التوليد" + }, + "generate_type_pbr": { + "name": "PBR" + }, + "image": { + "name": "الصورة" + }, + "image_back": { + "name": "الصورة الخلفية" + }, + "image_left": { + "name": "الصورة اليسرى" + }, + "image_right": { + "name": "الصورة اليمنى" + }, + "model": { + "name": "النموذج", + "tooltip": "خيار LowPoly غير متوفر لنموذج `3.1`." + }, + "seed": { + "name": "البذرة", + "tooltip": "البذرة تتحكم فيما إذا كان يجب إعادة تشغيل العقدة؛ النتائج غير حتمية بغض النظر عن البذرة." + } + }, + "outputs": { + "0": { + "name": "ملف النموذج", + "tooltip": null + } + } + }, + "TencentTextToModelNode": { + "display_name": "Hunyuan3D: من نص إلى نموذج (احترافي)", + "inputs": { + "control_after_generate": { + "name": "التحكم بعد التوليد" + }, + "face_count": { + "name": "عدد الأوجه" + }, + "generate_type": { + "name": "نوع التوليد" + }, + "generate_type_pbr": { + "name": "PBR" + }, + "model": { + "name": "النموذج", + "tooltip": "خيار LowPoly غير متوفر لنموذج `3.1`." + }, + "prompt": { + "name": "الموجه", + "tooltip": "يدعم حتى ١٠٢٤ حرفاً." + }, + "seed": { + "name": "البذرة", + "tooltip": "البذرة تتحكم فيما إذا كان يجب إعادة تشغيل العقدة؛ النتائج غير حتمية بغض النظر عن البذرة." + } + }, + "outputs": { + "0": { + "name": "ملف النموذج", + "tooltip": null + } + } + }, "TextEncodeAceStepAudio": { "display_name": "TextEncodeAceStepAudio", "inputs": { @@ -11523,6 +13603,70 @@ } } }, + "TextEncodeZImageOmni": { + "display_name": "TextEncodeZImageOmni", + "inputs": { + "auto_resize_images": { + "name": "تغيير حجم الصور تلقائياً" + }, + "clip": { + "name": "clip" + }, + "image1": { + "name": "الصورة ١" + }, + "image2": { + "name": "الصورة ٢" + }, + "image3": { + "name": "الصورة ٣" + }, + "image_encoder": { + "name": "مُرمّز الصورة" + }, + "prompt": { + "name": "التوجيه" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TextToLowercase": { + "display_name": "تحويل النص إلى أحرف صغيرة", + "inputs": { + "texts": { + "name": "النصوص", + "tooltip": "النص المراد معالجته." + } + }, + "outputs": { + "0": { + "name": "النصوص", + "tooltip": "النصوص المعالجة" + } + } + }, + "TextToUppercase": { + "display_name": "تحويل النص إلى أحرف كبيرة", + "inputs": { + "texts": { + "name": "النصوص", + "tooltip": "النص المراد معالجته." + } + }, + "outputs": { + "0": { + "name": "النصوص", + "tooltip": "النصوص المعالجة" + } + } + }, "ThresholdMask": { "display_name": "قناع العتبة", "inputs": { @@ -11532,6 +13676,11 @@ "value": { "name": "القيمة" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "TomePatchModel": { @@ -11550,6 +13699,118 @@ } } }, + "TopazImageEnhance": { + "description": "تكبير وتحسين الصور بمعايير الصناعة.", + "display_name": "تحسين الصورة بواسطة Topaz", + "inputs": { + "color_preservation": { + "name": "حفظ الألوان", + "tooltip": "الحفاظ على الألوان الأصلية." + }, + "creativity": { + "name": "الإبداع" + }, + "crop_to_fill": { + "name": "قص لملء", + "tooltip": "بشكل افتراضي، يتم وضع الصورة في إطار عند اختلاف نسبة العرض إلى الارتفاع. فعّل هذا الخيار لقص الصورة لتملأ أبعاد الإخراج." + }, + "face_enhancement": { + "name": "تحسين الوجه", + "tooltip": "تحسين الوجوه (إن وجدت) أثناء المعالجة." + }, + "face_enhancement_creativity": { + "name": "إبداعية تحسين الوجه", + "tooltip": "تحديد مستوى الإبداع في تحسين الوجه." + }, + "face_enhancement_strength": { + "name": "قوة تحسين الوجه", + "tooltip": "التحكم في مدى وضوح الوجوه المحسنة مقارنة بالخلفية." + }, + "face_preservation": { + "name": "حفظ الوجه", + "tooltip": "الحفاظ على هوية الوجوه في الصورة." + }, + "image": { + "name": "الصورة" + }, + "model": { + "name": "النموذج" + }, + "output_height": { + "name": "ارتفاع الإخراج", + "tooltip": "القيمة صفر تعني الإخراج بنفس ارتفاع الصورة الأصلية أو عرض الإخراج." + }, + "output_width": { + "name": "عرض الإخراج", + "tooltip": "القيمة صفر تعني الحساب تلقائياً (عادةً سيكون الحجم الأصلي أو ارتفاع الإخراج إذا تم تحديده)." + }, + "prompt": { + "name": "الموجه", + "tooltip": "موجه نصي اختياري لإرشاد التكبير الإبداعي." + }, + "subject_detection": { + "name": "اكتشاف الموضوع" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TopazVideoEnhance": { + "description": "أعد إحياء الفيديو بتقنية التكبير والاستعادة القوية.", + "display_name": "Topaz Video Enhance", + "inputs": { + "dynamic_compression_level": { + "name": "مستوى الضغط الديناميكي", + "tooltip": "مستوى CQP." + }, + "interpolation_duplicate": { + "name": "إزالة الإطارات المكررة", + "tooltip": "تحليل الفيديو المدخل للبحث عن الإطارات المكررة وإزالتها." + }, + "interpolation_duplicate_threshold": { + "name": "حساسية اكتشاف التكرار", + "tooltip": "حساسية اكتشاف الإطارات المكررة." + }, + "interpolation_enabled": { + "name": "تمكين التداخل" + }, + "interpolation_frame_rate": { + "name": "معدل الإطارات", + "tooltip": "معدل الإطارات للإخراج." + }, + "interpolation_model": { + "name": "نموذج التداخل" + }, + "interpolation_slowmo": { + "name": "عامل الحركة البطيئة", + "tooltip": "عامل الحركة البطيئة المطبق على الفيديو المدخل. على سبيل المثال، 2 يجعل الإخراج أبطأ مرتين ويضاعف المدة." + }, + "upscaler_creativity": { + "name": "مستوى الإبداع", + "tooltip": "مستوى الإبداع (ينطبق فقط على Starlight (Astra) Creative)." + }, + "upscaler_enabled": { + "name": "تمكين التكبير" + }, + "upscaler_model": { + "name": "نموذج التكبير" + }, + "upscaler_resolution": { + "name": "دقة التكبير" + }, + "video": { + "name": "فيديو" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "TorchCompileModel": { "display_name": "نموذج TorchCompile", "inputs": { @@ -11577,6 +13838,10 @@ "name": "حجم الدُفعة", "tooltip": "حجم الدُفعة المستخدم في التدريب." }, + "bucket_mode": { + "name": "وضع الدلو", + "tooltip": "تمكين وضع دلو الدقة. عند التمكين، يتوقع وجود بيانات كامنة مجمعة مسبقًا من عقدة ResolutionBucket." + }, "control_after_generate": { "name": "التحكم بعد التوليد" }, @@ -11637,20 +13902,20 @@ "tooltip": "نوع البيانات المستخدم في التدريب." } }, - "outputs": { - "0": { - "name": "النموذج مع LoRA" + "outputs": [ + { + "tooltip": "نموذج مع LoRA مطبق" }, - "1": { - "name": "LoRA" + { + "tooltip": "أوزان LoRA" }, - "2": { - "name": "الخسارة" + { + "tooltip": "سجل الخسارة" }, - "3": { - "name": "الخطوات" + { + "tooltip": "إجمالي خطوات التدريب" } - } + ] }, "TrimAudioDuration": { "description": "قص موتر الصوت إلى النطاق الزمني المختار.", @@ -11667,6 +13932,11 @@ "name": "فهرس البداية", "tooltip": "وقت البداية بالثواني، يمكن أن يكون سالبًا للعد من النهاية (يدعم أجزاء الثانية)." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "TrimVideoLatent": { @@ -11708,23 +13978,62 @@ "TripoConversionNode": { "display_name": "Tripo: تحويل النموذج", "inputs": { + "animate_in_place": { + "name": "تحريك في المكان" + }, + "bake": { + "name": "خبز" + }, + "export_orientation": { + "name": "تصدير الاتجاه" + }, + "export_vertex_colors": { + "name": "تصدير ألوان الرؤوس" + }, "face_limit": { "name": "حد الوجه" }, + "fbx_preset": { + "name": "إعداد FBX" + }, + "flatten_bottom": { + "name": "تسوية القاع" + }, + "flatten_bottom_threshold": { + "name": "عتبة تسوية القاع" + }, + "force_symmetry": { + "name": "فرض التماثل" + }, "format": { "name": "التنسيق" }, "original_model_task_id": { "name": "original_model_task_id" }, + "pack_uv": { + "name": "تجميع UV" + }, + "part_names": { + "name": "أسماء الأجزاء" + }, + "pivot_to_center_bottom": { + "name": "محور إلى مركز القاع" + }, "quad": { "name": "رباعي" }, + "scale_factor": { + "name": "عامل المقياس" + }, "texture_format": { "name": "تنسيق النسيج" }, "texture_size": { "name": "حجم النسيج" + }, + "with_animation": { + "name": "مع الحركة" } } }, @@ -11734,6 +14043,9 @@ "face_limit": { "name": "حد الوجه" }, + "geometry_quality": { + "name": "جودة الهندسة" + }, "image": { "name": "الصورة" }, @@ -11786,6 +14098,9 @@ "face_limit": { "name": "حد_الوجه" }, + "geometry_quality": { + "name": "جودة الهندسة" + }, "image": { "name": "الصورة" }, @@ -11903,6 +14218,9 @@ "face_limit": { "name": "حد_الوجه" }, + "geometry_quality": { + "name": "جودة الهندسة" + }, "image_seed": { "name": "بذرة_الصورة" }, @@ -11981,6 +14299,25 @@ } } }, + "TruncateText": { + "display_name": "اقتطاع النص", + "inputs": { + "max_length": { + "name": "الحد الأقصى للطول", + "tooltip": "الحد الأقصى لطول النص." + }, + "texts": { + "name": "النصوص", + "tooltip": "النص المراد معالجته." + } + }, + "outputs": { + "0": { + "name": "النصوص", + "tooltip": "النصوص المعالجة" + } + } + }, "UNETLoader": { "display_name": "تحميل نموذج الانتشار", "inputs": { @@ -12122,6 +14459,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEDecodeHunyuan3D": { @@ -12139,6 +14481,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEDecodeTiled": { @@ -12186,6 +14533,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEEncodeForInpaint": { @@ -12264,6 +14616,63 @@ "steps": { "name": "الخطوات" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Veo3FirstLastFrameNode": { + "description": "إنشاء فيديو باستخدام الوصف النصي والإطارين الأول والأخير.", + "display_name": "Google Veo 3 من الإطار الأول والأخير إلى الفيديو", + "inputs": { + "aspect_ratio": { + "name": "نسبة العرض إلى الارتفاع", + "tooltip": "نسبة العرض إلى الارتفاع للفيديو الناتج" + }, + "control_after_generate": { + "name": "التحكم بعد التوليد" + }, + "duration": { + "name": "المدة", + "tooltip": "مدة الفيديو الناتج بالثواني" + }, + "first_frame": { + "name": "الإطار الأول", + "tooltip": "إطار البداية" + }, + "generate_audio": { + "name": "توليد الصوت", + "tooltip": "توليد صوت للفيديو." + }, + "last_frame": { + "name": "الإطار الأخير", + "tooltip": "إطار النهاية" + }, + "model": { + "name": "النموذج" + }, + "negative_prompt": { + "name": "الوصف السلبي", + "tooltip": "وصف نصي سلبي لتوجيه ما يجب تجنبه في الفيديو" + }, + "prompt": { + "name": "الوصف النصي", + "tooltip": "وصف نصي للفيديو" + }, + "resolution": { + "name": "الدقة" + }, + "seed": { + "name": "البذرة", + "tooltip": "بذرة لتوليد الفيديو" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Veo3VideoGenerationNode": { @@ -12392,6 +14801,166 @@ } } }, + "Vidu2ImageToVideoNode": { + "description": "إنشاء فيديو من صورة ونص اختياري.", + "display_name": "توليد فيديو من صورة Vidu2", + "inputs": { + "control_after_generate": { + "name": "التحكم بعد التوليد" + }, + "duration": { + "name": "المدة" + }, + "image": { + "name": "الصورة", + "tooltip": "صورة تُستخدم كإطار بداية للفيديو المُنتج." + }, + "model": { + "name": "النموذج" + }, + "movement_amplitude": { + "name": "سعة الحركة", + "tooltip": "سعة حركة العناصر داخل الإطار." + }, + "prompt": { + "name": "النص التوجيهي", + "tooltip": "نص اختياري لتوليد الفيديو (بحد أقصى 2000 حرف)." + }, + "resolution": { + "name": "الدقة" + }, + "seed": { + "name": "البذرة" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2ReferenceVideoNode": { + "description": "إنشاء فيديو من عدة صور مرجعية ونص توجيهي.", + "display_name": "توليد فيديو من مراجع Vidu2", + "inputs": { + "aspect_ratio": { + "name": "نسبة العرض إلى الارتفاع" + }, + "audio": { + "name": "الصوت", + "tooltip": "عند التفعيل، سيحتوي الفيديو على كلام وموسيقى خلفية مولدة بناءً على النص التوجيهي." + }, + "control_after_generate": { + "name": "التحكم بعد التوليد" + }, + "duration": { + "name": "المدة" + }, + "model": { + "name": "النموذج" + }, + "movement_amplitude": { + "name": "سعة الحركة", + "tooltip": "سعة حركة العناصر داخل الإطار." + }, + "prompt": { + "name": "النص التوجيهي", + "tooltip": "عند التفعيل، سيحتوي الفيديو على كلام وموسيقى خلفية مولدة بناءً على النص التوجيهي." + }, + "resolution": { + "name": "الدقة" + }, + "seed": { + "name": "البذرة" + }, + "subjects": { + "name": "الموضوعات", + "tooltip": "لكل موضوع، قدم حتى 3 صور مرجعية (بحد أقصى 7 صور لجميع الموضوعات). استخدمها في النص التوجيهي عبر @subject{subject_id}." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2StartEndToVideoNode": { + "description": "إنشاء فيديو من إطار بداية، إطار نهاية، وموجه نصي.", + "display_name": "توليد فيديو من إطار البداية/النهاية باستخدام Vidu2", + "inputs": { + "control_after_generate": { + "name": "التحكم بعد التوليد" + }, + "duration": { + "name": "المدة" + }, + "end_frame": { + "name": "إطار النهاية" + }, + "first_frame": { + "name": "إطار البداية" + }, + "model": { + "name": "النموذج" + }, + "movement_amplitude": { + "name": "سعة الحركة", + "tooltip": "سعة حركة العناصر داخل الإطار." + }, + "prompt": { + "name": "الموجه", + "tooltip": "وصف الموجه (بحد أقصى 2000 حرف)." + }, + "resolution": { + "name": "الدقة" + }, + "seed": { + "name": "البذرة" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2TextToVideoNode": { + "description": "إنشاء فيديو من موجه نصي", + "display_name": "توليد فيديو من نص باستخدام Vidu2", + "inputs": { + "aspect_ratio": { + "name": "نسبة العرض إلى الارتفاع" + }, + "background_music": { + "name": "موسيقى خلفية", + "tooltip": "هل تريد إضافة موسيقى خلفية للفيديو المُنتج." + }, + "control_after_generate": { + "name": "التحكم بعد التوليد" + }, + "duration": { + "name": "المدة" + }, + "model": { + "name": "النموذج" + }, + "prompt": { + "name": "الموجه", + "tooltip": "وصف نصي لتوليد الفيديو، بحد أقصى 2000 حرف." + }, + "resolution": { + "name": "الدقة" + }, + "seed": { + "name": "البذرة" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "ViduImageToVideoNode": { "description": "إنشاء فيديو من صورة ونص اختياري", "display_name": "إنشاء الفيديو من الصورة باستخدام Vidu", @@ -12580,6 +15149,11 @@ "voxel": { "name": "فوكسل" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VoxelToMeshBasic": { @@ -12591,6 +15165,11 @@ "voxel": { "name": "فوكسل" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Wan22FunControlToVideo": { @@ -12870,6 +15449,10 @@ "name": "خطوة السياق", "tooltip": "خطوة نافذة السياق؛ تنطبق فقط على الجداول المنتظمة." }, + "freenoise": { + "name": "freenoise", + "tooltip": "ما إذا كان سيتم تطبيق خلط ضوضاء FreeNoise، يحسن دمج النوافذ." + }, "fuse_method": { "name": "طريقة الدمج", "tooltip": "الطريقة المستخدمة لدمج نوافذ السياق." @@ -13210,6 +15793,10 @@ "name": "البذرة", "tooltip": "البذرة المستخدمة في التوليد." }, + "shot_type": { + "name": "نوع اللقطة", + "tooltip": "يحدد نوع اللقطة للفيديو الناتج، أي ما إذا كان الفيديو لقطة واحدة متواصلة أو لقطات متعددة مع انتقالات. هذا الخيار يعمل فقط عند تفعيل prompt_extend." + }, "watermark": { "name": "علامة مائية", "tooltip": "ما إذا كان سيتم إضافة علامة مائية \"تم إنشاؤها بالذكاء الاصطناعي\" على النتيجة." @@ -13221,6 +15808,196 @@ } } }, + "WanInfiniteTalkToVideo": { + "display_name": "WanInfiniteTalkToVideo", + "inputs": { + "audio_encoder_output_1": { + "name": "مخرجات ترميز الصوت ١" + }, + "audio_scale": { + "name": "مقياس الصوت" + }, + "clip_vision_output": { + "name": "مخرجات clip للرؤية" + }, + "height": { + "name": "الارتفاع" + }, + "length": { + "name": "الطول" + }, + "mode": { + "name": "الوضع" + }, + "model": { + "name": "النموذج" + }, + "model_patch": { + "name": "تصحيح النموذج" + }, + "motion_frame_count": { + "name": "عدد إطارات الحركة", + "tooltip": "عدد الإطارات السابقة المستخدمة كسياق للحركة." + }, + "negative": { + "name": "سلبي" + }, + "positive": { + "name": "إيجابي" + }, + "previous_frames": { + "name": "الإطارات السابقة" + }, + "start_image": { + "name": "صورة البداية" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "العرض" + } + }, + "outputs": { + "0": { + "name": "النموذج", + "tooltip": null + }, + "1": { + "name": "إيجابي", + "tooltip": null + }, + "2": { + "name": "سلبي", + "tooltip": null + }, + "3": { + "name": "الفضاء الكامن", + "tooltip": null + }, + "4": { + "name": "قص الصورة", + "tooltip": null + } + } + }, + "WanMoveConcatTrack": { + "display_name": "WanMoveConcatTrack", + "inputs": { + "tracks_1": { + "name": "المسارات_1" + }, + "tracks_2": { + "name": "المسارات_2" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanMoveTrackToVideo": { + "display_name": "WanMoveTrackToVideo", + "inputs": { + "batch_size": { + "name": "حجم الدفعة" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "height": { + "name": "الارتفاع" + }, + "length": { + "name": "الطول" + }, + "negative": { + "name": "سلبي" + }, + "positive": { + "name": "إيجابي" + }, + "start_image": { + "name": "صورة البداية" + }, + "strength": { + "name": "القوة", + "tooltip": "قوة تكييف المسار." + }, + "tracks": { + "name": "المسارات" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "العرض" + } + }, + "outputs": { + "0": { + "name": "إيجابي", + "tooltip": null + }, + "1": { + "name": "سلبي", + "tooltip": null + }, + "2": { + "name": "كامنة", + "tooltip": null + } + } + }, + "WanMoveTracksFromCoords": { + "display_name": "WanMoveTracksFromCoords", + "inputs": { + "track_coords": { + "name": "إحداثيات المسار" + }, + "track_mask": { + "name": "قناع المسار" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "طول المسار", + "tooltip": null + } + } + }, + "WanMoveVisualizeTracks": { + "display_name": "WanMoveVisualizeTracks", + "inputs": { + "circle_size": { + "name": "حجم الدائرة" + }, + "images": { + "name": "الصور" + }, + "line_resolution": { + "name": "دقة الخط" + }, + "line_width": { + "name": "عرض الخط" + }, + "opacity": { + "name": "الشفافية" + }, + "tracks": { + "name": "المسارات" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WanPhantomSubjectToVideo": { "display_name": "وان فانتوم موضوع إلى فيديو", "inputs": { @@ -13268,6 +16045,51 @@ } } }, + "WanReferenceVideoApi": { + "description": "استخدم الشخصية والصوت من مقاطع الفيديو المدخلة، مع الجمع مع التعليمات، لإنشاء فيديو جديد يحافظ على اتساق الشخصية.", + "display_name": "Wan Reference to Video", + "inputs": { + "control_after_generate": { + "name": "التحكم بعد التوليد" + }, + "duration": { + "name": "المدة" + }, + "model": { + "name": "النموذج" + }, + "negative_prompt": { + "name": "تعليمات سلبية", + "tooltip": "تعليمات سلبية تصف ما يجب تجنبه." + }, + "prompt": { + "name": "التعليمات", + "tooltip": "تعليمات تصف العناصر والميزات البصرية. يدعم الإنجليزية والصينية. استخدم معرفات مثل `character1` و `character2` للإشارة إلى الشخصيات المرجعية." + }, + "reference_videos": { + "name": "مقاطع الفيديو المرجعية" + }, + "seed": { + "name": "البذرة" + }, + "shot_type": { + "name": "نوع اللقطة", + "tooltip": "يحدد نوع اللقطة للفيديو الناتج، أي ما إذا كان الفيديو لقطة واحدة متواصلة أو عدة لقطات مع انتقالات." + }, + "size": { + "name": "الحجم" + }, + "watermark": { + "name": "علامة مائية", + "tooltip": "ما إذا كان سيتم إضافة علامة مائية تم إنشاؤها بالذكاء الاصطناعي إلى النتيجة." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WanSoundImageToVideo": { "display_name": "WanSoundImageToVideo", "inputs": { @@ -13446,6 +16268,10 @@ "name": "بذرة", "tooltip": "البذرة المستخدمة في التوليد." }, + "shot_type": { + "name": "نوع اللقطة", + "tooltip": "يحدد نوع اللقطة للفيديو الناتج، أي ما إذا كان الفيديو لقطة واحدة متواصلة أو عدة لقطات مع انتقالات. هذا الخيار يعمل فقط عند تفعيل prompt_extend." + }, "size": { "name": "الحجم" }, @@ -13571,6 +16397,43 @@ } } }, + "WavespeedFlashVSRNode": { + "description": "مُرقّي فيديو سريع وعالي الجودة يعزز الدقة ويعيد الوضوح للمقاطع منخفضة الدقة أو الضبابية.", + "display_name": "ترقية فيديو FlashVSR", + "inputs": { + "target_resolution": { + "name": "الدقة المستهدفة" + }, + "video": { + "name": "الفيديو" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WavespeedImageUpscaleNode": { + "description": "عزز دقة وجودة الصورة، وارفع الصور إلى دقة 4K أو 8K للحصول على نتائج حادة ومفصلة.", + "display_name": "ترقية صورة WaveSpeed", + "inputs": { + "image": { + "name": "الصورة" + }, + "model": { + "name": "النموذج" + }, + "target_resolution": { + "name": "الدقة المستهدفة" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WebcamCapture": { "display_name": "التقاط كاميرا ويب", "inputs": { @@ -13590,6 +16453,32 @@ } } }, + "ZImageFunControlnet": { + "display_name": "ZImageFunControlnet", + "inputs": { + "image": { + "name": "الصورة" + }, + "inpaint_image": { + "name": "صورة التلوين" + }, + "mask": { + "name": "القناع" + }, + "model": { + "name": "النموذج" + }, + "model_patch": { + "name": "تصحيح النموذج" + }, + "strength": { + "name": "القوة" + }, + "vae": { + "name": "vae" + } + } + }, "unCLIPCheckpointLoader": { "display_name": "محمل نقطة فحص unCLIP", "inputs": { @@ -13614,5 +16503,19 @@ "name": "القوة" } } + }, + "wanBlockSwap": { + "description": "NOP", + "display_name": "wanBlockSwap", + "inputs": { + "model": { + "name": "النموذج" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } } } diff --git a/src/locales/ar/settings.json b/src/locales/ar/settings.json index 8a4338f9f..6cfea92eb 100644 --- a/src/locales/ar/settings.json +++ b/src/locales/ar/settings.json @@ -29,12 +29,26 @@ "name": "صورة خلفية اللوحة", "tooltip": "رابط صورة لخلفية اللوحة. يمكنك النقر بزر الفأرة الأيمن على صورة في لوحة النتائج واختيار \"تعيين كخلفية\" لاستخدامها، أو رفع صورتك الخاصة باستخدام زر الرفع." }, + "Comfy_Canvas_LeftMouseClickBehavior": { + "name": "سلوك النقر بزر الفأرة الأيسر", + "options": { + "Panning": "التنقل", + "Select": "تحديد" + } + }, + "Comfy_Canvas_MouseWheelScroll": { + "name": "تمرير عجلة الفأرة", + "options": { + "Panning": "التنقل", + "Zoom in/out": "تكبير/تصغير" + } + }, "Comfy_Canvas_NavigationMode": { "name": "وضع تنقل اللوحة", "options": { + "Custom": "مخصص", "Drag Navigation": "سحب للتنقل", - "Standard (New)": "قياسي (جديد)", - "Custom": "مخصص" + "Standard (New)": "قياسي (جديد)" } }, "Comfy_Canvas_SelectionToolbox": { @@ -65,6 +79,17 @@ "Comfy_EnableWorkflowViewRestore": { "name": "حفظ واستعادة موقع اللوحة ومستوى التكبير في سير العمل" }, + "Comfy_Execution_PreviewMethod": { + "name": "طريقة المعاينة الحية", + "options": { + "auto": "تلقائي", + "default": "default", + "latent2rgb": "latent2rgb", + "none": "بدون", + "taesd": "taesd" + }, + "tooltip": "طريقة المعاينة الحية أثناء توليد الصورة. \"default\" تستخدم إعداد الخادم CLI." + }, "Comfy_FloatRoundingPrecision": { "name": "عدد أرقام التقريب العشرية لأدوات التحكم العائمة [0 = تلقائي]", "tooltip": "(يتطلب إعادة تحميل الصفحة)" @@ -86,6 +111,10 @@ "None": "لا شيء" } }, + "Comfy_Graph_LiveSelection": { + "name": "تحديد مباشر", + "tooltip": "عند التفعيل، يتم تحديد/إلغاء تحديد العقد في الوقت الفعلي أثناء سحب مستطيل التحديد، كما في أدوات التصميم الأخرى." + }, "Comfy_Graph_ZoomSpeed": { "name": "سرعة تكبير اللوحة" }, @@ -152,6 +181,15 @@ "name": "أدنى شدة إضاءة", "tooltip": "يحدد الحد الأدنى المسموح به لشدة الإضاءة في المشاهد ثلاثية الأبعاد." }, + "Comfy_Load3D_PLYEngine": { + "name": "محرك PLY", + "options": { + "fastply": "fastply", + "sparkjs": "sparkjs", + "threejs": "threejs" + }, + "tooltip": "اختر المحرك لتحميل ملفات PLY. \"threejs\" يستخدم PLYLoader الأصلي من Three.js (الأفضل لملفات PLY الشبكية). \"fastply\" يستخدم محمل محسن لملفات PLY السحابية النقطية بنسق ASCII. \"sparkjs\" يستخدم Spark.js لملفات PLY الخاصة بتوزيع Gaussian ثلاثي الأبعاد." + }, "Comfy_Load3D_ShowGrid": { "name": "رؤية الشبكة الابتدائية", "tooltip": "يتحكم في ظهور الشبكة بشكل افتراضي عند إنشاء عنصر ثلاثي الأبعاد جديد." @@ -167,10 +205,6 @@ "name": "تقييد تعديل الفرشاة إلى المحور السائد", "tooltip": "عند التمكين، تؤثر التعديلات على الحجم أو الصلابة فقط بناءً على الاتجاه الذي تتحرك فيه أكثر." }, - "Comfy_MaskEditor_UseNewEditor": { - "name": "استخدام محرر القناع الجديد", - "tooltip": "التحويل إلى واجهة محرر القناع الجديدة" - }, "Comfy_ModelLibrary_AutoLoadAll": { "name": "تحميل جميع مجلدات النماذج تلقائيًا", "tooltip": "إذا كانت صحيحة، سيتم تحميل جميع المجلدات عند فتح مكتبة النماذج (قد يسبب تأخيرًا أثناء التحميل). إذا كانت خاطئة، يتم تحميل مجلدات النماذج على مستوى الجذر فقط عند النقر عليها." @@ -238,6 +272,10 @@ "Comfy_Node_AllowImageSizeDraw": { "name": "عرض العرض × الارتفاع تحت معاينة الصورة" }, + "Comfy_Node_AlwaysShowAdvancedWidgets": { + "name": "عرض الأدوات المتقدمة دائمًا في جميع العقد", + "tooltip": "عند التفعيل، ستظهر الأدوات المتقدمة دائمًا في جميع العقد دون الحاجة لتوسيعها بشكل فردي." + }, "Comfy_Node_AutoSnapLinkToSlot": { "name": "التثبيت التلقائي للرابط إلى فتحة العقدة", "tooltip": "عند سحب رابط فوق عقدة، يتم تثبيت الرابط تلقائيًا على فتحة إدخال صالحة في العقدة" @@ -298,6 +336,10 @@ "name": "حجم تاريخ قائمة الانتظار", "tooltip": "العدد الأقصى للمهام المعروضة في تاريخ قائمة الانتظار." }, + "Comfy_Queue_QPOV2": { + "name": "استخدم قائمة انتظار المهام الموحدة في لوحة الأصول الجانبية", + "tooltip": "يستبدل لوحة قائمة انتظار المهام العائمة بقائمة انتظار مهام مكافئة مدمجة في لوحة الأصول الجانبية. يمكنك تعطيل هذا الخيار للعودة إلى تخطيط اللوحة العائمة." + }, "Comfy_Sidebar_Location": { "name": "موقع الشريط الجانبي", "options": { @@ -312,6 +354,13 @@ "small": "صغير" } }, + "Comfy_Sidebar_Style": { + "name": "نمط الشريط الجانبي", + "options": { + "connected": "متصل", + "floating": "عائم" + } + }, "Comfy_Sidebar_UnifiedWidth": { "name": "عرض موحد للشريط الجانبي" }, @@ -328,6 +377,14 @@ "Comfy_TreeExplorer_ItemPadding": { "name": "حشو عناصر مستعرض الشجرة" }, + "Comfy_UI_TabBarLayout": { + "name": "تخطيط شريط التبويبات", + "options": { + "Default": "افتراضي", + "Integrated": "مُدمج" + }, + "tooltip": "يتحكم في تخطيط شريط التبويبات. \"مُدمج\" ينقل عناصر المساعدة والتحكمات الخاصة بالمستخدم إلى منطقة شريط التبويبات." + }, "Comfy_UseNewMenu": { "name": "استخدام القائمة الجديدة", "options": { @@ -339,6 +396,14 @@ "Comfy_Validation_Workflows": { "name": "التحقق من صحة سير العمل" }, + "Comfy_VueNodes_AutoScaleLayout": { + "name": "تخطيط مقياس تلقائي (عقد Vue)", + "tooltip": "قياس مواضع العقد تلقائيًا عند التبديل إلى عرض Vue لمنع التداخل" + }, + "Comfy_VueNodes_Enabled": { + "name": "تصميم العقد الحديث (عقد Vue)", + "tooltip": "الحديث: عرض قائم على DOM مع تفاعلية محسّنة وميزات متصفح أصلية وتصميم مرئي محدث. الكلاسيكي: عرض لوحة تقليدي." + }, "Comfy_WidgetControlMode": { "name": "وضع التحكم في الودجت", "options": { @@ -376,6 +441,9 @@ "Comfy_Workflow_SortNodeIdOnSave": { "name": "ترتيب معرفات العقد عند حفظ سير العمل" }, + "Comfy_Workflow_WarnBlueprintOverwrite": { + "name": "طلب تأكيد لاستبدال مخطط الرسم البياني الفرعي الموجود" + }, "Comfy_Workflow_WorkflowTabsPosition": { "name": "موضع تبويبات سير العمل المفتوحة", "options": { @@ -407,37 +475,5 @@ }, "pysssss_SnapToGrid": { "name": "الالتصاق بالشبكة دائمًا" - }, - "Comfy_Canvas_LeftMouseClickBehavior": { - "name": "سلوك النقر بزر الفأرة الأيسر", - "options": { - "Panning": "التنقل", - "Select": "تحديد" - } - }, - "Comfy_Canvas_MouseWheelScroll": { - "name": "تمرير عجلة الفأرة", - "options": { - "Panning": "التنقل", - "Zoom in/out": "تكبير/تصغير" - } - }, - "Comfy_Sidebar_Style": { - "name": "نمط الشريط الجانبي", - "options": { - "floating": "عائم", - "connected": "متصل" - } - }, - "Comfy_VueNodes_AutoScaleLayout": { - "name": "تخطيط مقياس تلقائي (عقد Vue)", - "tooltip": "قياس مواضع العقد تلقائيًا عند التبديل إلى عرض Vue لمنع التداخل" - }, - "Comfy_VueNodes_Enabled": { - "name": "تصميم العقد الحديث (عقد Vue)", - "tooltip": "الحديث: عرض قائم على DOM مع تفاعلية محسّنة وميزات متصفح أصلية وتصميم مرئي محدث. الكلاسيكي: عرض لوحة تقليدي." - }, - "Comfy_Workflow_WarnBlueprintOverwrite": { - "name": "طلب تأكيد لاستبدال مخطط الرسم البياني الفرعي الموجود" } } diff --git a/src/locales/en/commands.json b/src/locales/en/commands.json index e838e3aeb..60f906db0 100644 --- a/src/locales/en/commands.json +++ b/src/locales/en/commands.json @@ -1,4 +1,40 @@ { + "Comfy-Desktop_CheckForUpdates": { + "label": "Check for Updates" + }, + "Comfy-Desktop_Folders_OpenCustomNodesFolder": { + "label": "Open Custom Nodes Folder" + }, + "Comfy-Desktop_Folders_OpenInputsFolder": { + "label": "Open Inputs Folder" + }, + "Comfy-Desktop_Folders_OpenLogsFolder": { + "label": "Open Logs Folder" + }, + "Comfy-Desktop_Folders_OpenModelConfig": { + "label": "Open extra_model_paths.yaml" + }, + "Comfy-Desktop_Folders_OpenModelsFolder": { + "label": "Open Models Folder" + }, + "Comfy-Desktop_Folders_OpenOutputsFolder": { + "label": "Open Outputs Folder" + }, + "Comfy-Desktop_OpenDevTools": { + "label": "Open DevTools" + }, + "Comfy-Desktop_OpenUserGuide": { + "label": "Desktop User Guide" + }, + "Comfy-Desktop_Quit": { + "label": "Quit" + }, + "Comfy-Desktop_Reinstall": { + "label": "Reinstall" + }, + "Comfy-Desktop_Restart": { + "label": "Restart" + }, "Comfy_3DViewer_Open3DViewer": { "label": "Open 3D Viewer (Beta) for Selected Node" }, @@ -155,18 +191,30 @@ "Comfy_Manager_ShowUpdateAvailablePacks": { "label": "Check for Custom Node Updates" }, - "Comfy_Manager_ToggleManagerProgressDialog": { - "label": "Toggle the Custom Nodes Manager Progress Bar" - }, "Comfy_MaskEditor_BrushSize_Decrease": { "label": "Decrease Brush Size in MaskEditor" }, "Comfy_MaskEditor_BrushSize_Increase": { "label": "Increase Brush Size in MaskEditor" }, + "Comfy_MaskEditor_ColorPicker": { + "label": "Open Color Picker in MaskEditor" + }, + "Comfy_MaskEditor_Mirror_Horizontal": { + "label": "Mirror Horizontal in MaskEditor" + }, + "Comfy_MaskEditor_Mirror_Vertical": { + "label": "Mirror Vertical in MaskEditor" + }, "Comfy_MaskEditor_OpenMaskEditor": { "label": "Open Mask Editor for Selected Node" }, + "Comfy_MaskEditor_Rotate_Left": { + "label": "Rotate Left in MaskEditor" + }, + "Comfy_MaskEditor_Rotate_Right": { + "label": "Rotate Right in MaskEditor" + }, "Comfy_Memory_UnloadModels": { "label": "Unload Models" }, @@ -188,6 +236,9 @@ "Comfy_PublishSubgraph": { "label": "Publish Subgraph" }, + "Comfy_Queue_ToggleOverlay": { + "label": "Toggle Job History" + }, "Comfy_QueuePrompt": { "label": "Queue Prompt" }, @@ -203,6 +254,9 @@ "Comfy_RefreshNodeDefinitions": { "label": "Refresh Node Definitions" }, + "Comfy_RenameWorkflow": { + "label": "Rename Workflow" + }, "Comfy_SaveWorkflow": { "label": "Save Workflow" }, @@ -222,7 +276,10 @@ "label": "Help Center" }, "Comfy_ToggleLinear": { - "label": "toggle linear mode" + "label": "Toggle Simple Mode" + }, + "Comfy_ToggleQPOV2": { + "label": "Toggle Queue Panel V2" }, "Comfy_ToggleTheme": { "label": "Toggle Theme (Dark/Light)" diff --git a/src/locales/en/main.json b/src/locales/en/main.json index e9e013edd..061056d57 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -1,6 +1,7 @@ { "g": { "user": "User", + "you": "You", "currentUser": "Current user", "empty": "Empty", "noWorkflowsFound": "No workflows found.", @@ -10,10 +11,13 @@ "downloadVideo": "Download video", "editOrMaskImage": "Edit or mask image", "editImage": "Edit image", + "decrement": "Decrement", "deleteImage": "Delete image", "deleteAudioFile": "Delete audio file", + "increment": "Increment", "removeImage": "Remove image", "removeVideo": "Remove video", + "removeTag": "Remove tag", "chart": "Chart", "chartLowercase": "chart", "file": "file", @@ -40,8 +44,6 @@ "comfy": "Comfy", "refresh": "Refresh", "refreshNode": "Refresh Node", - "vitePreloadErrorTitle": "New Version Available", - "vitePreloadErrorMessage": "A new version of the app has been released. Would you like to reload?\nIf not, some parts of the app might not work as expected.\nFeel free to decline and save your progress before reloading.", "terminal": "Terminal", "logs": "Logs", "videoFailedToLoad": "Video failed to load", @@ -85,6 +87,7 @@ "reportIssueTooltip": "Submit the error report to Comfy Org", "reportSent": "Report Submitted", "copyToClipboard": "Copy to Clipboard", + "copyAll": "Copy All", "openNewIssue": "Open New Issue", "showReport": "Show Report", "imageFailedToLoad": "Image failed to load", @@ -100,6 +103,12 @@ "off": "Off", "cancel": "Cancel", "close": "Close", + "closeDialog": "Close dialog", + "showLeftPanel": "Show left panel", + "hideLeftPanel": "Hide left panel", + "showRightPanel": "Show right panel", + "hideRightPanel": "Hide right panel", + "or": "or", "pressKeysForNewBinding": "Press keys for new binding", "defaultBanner": "default banner", "enableOrDisablePack": "Enable or disable pack", @@ -121,19 +130,28 @@ "customBackground": "Custom Background", "settings": "Settings", "searchWorkflows": "Search Workflows", + "scrollLeft": "Scroll Left", + "scrollRight": "Scroll Right", "searchSettings": "Search Settings", "searchNodes": "Search Nodes", "searchModels": "Search Models", "searchKeybindings": "Search Keybindings", "searchExtensions": "Search Extensions", "search": "Search", + "searchPlaceholder": "Search...", "noResultsFound": "No Results Found", + "noResults": "No Results", "searchFailedMessage": "We couldn't find any settings matching your search. Try adjusting your search terms.", "noTasksFound": "No Tasks Found", "noTasksFoundMessage": "There are no tasks in the queue.", "newFolder": "New Folder", "enableAll": "Enable All", "disableAll": "Disable All", + "enableSelected": "Enable Selected", + "disableSelected": "Disable Selected", + "disableThirdParty": "Disable Third-Party", + "core": "Core", + "custom": "Custom", "command": "Command", "keybinding": "Keybinding", "upload": "Upload", @@ -160,6 +178,7 @@ "webcamRequiresTLS": "Unable to load webcam. TLS is required when not on localhost. Error: {error}", "turnOnCamera": "Turn on Camera", "nodes": "Nodes", + "nodesCount": "{count} nodes | {count} node | {count} nodes", "community": "Community", "all": "All", "versionMismatchWarning": "Version Compatibility Warning", @@ -180,18 +199,21 @@ "source": "Source", "filter": "Filter", "apply": "Apply", + "use": "Use", "enabled": "Enabled", "installed": "Installed", "restart": "Restart", "missing": "Missing", "inProgress": "In progress", "completed": "Completed", + "downloading": "Downloading", "interrupted": "Interrupted", "queued": "Queued", "running": "Running", "failed": "Failed", "cancelled": "Cancelled", "job": "Job", + "asset": "{count} assets | {count} asset | {count} assets", "untitled": "Untitled", "emDash": "—", "enabling": "Enabling {id}", @@ -208,6 +230,15 @@ "copy": "Copy", "copyJobId": "Copy Job ID", "copied": "Copied", + "relativeTime": { + "now": "now", + "yearsAgo": "{count}y ago", + "monthsAgo": "{count}mo ago", + "weeksAgo": "{count}w ago", + "daysAgo": "{count}d ago", + "hoursAgo": "{count}h ago", + "minutesAgo": "{count}min ago" + }, "jobIdCopied": "Job ID copied to clipboard", "failedToCopyJobId": "Failed to copy job ID", "imageUrl": "Image URL", @@ -260,11 +291,12 @@ "1x": "1x", "2x": "2x", "beta": "BETA", + "nightly": "NIGHTLY", "profile": "Profile", "noItems": "No items" }, "manager": { - "title": "Custom Nodes Manager", + "title": "Nodes Manager", "legacyMenuNotAvailable": "Legacy manager menu is not available, defaulting to the new manager menu.", "legacyManagerUI": "Use Legacy UI", "legacyManagerUIDescription": "To use the legacy Manager UI, start ComfyUI with --enable-manager-legacy-ui", @@ -293,6 +325,8 @@ "uninstall": "Uninstall", "uninstalling": "Uninstalling {id}", "update": "Update", + "tryUpdate": "Try Update", + "tryUpdateTooltip": "Pull latest changes from repository. Nightly versions may have updates that cannot be detected automatically.", "uninstallSelected": "Uninstall Selected", "updateSelected": "Update Selected", "updateAll": "Update All", @@ -371,6 +405,10 @@ "warningTooltip": "This package may have compatibility issues with your current environment" } }, + "importFailed": { + "title": "Import Failed", + "copyError": "Copy Error" + }, "issueReport": { "helpFix": "Help Fix This" }, @@ -422,6 +460,8 @@ "Save Image": "Save Image", "Rename": "Rename", "RenameWidget": "Rename Widget", + "FavoriteWidget": "Favorite Widget", + "UnfavoriteWidget": "Unfavorite Widget", "Copy": "Copy", "Duplicate": "Duplicate", "Paste": "Paste", @@ -447,7 +487,8 @@ "Horizontal": "Horizontal", "Vertical": "Vertical", "new": "new", - "deprecated": "deprecated" + "deprecated": "deprecated", + "Extensions": "Extensions" }, "icon": { "bookmark": "Bookmark", @@ -474,6 +515,7 @@ "notSupported": { "title": "Your device is not supported", "message": "Only following devices are supported:", + "illustrationAlt": "Sad girl illustration", "learnMore": "Learn More", "reportIssue": "Report Issue", "supportedDevices": { @@ -500,10 +542,12 @@ "title": "Choose your hardware setup", "recommended": "RECOMMENDED", "nvidiaSubtitle": "NVIDIA CUDA", + "amdSubtitle": "AMD ROCm™", "cpuSubtitle": "CPU Mode", "manualSubtitle": "Manual Setup", "appleMetalDescription": "Leverages your Mac's GPU for faster speed and a better overall experience", "nvidiaDescription": "Use your NVIDIA GPU with CUDA acceleration for the best performance.", + "amdDescription": "Use your AMD GPU with ROCm™ acceleration for the best performance.", "cpuDescription": "Use CPU mode for compatibility when GPU acceleration is not available", "manualDescription": "Configure ComfyUI manually for advanced setups or unsupported hardware" }, @@ -629,7 +673,9 @@ "serverConfig": { "modifiedConfigs": "You have modified the following server configurations. Restart to apply changes.", "revertChanges": "Revert Changes", - "restart": "Restart" + "restart": "Restart", + "restartRequiredToastSummary": "Restart required", + "restartRequiredToastDetail": "Restart the app to apply server configuration changes." }, "shape": { "default": "Default", @@ -640,6 +686,7 @@ "box": "Box" }, "sideToolbar": { + "sidebar": "Sidebar", "themeToggle": "Toggle Theme", "helpCenter": "Help Center", "logout": "Logout", @@ -657,10 +704,11 @@ "filterImage": "Image", "filterVideo": "Video", "filterAudio": "Audio", - "filter3D": "3D" + "filter3D": "3D", + "filterText": "Text" }, "backToAssets": "Back to all assets", - "searchAssets": "Search assets...", + "searchAssets": "Search Assets", "labels": { "queue": "Queue", "nodes": "Nodes", @@ -676,6 +724,9 @@ "noFilesFound": "No files found", "noImportedFiles": "No imported files found", "noGeneratedFiles": "No generated files found", + "generatedAssetsHeader": "Generated assets", + "importedAssetsHeader": "Imported assets", + "activeJobStatus": "Active job: {status}", "noFilesFoundMessage": "Upload files or generate content to see them here", "browseTemplates": "Browse example templates", "openWorkflow": "Open workflow in local file system", @@ -707,6 +758,8 @@ "colonPercent": ": {percent}", "currentNode": "Current node:", "viewAllJobs": "View all jobs", + "viewList": "List view", + "viewGrid": "Grid view", "running": "running", "preview": "Preview", "interruptAll": "Interrupt all running jobs", @@ -722,6 +775,8 @@ "filterCurrentWorkflow": "Current workflow", "sortJobs": "Sort jobs", "sortBy": "Sort by", + "activeJobs": "{count} active job | {count} active jobs", + "activeJobsShort": "{count} active | {count} active", "activeJobsSuffix": "active jobs", "jobQueue": "Job Queue", "expandCollapsedQueue": "Expand job queue", @@ -755,9 +810,10 @@ } }, "helpCenter": { + "feedback": "Give Feedback", "docs": "Docs", "github": "Github", - "helpFeedback": "Help & Feedback", + "help": "Help & Support", "managerExtension": "Manager Extension", "more": "More...", "whatsNew": "What's New?", @@ -768,13 +824,20 @@ "updateAvailable": "Update", "desktopUserGuide": "Desktop User Guide", "openDevTools": "Open Dev Tools", - "reinstall": "Re-Install" + "reinstall": "Re-Install", + "updateComfyUI": "Update ComfyUI", + "updateComfyUIStarted": "Update Started", + "updateComfyUIStartedDetail": "ComfyUI update has been queued. Please wait...", + "updateComfyUISuccess": "Update Complete", + "updateComfyUISuccessDetail": "ComfyUI has been updated. Rebooting...", + "updateComfyUIFailed": "Failed to update ComfyUI. Please try again." }, "releaseToast": { - "newVersionAvailable": "New Version Available!", - "whatsNew": "What's New?", + "newVersionAvailable": "New update is out!", + "whatsNew": "See what's new", "skip": "Skip", - "update": "Update" + "update": "Update", + "description": "Check out the latest improvements and features in this update." }, "menu": { "hideMenu": "Hide Menu", @@ -803,8 +866,10 @@ "dark": "Dark", "light": "Light", "manageExtensions": "Manage Extensions", + "customNodesManager": "Custom Nodes Manager", "settings": "Settings", "help": "Help", + "helpAndFeedback": "Help & Feedback", "queue": "Queue Panel" }, "tabMenu": { @@ -851,7 +916,7 @@ "noResultsHint": "Try adjusting your search or filters", "allTemplates": "All Templates", "modelFilter": "Model Filter", - "useCaseFilter": "Use Case", + "useCaseFilter": "Tasks", "licenseFilter": "License", "modelsSelected": "{count} Models", "useCasesSelected": "{count} Use Cases", @@ -860,6 +925,7 @@ "resultsCount": "Showing {count} of {total} templates", "sort": { "recommended": "Recommended", + "popular": "Popular", "alphabetical": "A → Z", "newest": "Newest", "searchPlaceholder": "Search...", @@ -943,6 +1009,10 @@ "clear": "Clear", "undo": "Undo", "redo": "Redo", + "rotateLeft": "Rotate Left", + "rotateRight": "Rotate Right", + "mirrorHorizontal": "Mirror Horizontal", + "mirrorVertical": "Mirror Vertical", "clickToResetZoom": "Click to reset zoom", "brushSettings": "Brush Settings", "brushShape": "Brush Shape", @@ -999,6 +1069,7 @@ "initializingAlmostReady": "Initializing - Almost ready", "inQueue": "In queue...", "jobAddedToQueue": "Job added to queue", + "completedIn": "Finished in {duration}", "jobMenu": { "openAsWorkflowNewTab": "Open as workflow in new tab", "openWorkflowNewTab": "Open workflow in new tab", @@ -1014,6 +1085,8 @@ "copyErrorMessage": "Copy error message", "reportError": "Report error" }, + "toggleJobHistory": "Toggle Job History", + "jobHistory": "Job History", "jobList": { "undated": "Undated", "sortMostRecent": "Most recent", @@ -1049,6 +1122,18 @@ "Edit": "Edit", "View": "View", "Help": "Help", + "Check for Updates": "Check for Updates", + "Open Custom Nodes Folder": "Open Custom Nodes Folder", + "Open Inputs Folder": "Open Inputs Folder", + "Open Logs Folder": "Open Logs Folder", + "Open extra_model_paths_yaml": "Open extra_model_paths.yaml", + "Open Models Folder": "Open Models Folder", + "Open Outputs Folder": "Open Outputs Folder", + "Open DevTools": "Open DevTools", + "Desktop User Guide": "Desktop User Guide", + "Quit": "Quit", + "Reinstall": "Reinstall", + "Restart": "Restart", "Open 3D Viewer (Beta) for Selected Node": "Open 3D Viewer (Beta) for Selected Node", "Experimental: Browse Model Assets": "Experimental: Browse Model Assets", "Browse Templates": "Browse Templates", @@ -1101,10 +1186,14 @@ "Manager Menu (Legacy)": "Manager Menu (Legacy)", "Install Missing Custom Nodes": "Install Missing Custom Nodes", "Check for Custom Node Updates": "Check for Custom Node Updates", - "Toggle the Custom Nodes Manager Progress Bar": "Toggle the Custom Nodes Manager Progress Bar", "Decrease Brush Size in MaskEditor": "Decrease Brush Size in MaskEditor", "Increase Brush Size in MaskEditor": "Increase Brush Size in MaskEditor", + "Open Color Picker in MaskEditor": "Open Color Picker in MaskEditor", + "Mirror Horizontal in MaskEditor": "Mirror Horizontal in MaskEditor", + "Mirror Vertical in MaskEditor": "Mirror Vertical in MaskEditor", "Open Mask Editor for Selected Node": "Open Mask Editor for Selected Node", + "Rotate Left in MaskEditor": "Rotate Left in MaskEditor", + "Rotate Right in MaskEditor": "Rotate Right in MaskEditor", "Unload Models": "Unload Models", "Unload Models and Execution Cache": "Unload Models and Execution Cache", "New": "New", @@ -1112,25 +1201,26 @@ "Manager": "Manager", "Open": "Open", "Publish": "Publish", + "Job History": "Job History", "Queue Prompt": "Queue Prompt", "Queue Prompt (Front)": "Queue Prompt (Front)", "Queue Selected Output Nodes": "Queue Selected Output Nodes", "Redo": "Redo", "Refresh Node Definitions": "Refresh Node Definitions", + "Rename": "Rename", "Save": "Save", "Save As": "Save As", "Show Settings Dialog": "Show Settings Dialog", "Experimental: Enable AssetAPI": "Experimental: Enable AssetAPI", "Canvas Performance": "Canvas Performance", "Help Center": "Help Center", - "toggle linear mode": "toggle linear mode", + "Toggle Simple Mode": "Toggle Simple Mode", + "Toggle Queue Panel V2": "Toggle Queue Panel V2", "Toggle Theme (Dark/Light)": "Toggle Theme (Dark/Light)", "Undo": "Undo", "Open Sign In Dialog": "Open Sign In Dialog", "Sign Out": "Sign Out", - "Experimental: Enable Vue Nodes": "Experimental: Enable Nodes 2.0", - "Experimental: Enable Nodes 2.0": "Experimental: Enable Nodes 2.0", - "Experimental: Disable Nodes 2.0": "Experimental: Disable Nodes 2.0", + "Experimental: Enable Nodes 2_0": "Experimental: Enable Nodes 2.0", "Close Current Workflow": "Close Current Workflow", "Next Opened Workflow": "Next Opened Workflow", "Previous Opened Workflow": "Previous Opened Workflow", @@ -1175,7 +1265,6 @@ "Locale": "Locale", "Mask Editor": "Mask Editor", "BrushAdjustment": "Brush Adjustment", - "NewEditor": "New Editor", "ModelLibrary": "Model Library", "NodeLibrary": "Node Library", "Node Search Box": "Node Search Box", @@ -1210,7 +1299,11 @@ "PlanCredits": "Plan & Credits", "Vue Nodes": "Nodes 2.0", "VueNodes": "Nodes 2.0", - "Nodes 2_0": "Nodes 2.0" + "Nodes 2_0": "Nodes 2.0", + "Execution": "Execution", + "PLY": "PLY", + "Workspace": "Workspace", + "Other": "Other" }, "serverConfigItems": { "listen": { @@ -1317,6 +1410,10 @@ "disable-metadata": { "name": "Disable saving prompt metadata in files." }, + "enable-manager-legacy-ui": { + "name": "Use legacy Manager UI", + "tooltip": "Uses the legacy ComfyUI-Manager UI instead of the new UI." + }, "disable-all-custom-nodes": { "name": "Disable loading all custom nodes." }, @@ -1345,16 +1442,21 @@ "_for_testing": "_for_testing", "custom_sampling": "custom_sampling", "noise": "noise", + "dataset": "dataset", + "text": "text", + "image": "image", "sampling": "sampling", "schedulers": "schedulers", "audio": "audio", "conditioning": "conditioning", "loaders": "loaders", "guiders": "guiders", + "latent": "latent", + "mask": "mask", "api node": "api node", + "Bria": "Bria", "video": "video", "ByteDance": "ByteDance", - "image": "image", "preprocessors": "preprocessors", "utils": "utils", "string": "string", @@ -1365,29 +1467,29 @@ "chroma_radiance": "chroma_radiance", "attention_experiments": "attention_experiments", "flux": "flux", + "kandinsky5": "kandinsky5", "hooks": "hooks", "combine": "combine", + "logic": "logic", "cond single": "cond single", "context": "context", "controlnet": "controlnet", "inpaint": "inpaint", "scheduling": "scheduling", "create": "create", - "mask": "mask", "deprecated": "deprecated", "debug": "debug", "model": "model", - "latent": "latent", "3d": "3d", "ltxv": "ltxv", + "qwen": "qwen", "sd3": "sd3", "unet": "unet", "sigmas": "sigmas", "BFL": "BFL", "Gemini": "Gemini", - "text": "text", - "gligen": "gligen", "video_models": "video_models", + "gligen": "gligen", "sd": "sd", "Ideogram": "Ideogram", "postprocessing": "postprocessing", @@ -1403,6 +1505,7 @@ "lotus": "lotus", "LTXV": "LTXV", "Luma": "Luma", + "Meshy": "Meshy", "MiniMax": "MiniMax", "model_specific": "model_specific", "Moonvalley Marey": "Moonvalley Marey", @@ -1410,10 +1513,8 @@ "Sora": "Sora", "cond pair": "cond pair", "photomaker": "photomaker", - "Pika": "Pika", "PixVerse": "PixVerse", "primitive": "primitive", - "qwen": "qwen", "Recraft": "Recraft", "edit_models": "edit_models", "Rodin": "Rodin", @@ -1427,13 +1528,16 @@ "stable_cascade": "stable_cascade", "3d_models": "3d_models", "style_model": "style_model", + "Tencent": "Tencent", "Topaz": "Topaz", "Tripo": "Tripo", "Veo": "Veo", "Vidu": "Vidu", "": "", "camera": "camera", - "Wan": "Wan" + "Wan": "Wan", + "WaveSpeed": "WaveSpeed", + "zimage": "zimage" }, "dataTypes": { "*": "*", @@ -1447,6 +1551,9 @@ "CLIP_VISION": "CLIP_VISION", "CLIP_VISION_OUTPUT": "CLIP_VISION_OUTPUT", "COMBO": "COMBO", + "COMFY_AUTOGROW_V3": "COMFY_AUTOGROW_V3", + "COMFY_DYNAMICCOMBO_V3": "COMFY_DYNAMICCOMBO_V3", + "COMFY_MATCHTYPE_V3": "COMFY_MATCHTYPE_V3", "CONDITIONING": "CONDITIONING", "CONTROL_NET": "CONTROL_NET", "FLOAT": "FLOAT", @@ -1457,12 +1564,12 @@ "HOOK_KEYFRAMES": "HOOK_KEYFRAMES", "HOOKS": "HOOKS", "IMAGE": "IMAGE", + "IMAGECOMPARE": "IMAGECOMPARE", "INT": "INT", "LATENT": "LATENT", "LATENT_OPERATION": "LATENT_OPERATION", "LATENT_UPSCALE_MODEL": "LATENT_UPSCALE_MODEL", "LOAD_3D": "LOAD_3D", - "LOAD_3D_ANIMATION": "LOAD_3D_ANIMATION", "LOAD3D_CAMERA": "LOAD3D_CAMERA", "LORA_MODEL": "LORA_MODEL", "LOSS_MAP": "LOSS_MAP", @@ -1470,6 +1577,8 @@ "LUMA_REF": "LUMA_REF", "MASK": "MASK", "MESH": "MESH", + "MESHY_RIGGED_TASK_ID": "MESHY_RIGGED_TASK_ID", + "MESHY_TASK_ID": "MESHY_TASK_ID", "MODEL": "MODEL", "MODEL_PATCH": "MODEL_PATCH", "MODEL_TASK_ID": "MODEL_TASK_ID", @@ -1489,6 +1598,7 @@ "STYLE_MODEL": "STYLE_MODEL", "SVG": "SVG", "TIMESTEPS_RANGE": "TIMESTEPS_RANGE", + "TRACKS": "TRACKS", "UPSCALE_MODEL": "UPSCALE_MODEL", "VAE": "VAE", "VIDEO": "VIDEO", @@ -1536,6 +1646,11 @@ "updateFrontend": "Update Frontend", "dismiss": "Dismiss" }, + "loadWorkflowWarning": { + "outdatedVersion": "This workflow was created with a newer version of ComfyUI ({version}). Some nodes may not work correctly.", + "outdatedVersionGeneric": "This workflow was created with a newer version of ComfyUI. Some nodes may not work correctly.", + "coreNodesFromVersion": "Core nodes from version {version}:" + }, "errorDialog": { "defaultTitle": "An error occurred", "loadWorkflowTitle": "Loading aborted due to error reloading workflow data", @@ -1567,6 +1682,9 @@ "errorMessage": "Failed to copy to clipboard", "errorNotSupported": "Clipboard API not supported in your browser" }, + "imageCompare": { + "noImages": "No images to compare" + }, "load3d": { "switchCamera": "Switch Camera", "showGrid": "Show Grid", @@ -1581,6 +1699,7 @@ "loadingModel": "Loading 3D Model...", "upDirection": "Up Direction", "materialMode": "Material Mode", + "showSkeleton": "Show Skeleton", "scene": "Scene", "model": "Model", "camera": "Camera", @@ -1597,6 +1716,7 @@ "normal": "Normal", "wireframe": "Wireframe", "original": "Original", + "pointCloud": "Point Cloud", "depth": "Depth", "lineart": "Lineart" }, @@ -1629,10 +1749,22 @@ "unsupportedFileType": "Unsupported file type (supports .gltf, .glb, .obj, .fbx, .stl)", "uploadingModel": "Uploading 3D model..." }, + "imageCrop": { + "loading": "Loading...", + "noInputImage": "No input image connected", + "cropPreviewAlt": "Crop preview" + }, + "boundingBox": { + "x": "X", + "y": "Y", + "width": "Width", + "height": "Height" + }, "toastMessages": { "nothingToQueue": "Nothing to queue", "pleaseSelectOutputNodes": "Please select output nodes", "failedToQueue": "Failed to queue", + "failedToSaveDraft": "Failed to save workflow draft", "failedExecutionPathResolution": "Could not resolve path to selected nodes", "no3dScene": "No 3D scene to apply texture", "failedToApplyTexture": "Failed to apply texture", @@ -1647,6 +1779,7 @@ "pleaseSelectNodesToGroup": "Please select the nodes (or other groups) to create a group for", "emptyCanvas": "Empty canvas", "fileUploadFailed": "File upload failed", + "fileTooLarge": "File too large ({size} MB). Maximum supported size is {maxSize} MB", "unableToGetModelFilePath": "Unable to get model file path", "couldNotDetermineFileType": "Could not determine file type", "errorLoadingModel": "Error loading model", @@ -1658,6 +1791,7 @@ "noTemplatesToExport": "No templates to export", "failedToFetchLogs": "Failed to fetch server logs", "migrateToLitegraphReroute": "Reroute nodes will be removed in future versions. Click to migrate to litegraph-native reroute.", + "legacyMaskEditorDeprecated": "The legacy mask editor is deprecated and will be removed soon.", "userNotAuthenticated": "User not authenticated", "failedToFetchBalance": "Failed to fetch balance: {error}", "failedToCreateCustomer": "Failed to create customer: {error}", @@ -1842,15 +1976,47 @@ "maxAmount": "(Max. $1,000 USD)", "buyNow": "Buy now", "seeDetails": "See details", - "topUp": "Top Up" + "topUp": "Top Up", + "addMoreCredits": "Add more credits", + "addMoreCreditsToRun": "Add more credits to run", + "insufficientWorkflowMessage": "You don't have enough credits to run this workflow.", + "creditsDescription": "Credits are used to run workflows or partner nodes.", + "howManyCredits": "How many credits would you like to add?", + "usdAmount": "${amount}", + "videosEstimate": "~{count} videos*", + "templateNote": "*Generated with Wan Fun Control template", + "buy": "Buy", + "purchaseError": "Purchase Failed", + "purchaseErrorDetail": "Failed to purchase credits: {error}", + "unknownError": "An unknown error occurred", + "viewPricing": "View pricing details", + "youPay": "Amount (USD)", + "youGet": "Credits", + "buyCredits": "Continue to payment", + "minimumPurchase": "${amount} minimum ({credits} credits)", + "maximumAmount": "${amount} max.", + "minRequired": "{credits} credits minimum", + "maxAllowed": "{credits} credits maximum.", + "creditsPerDollar": "credits per dollar", + "amountToPayLabel": "Amount to pay in dollars", + "creditsToReceiveLabel": "Credits to receive", + "selectAmount": "Select amount", + "needMore": "Need more?", + "contactUs": "Contact us" }, + "creditsAvailable": "Credits available", + "refreshes": "Refreshes {date}", "eventType": "Event Type", "details": "Details", "time": "Time", "additionalInfo": "Additional Info", "model": "Model", "added": "Added", - "accountInitialized": "Account initialized" + "accountInitialized": "Account initialized", + "unified": { + "message": "Credits have been unified", + "tooltip": "We've unified payments across Comfy. Everything now runs on Comfy Credits:\n- Partner Nodes (formerly API nodes)\n- Cloud workflows\n\nYour existing Partner node balance has been converted into credits." + } }, "subscription": { "title": "Subscription", @@ -1858,11 +2024,13 @@ "comfyCloud": "Comfy Cloud", "comfyCloudLogo": "Comfy Cloud Logo", "beta": "BETA", - "perMonth": "USD / month", + "perMonth": "/ month", + "usdPerMonth": "USD / mo", "renewsDate": "Renews {date}", - "refreshesOn": "Refreshes to ${monthlyCreditBonusUsd} on {date}", "expiresDate": "Expires {date}", "manageSubscription": "Manage subscription", + "managePayment": "Manage Payment", + "cancelSubscription": "Cancel Subscription", "partnerNodesBalance": "\"Partner Nodes\" Credit Balance", "partnerNodesDescription": "For running commercial/proprietary models", "totalCredits": "Total credits", @@ -1873,16 +2041,41 @@ "monthlyBonusDescription": "Monthly credit bonus", "prepaidDescription": "Pre-paid credits", "prepaidCreditsInfo": "Pre-paid credits expire after 1 year from purchase date.", + "creditsRemainingThisMonth": "Included (Refills {date})", + "creditsRemainingThisYear": "Included (Refills {date})", + "creditsYouveAdded": "Additional", + "monthlyCreditsInfo": "These credits refresh monthly and don't roll over", + "viewMoreDetailsPlans": "View more details about plans & pricing", "nextBillingCycle": "next billing cycle", "yourPlanIncludes": "Your plan includes:", "viewMoreDetails": "View more details", "learnMore": "Learn more", + "billedMonthly": "Billed monthly", + "billedYearly": "{total} Billed yearly", + "monthly": "Monthly", + "yearly": "Yearly", + "tierNameYearly": "{name} Yearly", "messageSupport": "Message support", "invoiceHistory": "Invoice history", "benefits": { "benefit1": "$10 in monthly credits for Partner Nodes — top up when needed", "benefit2": "Up to 30 min runtime per job" }, + "yearlyDiscount": "20% DISCOUNT", + "tiers": { + "founder": { + "name": "Founder's Edition" + }, + "standard": { + "name": "Standard" + }, + "creator": { + "name": "Creator" + }, + "pro": { + "name": "Pro" + } + }, "required": { "title": "Subscribe to", "waitingForSubscription": "Complete your subscription in the new tab. We'll automatically detect when you're done!", @@ -1892,16 +2085,177 @@ "subscribeToRunFull": "Subscribe to Run", "subscribeNow": "Subscribe Now", "subscribeToComfyCloud": "Subscribe to Comfy Cloud", - "partnerNodesCredits": "Partner Nodes pricing table" + "workspaceNotSubscribed": "This workspace is not on a subscription", + "subscriptionRequiredMessage": "A subscription is required for members to run workflows on Cloud", + "contactOwnerToSubscribe": "Contact the workspace owner to subscribe", + "description": "Choose the best plan for you", + "haveQuestions": "Have questions or wondering about enterprise?", + "contactUs": "Contact us", + "viewEnterprise": "View enterprise", + "partnerNodesCredits": "Partner nodes pricing", + "plansAndPricing": "Plans & pricing", + "managePlan": "Manage plan", + "upgrade": "UPGRADE", + "mostPopular": "Most popular", + "currentPlan": "Current Plan", + "subscribeTo": "Subscribe to {plan}", + "monthlyCreditsLabel": "Monthly credits", + "yearlyCreditsLabel": "Total yearly credits", + "maxDurationLabel": "Max run duration", + "gpuLabel": "RTX 6000 Pro (96GB VRAM)", + "addCreditsLabel": "Add more credits whenever", + "customLoRAsLabel": "Import your own LoRAs", + "videoEstimateLabel": "Approx. amount of 5s videos generated with Wan 2.2 Image-to-Video template", + "videoEstimateHelp": "More details on this template", + "videoEstimateExplanation": "These estimates are based on the Wan 2.2 Image-to-Video template using default settings (5 seconds, 640x640, 16fps, 4-step sampling).", + "videoEstimateTryTemplate": "Try this template", + "videoTemplateBasedCredits": "Videos generated with Wan 2.2 Image to Video", + "upgradePlan": "Upgrade Plan", + "upgradeTo": "Upgrade to {plan}", + "changeTo": "Change to {plan}", + "maxDuration": { + "standard": "30 min", + "creator": "30 min", + "pro": "1 hr", + "founder": "30 min" + }, + "billingComingSoon": { + "title": "Coming Soon", + "message": "Team billing is coming soon. You'll be able to subscribe to a plan for your workspace with per-seat pricing. Stay tuned for updates." + } }, "userSettings": { - "title": "User Settings", + "title": "My Account Settings", + "accountSettings": "Account settings", + "workspaceSettings": "Workspace settings", "name": "Name", "email": "Email", "provider": "Sign-in Provider", "notSet": "Not set", "updatePassword": "Update Password" }, + "workspacePanel": { + "invite": "Invite", + "inviteMember": "Invite member", + "inviteLimitReached": "You've reached the maximum of 50 members", + "tabs": { + "dashboard": "Dashboard", + "planCredits": "Plan & Credits", + "membersCount": "Members ({count})" + }, + "dashboard": { + "placeholder": "Dashboard workspace settings" + }, + "members": { + "membersCount": "{count}/50 Members", + "pendingInvitesCount": "{count} pending invite | {count} pending invites", + "tabs": { + "active": "Active", + "pendingCount": "Pending ({count})" + }, + "columns": { + "inviteDate": "Invite date", + "expiryDate": "Expiry date", + "joinDate": "Join date" + }, + "actions": { + "copyLink": "Copy invite link", + "revokeInvite": "Revoke invite", + "removeMember": "Remove member" + }, + "noInvites": "No pending invites", + "noMembers": "No members", + "personalWorkspaceMessage": "You can't invite other members to your personal workspace right now. To add members to a workspace,", + "createNewWorkspace": "create a new one." + }, + "menu": { + "editWorkspace": "Edit workspace details", + "leaveWorkspace": "Leave Workspace", + "deleteWorkspace": "Delete Workspace", + "deleteWorkspaceDisabledTooltip": "Cancel your workspace's active subscription first" + }, + "editWorkspaceDialog": { + "title": "Edit workspace details", + "nameLabel": "Workspace name", + "save": "Save" + }, + "leaveDialog": { + "title": "Leave this workspace?", + "message": "You won't be able to join again unless you contact the workspace owner.", + "leave": "Leave" + }, + "deleteDialog": { + "title": "Delete this workspace?", + "message": "Any unused credits or unsaved assets will be lost. This action cannot be undone.", + "messageWithName": "Delete \"{name}\"? Any unused credits or unsaved assets will be lost. This action cannot be undone." + }, + "removeMemberDialog": { + "title": "Remove this member?", + "message": "This member will be removed from your workspace. Credits they've used will not be refunded.", + "remove": "Remove member", + "success": "Member removed", + "error": "Failed to remove member" + }, + "revokeInviteDialog": { + "title": "Uninvite this person?", + "message": "This member won't be able to join your workspace anymore. Their invite link will be invalidated.", + "revoke": "Uninvite" + }, + "inviteMemberDialog": { + "title": "Invite a person to this workspace", + "message": "Create a shareable invite link to send to someone", + "placeholder": "Enter the person's email", + "createLink": "Create link", + "linkStep": { + "title": "Send this link to the person", + "message": "Make sure their account uses this email.", + "copyLink": "Copy Link", + "done": "Done" + }, + "linkCopied": "Copied", + "linkCopyFailed": "Failed to copy link" + }, + "createWorkspaceDialog": { + "title": "Create a new workspace", + "message": "Workspaces let members share a single credits pool. You'll become the owner after creating this.", + "nameLabel": "Workspace name*", + "namePlaceholder": "Enter workspace name", + "create": "Create" + }, + "toast": { + "workspaceCreated": { + "title": "Workspace created", + "message": "Subscribe to a plan, invite teammates, and start collaborating.", + "subscribe": "Subscribe" + }, + "workspaceUpdated": { + "title": "Workspace updated", + "message": "Workspace details have been saved." + }, + "workspaceDeleted": { + "title": "Workspace deleted", + "message": "The workspace has been permanently deleted." + }, + "workspaceLeft": { + "title": "Left workspace", + "message": "You have left the workspace." + }, + "failedToUpdateWorkspace": "Failed to update workspace", + "failedToCreateWorkspace": "Failed to create workspace", + "failedToDeleteWorkspace": "Failed to delete workspace", + "failedToLeaveWorkspace": "Failed to leave workspace", + "failedToFetchWorkspaces": "Failed to load workspaces" + } + }, + "workspaceSwitcher": { + "switchWorkspace": "Switch workspace", + "subscribe": "Subscribe", + "personal": "Personal", + "roleOwner": "Owner", + "roleMember": "Member", + "createWorkspace": "Create new workspace", + "maxWorkspacesReached": "You can only own 10 workspaces. Delete one to create a new one." + }, "selectionToolbox": { "executeButton": { "tooltip": "Execute to selected output nodes (Highlighted with orange border)", @@ -1912,6 +2266,7 @@ "Set Group Nodes to Always": "Set Group Nodes to Always" }, "widgets": { + "node2only": "Node 2.0 only", "selectModel": "Select model", "uploadSelect": { "placeholder": "Select...", @@ -1920,6 +2275,26 @@ "placeholderVideo": "Select video...", "placeholderModel": "Select model...", "placeholderUnknown": "Select media..." + }, + "valueControl": { + "header": { + "prefix": "Automatically update the value", + "after": "AFTER", + "before": "BEFORE", + "postfix": "running the workflow:" + }, + "linkToGlobal": "Link to", + "linkToGlobalSeed": "Global Value", + "linkToGlobalDesc": "Unique value linked to the Global Value's control setting", + "randomize": "Randomize Value", + "randomizeDesc": "Shuffles the value randomly after each generation", + "increment": "Increment Value", + "incrementDesc": "Adds 1 to value or selects the next option", + "decrement": "Decrement Value", + "decrementDesc": "Subtracts 1 from value or selects the previous option", + "fixed": "Fixed Value", + "fixedDesc": "Leaves value unchanged", + "editSettings": "Edit control settings" } }, "widgetFileUpload": { @@ -1936,6 +2311,7 @@ }, "whatsNewPopup": { "learnMore": "Learn more", + "later": "Later", "noReleaseNotes": "No release notes available." }, "breadcrumbsMenu": { @@ -1969,6 +2345,7 @@ "renderErrorState": "Render Error State" }, "cloudOnboarding": { + "skipToCloudApp": "Skip to the cloud app", "survey": { "title": "Cloud Survey", "placeholder": "Survey questions placeholder", @@ -2091,75 +2468,146 @@ "cloudSurvey_steps_industry": "What's your primary industry?", "cloudSurvey_steps_making": "What do you plan on making?", "assetBrowser": { - "assets": "Assets", - "assetCollection": "Asset collection", - "checkpoints": "Checkpoints", - "browseAssets": "Browse Assets", - "noAssetsFound": "No assets found", - "tryAdjustingFilters": "Try adjusting your search or filters", - "loadingModels": "Loading {type}...", - "connectionError": "Please check your connection and try again", - "failedToCreateNode": "Failed to create node. Please try again or check console for details.", - "noModelsInFolder": "No {type} available in this folder", - "searchAssetsPlaceholder": "Type to search...", - "uploadModel": "Import model", - "uploadModelFromCivitai": "Import a model from Civitai", - "uploadModelFailedToRetrieveMetadata": "Failed to retrieve metadata. Please check the link and try again.", - "onlyCivitaiUrlsSupported": "Only Civitai URLs are supported", - "uploadModelDescription1": "Paste a Civitai model download link to add it to your library.", - "uploadModelDescription2": "Only links from https://civitai.com are supported at the moment", - "uploadModelDescription3": "Max file size: 1 GB", - "civitaiLinkLabel": "Civitai model download link", - "civitaiLinkPlaceholder": "Paste link here", - "civitaiLinkExample": "Example: https://civitai.com/api/download/models/833921?type=Model&format=SafeTensor", - "confirmModelDetails": "Confirm Model Details", - "fileName": "File Name", - "fileSize": "File Size", - "modelName": "Model Name", - "modelNamePlaceholder": "Enter a name for this model", - "tags": "Tags", - "tagsPlaceholder": "e.g., models, checkpoint", - "tagsHelp": "Separate tags with commas", - "upload": "Import", - "uploadingModel": "Importing model...", - "uploadSuccess": "Model imported successfully!", - "uploadFailed": "Import failed", - "modelAssociatedWithLink": "The model associated with the link you provided:", - "modelTypeSelectorLabel": "What type of model is this?", - "modelTypeSelectorPlaceholder": "Select model type", - "selectModelType": "Select model type", - "notSureLeaveAsIs": "Not sure? Just leave this as is", - "modelUploaded": "Model imported! 🎉", - "findInLibrary": "Find it in the {type} section of the models library.", - "finish": "Finish", - "allModels": "All Models", "allCategory": "All {category}", - "unknown": "Unknown", - "fileFormats": "File formats", + "allModels": "All Models", + "byType": "By type", + "emptyImported": { + "canImport": "No imported models yet. Click \"Import Model\" to add your own.", + "restricted": "Personal models are only available at Creator tier and above." + }, + "imported": "Imported", + "assetCollection": "Asset collection", + "assets": "Assets", "baseModels": "Base models", - "filterBy": "Filter by", - "sortBy": "Sort by", - "sortAZ": "A-Z", - "sortZA": "Z-A", - "sortRecent": "Recent", - "sortPopular": "Popular", - "selectFrameworks": "Select Frameworks", - "selectProjects": "Select Projects", - "sortingType": "Sorting Type", + "browseAssets": "Browse Assets", + "checkpoints": "Checkpoints", + "civitaiLinkExample": "{example} {link}", + "civitaiLinkExampleStrong": "Example:", + "civitaiLinkExampleUrl": "https://civitai.com/models/10706/luisap-z-image-and-qwen-pixel-art-refiner?modelVersionId=2225295", + "civitaiLinkLabel": "Civitai model {download} link", + "civitaiLinkLabelDownload": "download", + "civitaiLinkPlaceholder": "Paste link here", + "confirmModelDetails": "Confirm Model Details", + "connectionError": "Please check your connection and try again", "errorFileTooLarge": "File exceeds the maximum allowed size limit", "errorFormatNotAllowed": "Only SafeTensor format is allowed", - "errorUnsafePickleScan": "CivitAI detected potentially unsafe code in this file", - "errorUnsafeVirusScan": "CivitAI detected malware or suspicious content in this file", "errorModelTypeNotSupported": "This model type is not supported", "errorUnknown": "An unexpected error occurred", + "errorUnsafePickleScan": "CivitAI detected potentially unsafe code in this file", + "errorUnsafeVirusScan": "CivitAI detected malware or suspicious content in this file", "errorUploadFailed": "Failed to import asset. Please try again.", + "failedToCreateNode": "Failed to create node. Please try again or check console for details.", + "fileFormats": "File formats", + "fileName": "File Name", + "fileSize": "File Size", + "filterBy": "Filter by", + "findInLibrary": "Find it in the {type} section of the models library.", + "finish": "Finish", + "importAnother": "Import Another", + "genericLinkPlaceholder": "Paste link here", + "jobId": "Job ID", + "loadingModels": "Loading {type}...", + "maxFileSize": "Max file size: {size}", + "maxFileSizeValue": "1 GB", + "modelAssociatedWithLink": "The model associated with the link you provided:", + "modelName": "Model Name", + "modelNamePlaceholder": "Enter a name for this model", + "modelTypeSelectorLabel": "What type of model is this?", + "modelTypeSelectorPlaceholder": "Select model type", + "modelUploaded": "Model successfully imported.", + "noAssetsFound": "No assets found", + "noModelsInFolder": "No {type} available in this folder", + "notSureLeaveAsIs": "Not sure? Just leave this as is", + "noValidSourceDetected": "No valid import source detected", + "onlyCivitaiUrlsSupported": "Only Civitai URLs are supported", + "ownership": "Ownership", + "ownershipAll": "All", + "ownershipMyModels": "My models", + "ownershipPublicModels": "Public models", + "processingModel": "Download started", + "processingModelDescription": "You can close this dialog. The download will continue in the background.", + "providerCivitai": "Civitai", + "providerHuggingFace": "Hugging Face", + "selectFrameworks": "Select Frameworks", + "selectModelType": "Select model type", + "selectProjects": "Select Projects", + "sortAZ": "A-Z", + "sortBy": "Sort by", + "sortingType": "Sorting Type", + "sortPopular": "Popular", + "sortRecent": "Recent", + "sortZA": "Z-A", + "tags": "Tags", + "tagsHelp": "Separate tags with commas", + "tagsPlaceholder": "e.g., models, checkpoint", + "tryAdjustingFilters": "Try adjusting your search or filters", + "unknown": "Unknown", + "unsupportedUrlSource": "Only URLs from {sources} are supported", + "upgradeFeatureDescription": "This feature is only available with Creator or Pro plans.", + "upgradeToUnlockFeature": "Upgrade to unlock this feature", + "upload": "Import", + "uploadFailed": "Import failed", + "uploadingModel": "Importing model...", + "uploadModel": "Import", + "uploadModelDescription1": "Paste a Civitai model download link to add it to your library.", + "uploadModelDescription1Generic": "Paste a model download link to add it to your library.", + "uploadModelDescription2": "Only links from {link} are supported at the moment", + "uploadModelDescription2Generic": "Only URLs from the following providers are supported:", + "uploadModelDescription2Link": "https://civitai.com/models", + "uploadModelDescription3": "Max file size: {size}", + "uploadModelFailedToRetrieveMetadata": "Failed to retrieve metadata. Please check the link and try again.", + "uploadModelFromCivitai": "Import a model from Civitai", + "uploadModelGeneric": "Import a model", + "uploadModelHelpFooterText": "Need help finding the URLs? Click on a provider below to see a how-to video.", + "uploadModelHelpVideo": "Upload Model Help Video", + "uploadModelHowDoIFindThis": "How do I find this?", + "uploadSuccess": "Model imported successfully!", "ariaLabel": { "assetCard": "{name} - {type} asset", "loadingAsset": "Loading asset" }, + "modelInfo": { + "title": "Model Info", + "selectModelPrompt": "Select a model to see its information", + "basicInfo": "Basic Info", + "displayName": "Display Name", + "editDisplayName": "Edit display name", + "fileName": "File Name", + "source": "Source", + "viewOnSource": "View on {source}", + "modelTagging": "Model Tagging", + "modelType": "Model Type", + "selectModelType": "Select model type...", + "compatibleBaseModels": "Compatible Base Models", + "addBaseModel": "Add base model...", + "baseModelUnknown": "Base model unknown", + "additionalTags": "Additional Tags", + "addTag": "Add tag...", + "noAdditionalTags": "No additional tags", + "modelDescription": "Model Description", + "triggerPhrases": "Trigger Phrases", + "description": "Description", + "descriptionNotSet": "No description set", + "descriptionPlaceholder": "Add a description for this model..." + }, "media": { "threeDModelPlaceholder": "3D Model", "audioPlaceholder": "Audio" + }, + "deletion": { + "header": "Delete this model?", + "body": "This model will be permanently removed from your library.", + "inProgress": "Deleting {assetName}...", + "complete": "{assetName} has been deleted.", + "failed": "{assetName} could not be deleted." + }, + "download": { + "complete": "Download complete", + "failed": "Download failed", + "inProgress": "Downloading {assetName}..." + }, + "rename": { + "failed": "Could not rename asset." } }, "mediaAsset": { @@ -2171,9 +2619,17 @@ "deletingImportedFilesCloudOnly": "Deleting imported files is only supported in cloud version", "failedToDeleteAsset": "Failed to delete asset", "actions": { - "inspect": "Inspect", + "inspect": "Inspect asset", "more": "More options", - "seeMoreOutputs": "See more outputs" + "zoom": "Zoom in", + "moreOptions": "More options", + "seeMoreOutputs": "See more outputs", + "insertAsNodeInWorkflow": "Insert as node in workflow", + "download": "Download", + "openWorkflow": "Open as workflow in new tab", + "exportWorkflow": "Export workflow", + "copyJobId": "Copy job ID", + "delete": "Delete" }, "jobIdToast": { "jobIdCopied": "Job ID copied to clipboard", @@ -2183,14 +2639,29 @@ }, "selection": { "selectedCount": "Assets Selected: {count}", + "multipleSelectedAssets": "Multiple assets selected", "deselectAll": "Deselect all", "downloadSelected": "Download", + "downloadSelectedAll": "Download all", "deleteSelected": "Delete", + "deleteSelectedAll": "Delete all", + "insertAllAssetsAsNodes": "Insert all assets as nodes", + "openWorkflowAll": "Open all workflows", + "exportWorkflowAll": "Export all workflows", "downloadStarted": "Downloading {count} files...", "downloadsStarted": "Started downloading {count} file(s)", "assetsDeletedSuccessfully": "{count} asset(s) deleted successfully", "failedToDeleteAssets": "Failed to delete selected assets", - "partialDeleteSuccess": "{succeeded} deleted successfully, {failed} failed" + "partialDeleteSuccess": "{succeeded} deleted successfully, {failed} failed", + "nodesAddedToWorkflow": "{count} node(s) added to workflow", + "failedToAddNodes": "Failed to add nodes to workflow", + "partialAddNodesSuccess": "{succeeded} added successfully, {failed} failed", + "workflowsOpened": "{count} workflow(s) opened in new tabs", + "noWorkflowsFound": "No workflow data found in selected assets", + "partialWorkflowsOpened": "{succeeded} workflow(s) opened, {failed} failed", + "workflowsExported": "{count} workflow(s) exported successfully", + "noWorkflowsToExport": "No workflow data found to export", + "partialWorkflowsExported": "{succeeded} exported successfully, {failed} failed" }, "noJobIdFound": "No job ID found for this asset", "unsupportedFileType": "Unsupported file type for loader node", @@ -2229,8 +2700,14 @@ "message": "Switch back to Nodes 2.0 anytime from the main menu." }, "linearMode": { - "share": "Share", - "openWorkflow": "Open Workflow" + "linearMode": "Simple Mode", + "beta": "Simple Mode in Beta - Feedback", + "graphMode": "Graph Mode", + "dragAndDropImage": "Click to browse or drag an image", + "runCount": "Run count:", + "rerun": "Rerun", + "reuseParameters": "Reuse Parameters", + "downloadAll": "Download All" }, "missingNodes": { "cloud": { @@ -2246,5 +2723,104 @@ "description": "This workflow uses custom nodes you haven't installed yet.", "replacementInstruction": "Install these nodes to run this workflow, or replace them with installed alternatives. Missing nodes are highlighted in red on the canvas." } + }, + "rightSidePanel": { + "togglePanel": "Toggle properties panel", + "noSelection": "Select a node to see its properties and info.", + "workflowOverview": "Workflow Overview", + "title": "No item(s) selected | 1 item selected | {count} items selected", + "parameters": "Parameters", + "nodes": "Nodes", + "info": "Info", + "color": "Node color", + "pinned": "Pinned", + "bypass": "Bypass", + "normal": "Normal", + "mute": "Mute", + "inputs": "INPUTS", + "inputsNone": "NO INPUTS", + "inputsNoneTooltip": "Node has no inputs", + "advancedInputs": "ADVANCED INPUTS", + "showAdvancedInputsButton": "Show advanced inputs", + "properties": "Properties", + "nodeState": "Node state", + "settings": "Settings", + "addFavorite": "Favorite", + "removeFavorite": "Unfavorite", + "hideInput": "Hide input", + "showInput": "Show input", + "locateNode": "Locate node on canvas", + "favorites": "FAVORITED INPUTS", + "favoritesNone": "NO FAVORITED INPUTS", + "favoritesNoneTooltip": "Star widgets to quickly access them without selecting nodes", + "globalSettings": { + "title": "Global Settings", + "searchPlaceholder": "Search quick settings...", + "nodes": "NODES", + "canvas": "CANVAS", + "connectionLinks": "CONNECTION LINKS", + "showAdvanced": "Show advanced parameters", + "showAdvancedTooltip": "This is an important setting that when set to TRUE, reveals all advanced parameters for nodes", + "showInfoBadges": "Show info badges", + "showToolbox": "Show toolbox on selection", + "nodes2": "Nodes 2.0", + "gridSpacing": "Grid spacing", + "snapNodesToGrid": "Snap nodes to grid", + "linkShape": "Link shape", + "showConnectedLinks": "Show connected links", + "viewAllSettings": "View all settings" + }, + "groupSettings": "Group Settings", + "groups": "Groups", + "favoritesNoneDesc": "Inputs you favorite will show up here", + "noneSearchDesc": "No items match your search", + "nodesNoneDesc": "NO NODES", + "fallbackGroupTitle": "Group", + "fallbackNodeTitle": "Node", + "hideAdvancedInputsButton": "Hide advanced inputs" + }, + "help": { + "recentReleases": "Recent releases", + "helpCenterMenu": "Help Center Menu" + }, + "progressToast": { + "importingModels": "Importing Models", + "downloadingModel": "Downloading model...", + "downloadsFailed": "{count} downloads failed | {count} download failed | {count} downloads failed", + "allDownloadsCompleted": "All downloads completed", + "noImportsInQueue": "No {filter} in queue", + "failed": "Failed", + "finished": "Finished", + "pending": "Pending", + "progressCount": "{completed} of {total}", + "filter": { + "all": "All", + "completed": "Completed", + "failed": "Failed" + } + }, + "workspace": { + "unsavedChanges": { + "title": "Unsaved Changes", + "message": "You have unsaved changes. Do you want to discard them and switch workspaces?" + }, + "inviteAccepted": "Invite Accepted", + "addedToWorkspace": "You have been added to {workspaceName}", + "inviteFailed": "Failed to Accept Invite" + }, + "workspaceAuth": { + "errors": { + "notAuthenticated": "You must be logged in to access workspaces", + "invalidFirebaseToken": "Authentication failed. Please try logging in again.", + "accessDenied": "You do not have access to this workspace", + "workspaceNotFound": "Workspace not found", + "tokenExchangeFailed": "Failed to authenticate with workspace: {error}" + } + }, + "nightly": { + "badge": { + "label": "Preview Version", + "tooltip": "You are using a nightly version of ComfyUI. Please use the feedback button to share your thoughts about these features." + } } -} +} \ No newline at end of file diff --git a/src/locales/en/nodeDefs.json b/src/locales/en/nodeDefs.json index 0e4d2101a..10506f9fd 100644 --- a/src/locales/en/nodeDefs.json +++ b/src/locales/en/nodeDefs.json @@ -14,6 +14,87 @@ "latent_image": { "name": "latent_image" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AddTextPrefix": { + "display_name": "Add Text Prefix", + "inputs": { + "texts": { + "name": "texts", + "tooltip": "Text to process." + }, + "prefix": { + "name": "prefix", + "tooltip": "Prefix to add." + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "Processed texts" + } + } + }, + "AddTextSuffix": { + "display_name": "Add Text Suffix", + "inputs": { + "texts": { + "name": "texts", + "tooltip": "Text to process." + }, + "suffix": { + "name": "suffix", + "tooltip": "Suffix to add." + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "Processed texts" + } + } + }, + "AdjustBrightness": { + "display_name": "Adjust Brightness", + "inputs": { + "images": { + "name": "images", + "tooltip": "Image to process." + }, + "factor": { + "name": "factor", + "tooltip": "Brightness factor. 1.0 = no change, <1.0 = darker, >1.0 = brighter." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Processed images" + } + } + }, + "AdjustContrast": { + "display_name": "Adjust Contrast", + "inputs": { + "images": { + "name": "images", + "tooltip": "Image to process." + }, + "factor": { + "name": "factor", + "tooltip": "Contrast factor. 1.0 = no change, <1.0 = less contrast, >1.0 = more contrast." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Processed images" + } } }, "AlignYourStepsScheduler": { @@ -70,6 +151,11 @@ "name": "volume", "tooltip": "Volume adjustment in decibels (dB). 0 = no change, +6 = double, -6 = half, etc" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "AudioConcat": { @@ -86,6 +172,11 @@ "name": "direction", "tooltip": "Whether to append audio2 after or before audio1." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "AudioEncoderEncode": { @@ -131,6 +222,11 @@ "name": "merge_method", "tooltip": "The method used to combine the audio waveforms." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BasicGuider": { @@ -142,6 +238,11 @@ "conditioning": { "name": "conditioning" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BasicScheduler": { @@ -159,6 +260,50 @@ "denoise": { "name": "denoise" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchImagesNode": { + "display_name": "Batch Images", + "inputs": { + "images": { + "name": "images" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchLatentsNode": { + "display_name": "Batch Latents", + "inputs": { + "latents": { + "name": "latents" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchMasksNode": { + "display_name": "Batch Masks", + "inputs": { + "masks": { + "name": "masks" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BetaSamplingScheduler": { @@ -176,6 +321,73 @@ "beta": { "name": "beta" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BriaImageEditNode": { + "display_name": "Bria FIBO Image Edit", + "description": "Edit images using Bria latest model", + "inputs": { + "model": { + "name": "model" + }, + "image": { + "name": "image" + }, + "prompt": { + "name": "prompt", + "tooltip": "Instruction to edit image" + }, + "negative_prompt": { + "name": "negative_prompt" + }, + "structured_prompt": { + "name": "structured_prompt", + "tooltip": "A string containing the structured edit prompt in JSON format. Use this instead of usual prompt for precise, programmatic control." + }, + "seed": { + "name": "seed" + }, + "guidance_scale": { + "name": "guidance_scale", + "tooltip": "Higher value makes the image follow the prompt more closely." + }, + "steps": { + "name": "steps" + }, + "moderation": { + "name": "moderation", + "tooltip": "Moderation settings" + }, + "mask": { + "name": "mask", + "tooltip": "If omitted, the edit applies to the entire image." + }, + "control_after_generate": { + "name": "control after generate" + }, + "moderation_prompt_content_moderation": { + "name": "prompt_content_moderation" + }, + "moderation_visual_input_moderation": { + "name": "visual_input_moderation" + }, + "moderation_visual_output_moderation": { + "name": "visual_output_moderation" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "structured_prompt", + "tooltip": null + } } }, "ByteDanceFirstLastFrameNode": { @@ -183,8 +395,7 @@ "description": "Generate video using prompt and first and last frames.", "inputs": { "model": { - "name": "model", - "tooltip": "Model name" + "name": "model" }, "prompt": { "name": "prompt", @@ -222,6 +433,10 @@ "name": "watermark", "tooltip": "Whether to add an \"AI generated\" watermark to the video." }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "This parameter is ignored for any model except seedance-1-5-pro." + }, "control_after_generate": { "name": "control after generate" } @@ -237,8 +452,7 @@ "description": "Edit images using ByteDance models via api based on prompt", "inputs": { "model": { - "name": "model", - "tooltip": "Model name" + "name": "model" }, "image": { "name": "image", @@ -275,8 +489,7 @@ "description": "Generate images using ByteDance models via api based on prompt", "inputs": { "model": { - "name": "model", - "tooltip": "Model name" + "name": "model" }, "prompt": { "name": "prompt", @@ -321,8 +534,7 @@ "description": "Generate video using prompt and reference images.", "inputs": { "model": { - "name": "model", - "tooltip": "Model name" + "name": "model" }, "prompt": { "name": "prompt", @@ -367,8 +579,7 @@ "description": "Generate video using ByteDance models via api based on image and prompt", "inputs": { "model": { - "name": "model", - "tooltip": "Model name" + "name": "model" }, "prompt": { "name": "prompt", @@ -402,6 +613,10 @@ "name": "watermark", "tooltip": "Whether to add an \"AI generated\" watermark to the video." }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "This parameter is ignored for any model except seedance-1-5-pro." + }, "control_after_generate": { "name": "control after generate" } @@ -413,7 +628,7 @@ } }, "ByteDanceSeedreamNode": { - "display_name": "ByteDance Seedream 4", + "display_name": "ByteDance Seedream 4.5", "description": "Unified text-to-image generation and precise single-sentence editing at up to 4K resolution.", "inputs": { "model": { @@ -475,8 +690,7 @@ "description": "Generate video using ByteDance models via api based on prompt", "inputs": { "model": { - "name": "model", - "tooltip": "Model name" + "name": "model" }, "prompt": { "name": "prompt", @@ -506,6 +720,10 @@ "name": "watermark", "tooltip": "Whether to add an \"AI generated\" watermark to the video." }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "This parameter is ignored for any model except seedance-1-5-pro." + }, "control_after_generate": { "name": "control after generate" } @@ -551,6 +769,29 @@ } } }, + "CenterCropImages": { + "display_name": "Center Crop Images", + "inputs": { + "images": { + "name": "images", + "tooltip": "Image to process." + }, + "width": { + "name": "width", + "tooltip": "Crop width." + }, + "height": { + "name": "height", + "tooltip": "Crop height." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Processed images" + } + } + }, "CFGGuider": { "display_name": "CFGGuider", "inputs": { @@ -566,6 +807,11 @@ "cfg": { "name": "cfg" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "CFGNorm": { @@ -883,6 +1129,25 @@ } } }, + "CLIPTextEncodeKandinsky5": { + "display_name": "CLIPTextEncodeKandinsky5", + "inputs": { + "clip": { + "name": "clip" + }, + "clip_l": { + "name": "clip_l" + }, + "qwen25_7b": { + "name": "qwen25_7b" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "CLIPTextEncodeLumina2": { "display_name": "CLIP Text Encode for Lumina2", "description": "Encodes a system prompt and a user prompt using a CLIP model into an embedding that can be used to guide the diffusion model towards generating specific images.", @@ -1095,6 +1360,26 @@ } } }, + "ComfySwitchNode": { + "display_name": "Switch", + "inputs": { + "switch": { + "name": "switch" + }, + "on_false": { + "name": "on_false" + }, + "on_true": { + "name": "on_true" + } + }, + "outputs": { + "0": { + "name": "output", + "tooltip": null + } + } + }, "ConditioningAverage": { "display_name": "ConditioningAverage", "inputs": { @@ -1329,10 +1614,12 @@ }, "outputs": { "0": { - "name": "positive" + "name": "positive", + "tooltip": null }, "1": { - "name": "negative" + "name": "negative", + "tooltip": null } } }, @@ -1398,6 +1685,10 @@ "dim": { "name": "dim", "tooltip": "The dimension to apply the context windows to." + }, + "freenoise": { + "name": "freenoise", + "tooltip": "Whether to apply FreeNoise noise shuffling, improves window blending." } }, "outputs": { @@ -1791,6 +2082,30 @@ "height": { "name": "height" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CustomCombo": { + "display_name": "Custom Combo", + "inputs": { + "choice": { + "name": "choice" + }, + "index": {}, + "option1": {} + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "INDEX", + "tooltip": null + } } }, "DiffControlNetLoader": { @@ -1829,7 +2144,12 @@ } }, "DisableNoise": { - "display_name": "DisableNoise" + "display_name": "DisableNoise", + "outputs": { + "0": { + "tooltip": null + } + } }, "DualCFGGuider": { "display_name": "DualCFGGuider", @@ -1855,11 +2175,16 @@ "style": { "name": "style" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "DualCLIPLoader": { "display_name": "DualCLIPLoader", - "description": "[Recipes]\n\nsdxl: clip-l, clip-g\nsd3: clip-l, clip-g / clip-l, t5 / clip-g, t5\nflux: clip-l, t5\nhidream: at least one of t5 or llama, recommended t5 and llama\nhunyuan_image: qwen2.5vl 7b and byt5 small", + "description": "[Recipes]\n\nsdxl: clip-l, clip-g\nsd3: clip-l, clip-g / clip-l, t5 / clip-g, t5\nflux: clip-l, t5\nhidream: at least one of t5 or llama, recommended t5 and llama\nhunyuan_image: qwen2.5vl 7b and byt5 small\nnewbie: gemma-3-4b-it, jina clip v2", "inputs": { "clip_name1": { "name": "clip_name1" @@ -1938,6 +2263,11 @@ "name": "channels", "tooltip": "Number of audio channels (1 for mono, 2 for stereo)." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyChromaRadianceLatentImage": { @@ -1981,6 +2311,25 @@ } } }, + "EmptyFlux2LatentImage": { + "display_name": "Empty Flux 2 Latent", + "inputs": { + "width": { + "name": "width" + }, + "height": { + "name": "height" + }, + "batch_size": { + "name": "batch_size" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptyHunyuanImageLatent": { "display_name": "EmptyHunyuanImageLatent", "inputs": { @@ -2071,6 +2420,11 @@ "name": "batch_size", "tooltip": "The number of latent images in the batch." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyLatentHunyuan3Dv2": { @@ -2157,6 +2511,28 @@ } } }, + "EmptyQwenImageLayeredLatentImage": { + "display_name": "Empty Qwen Image Layered Latent", + "inputs": { + "width": { + "name": "width" + }, + "height": { + "name": "height" + }, + "layers": { + "name": "layers" + }, + "batch_size": { + "name": "batch_size" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptySD3LatentImage": { "display_name": "EmptySD3LatentImage", "inputs": { @@ -2204,6 +2580,11 @@ "sigma_min": { "name": "sigma_min" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ExtendIntermediateSigmas": { @@ -2224,6 +2605,11 @@ "spacing": { "name": "spacing" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FeatherMask": { @@ -2244,6 +2630,11 @@ "bottom": { "name": "bottom" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FlipSigmas": { @@ -2252,6 +2643,102 @@ "sigmas": { "name": "sigmas" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2MaxImageNode": { + "display_name": "Flux.2 [max] Image", + "description": "Generates images synchronously based on prompt and resolution.", + "inputs": { + "prompt": { + "name": "prompt", + "tooltip": "Prompt for the image generation or edit" + }, + "width": { + "name": "width" + }, + "height": { + "name": "height" + }, + "seed": { + "name": "seed", + "tooltip": "The random seed used for creating the noise." + }, + "prompt_upsampling": { + "name": "prompt_upsampling", + "tooltip": "Whether to perform upsampling on the prompt. If active, automatically modifies the prompt for more creative generation." + }, + "images": { + "name": "images", + "tooltip": "Up to 9 images to be used as references." + }, + "control_after_generate": { + "name": "control after generate" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2ProImageNode": { + "display_name": "Flux.2 [pro] Image", + "description": "Generates images synchronously based on prompt and resolution.", + "inputs": { + "prompt": { + "name": "prompt", + "tooltip": "Prompt for the image generation or edit" + }, + "width": { + "name": "width" + }, + "height": { + "name": "height" + }, + "seed": { + "name": "seed", + "tooltip": "The random seed used for creating the noise." + }, + "prompt_upsampling": { + "name": "prompt_upsampling", + "tooltip": "Whether to perform upsampling on the prompt. If active, automatically modifies the prompt for more creative generation." + }, + "images": { + "name": "images", + "tooltip": "Up to 9 images to be used as references." + }, + "control_after_generate": { + "name": "control after generate" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2Scheduler": { + "display_name": "Flux2Scheduler", + "inputs": { + "steps": { + "name": "steps" + }, + "width": { + "name": "width" + }, + "height": { + "name": "height" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FluxDisableGuidance": { @@ -2300,7 +2787,7 @@ }, "FluxKontextMaxImageNode": { "display_name": "Flux.1 Kontext [max] Image", - "description": "Edits images using Flux.1 Kontext [max] via api based on prompt and aspect ratio.", + "description": "Edits images using Flux.1 Kontext [pro] via api based on prompt and aspect ratio.", "inputs": { "prompt": { "name": "prompt", @@ -2340,7 +2827,7 @@ } }, "FluxKontextMultiReferenceLatentMethod": { - "display_name": "FluxKontextMultiReferenceLatentMethod", + "display_name": "Edit Model Reference Method", "inputs": { "conditioning": { "name": "conditioning" @@ -2548,6 +3035,11 @@ "s2": { "name": "s2" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FreeU_V2": { @@ -2568,6 +3060,11 @@ "s2": { "name": "s2" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FreSca": { @@ -2613,7 +3110,7 @@ }, "aspect_ratio": { "name": "aspect_ratio", - "tooltip": "If set to 'auto', matches your input image's aspect ratio; if no image is provided, generates a 1:1 square." + "tooltip": "If set to 'auto', matches your input image's aspect ratio; if no image is provided, a 16:9 square is usually generated." }, "resolution": { "name": "resolution", @@ -2631,6 +3128,10 @@ "name": "files", "tooltip": "Optional file(s) to use as context for the model. Accepts inputs from the Gemini Generate Content Input Files node." }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "Foundational instructions that dictate an AI's behavior." + }, "control_after_generate": { "name": "control after generate" } @@ -2676,6 +3177,10 @@ "name": "response_modalities", "tooltip": "Choose 'IMAGE' for image-only output, or 'IMAGE+TEXT' to return both the generated image and a text response." }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "Foundational instructions that dictate an AI's behavior." + }, "control_after_generate": { "name": "control after generate" } @@ -2740,6 +3245,10 @@ "name": "files", "tooltip": "Optional file(s) to use as context for the model. Accepts inputs from the Gemini Generate Content Input Files node." }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "Foundational instructions that dictate an AI's behavior." + }, "control_after_generate": { "name": "control after generate" } @@ -2750,6 +3259,72 @@ } } }, + "GenerateTracks": { + "display_name": "GenerateTracks", + "inputs": { + "width": { + "name": "width" + }, + "height": { + "name": "height" + }, + "start_x": { + "name": "start_x", + "tooltip": "Normalized X coordinate (0-1) for start position." + }, + "start_y": { + "name": "start_y", + "tooltip": "Normalized Y coordinate (0-1) for start position." + }, + "end_x": { + "name": "end_x", + "tooltip": "Normalized X coordinate (0-1) for end position." + }, + "end_y": { + "name": "end_y", + "tooltip": "Normalized Y coordinate (0-1) for end position." + }, + "num_frames": { + "name": "num_frames" + }, + "num_tracks": { + "name": "num_tracks" + }, + "track_spread": { + "name": "track_spread", + "tooltip": "Normalized distance between tracks. Tracks are spread perpendicular to the motion direction." + }, + "bezier": { + "name": "bezier", + "tooltip": "Enable Bezier curve path using the mid point as control point." + }, + "mid_x": { + "name": "mid_x", + "tooltip": "Normalized X control point for Bezier curve. Only used when 'bezier' is enabled." + }, + "mid_y": { + "name": "mid_y", + "tooltip": "Normalized Y control point for Bezier curve. Only used when 'bezier' is enabled." + }, + "interpolation": { + "name": "interpolation", + "tooltip": "Controls the timing/speed of movement along the path." + }, + "track_mask": { + "name": "track_mask", + "tooltip": "Optional mask to indicate visible frames." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "track_length", + "tooltip": null + } + } + }, "GetImageSize": { "display_name": "Get Image Size", "description": "Returns width and height of the image, and passes it through unchanged.", @@ -2760,13 +3335,16 @@ }, "outputs": { "0": { - "name": "width" + "name": "width", + "tooltip": null }, "1": { - "name": "height" + "name": "height", + "tooltip": null }, "2": { - "name": "batch_size" + "name": "batch_size", + "tooltip": null } } }, @@ -2851,7 +3429,7 @@ } }, "GrowMask": { - "display_name": "GrowMask", + "display_name": "Grow Mask", "inputs": { "mask": { "name": "mask" @@ -2862,6 +3440,11 @@ "tapered_corners": { "name": "tapered_corners" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Hunyuan3Dv2Conditioning": { @@ -3297,6 +3880,11 @@ "control_after_generate": { "name": "control after generate" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageBatch": { @@ -3360,6 +3948,26 @@ "color": { "name": "color" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageCompare": { + "display_name": "Image Compare", + "description": "Compares two images side by side with a slider.", + "inputs": { + "compare_view": { + "name": "compare_view" + }, + "image_a": { + "name": "image_a" + }, + "image_b": { + "name": "image_b" + } } }, "ImageCompositeMasked": { @@ -3383,6 +3991,11 @@ "mask": { "name": "mask" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageCrop": { @@ -3403,6 +4016,30 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageDeduplication": { + "display_name": "Image Deduplication", + "inputs": { + "images": { + "name": "images", + "tooltip": "List of images to process." + }, + "similarity_threshold": { + "name": "similarity_threshold", + "tooltip": "Similarity threshold (0-1). Higher means more similar. Images above this threshold are considered duplicates." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Processed images" + } } }, "ImageFlip": { @@ -3414,6 +4051,11 @@ "flip_method": { "name": "flip_method" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageFromBatch": { @@ -3428,6 +4070,42 @@ "length": { "name": "length" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageGrid": { + "display_name": "Image Grid", + "inputs": { + "images": { + "name": "images", + "tooltip": "List of images to process." + }, + "columns": { + "name": "columns", + "tooltip": "Number of columns in the grid." + }, + "cell_width": { + "name": "cell_width", + "tooltip": "Width of each cell in the grid." + }, + "cell_height": { + "name": "cell_height", + "tooltip": "Height of each cell in the grid." + }, + "padding": { + "name": "padding", + "tooltip": "Padding between images." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Processed images" + } } }, "ImageInvert": { @@ -3536,6 +4214,11 @@ "rotation": { "name": "rotation" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageScale": { @@ -3584,6 +4267,11 @@ "largest_size": { "name": "largest_size" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageScaleToTotalPixels": { @@ -3597,6 +4285,9 @@ }, "megapixels": { "name": "megapixels" + }, + "resolution_steps": { + "name": "resolution_steps" } }, "outputs": { @@ -3629,7 +4320,7 @@ }, "ImageStitch": { "display_name": "Image Stitch", - "description": "\nStitches image2 to image1 in the specified direction.\nIf image2 is not provided, returns image1 unchanged.\nOptional spacing can be added between images.\n", + "description": "Stitches image2 to image1 in the specified direction.\nIf image2 is not provided, returns image1 unchanged.\nOptional spacing can be added between images.", "inputs": { "image1": { "name": "image1" @@ -3649,6 +4340,11 @@ "image2": { "name": "image2" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageToMask": { @@ -3660,6 +4356,11 @@ "channel": { "name": "channel" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageUpscaleWithModel": { @@ -3769,6 +4470,29 @@ "mask": { "name": "mask" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "JoinAudioChannels": { + "display_name": "Join Audio Channels", + "description": "Joins left and right mono audio channels into a stereo audio.", + "inputs": { + "audio_left": { + "name": "audio_left" + }, + "audio_right": { + "name": "audio_right" + } + }, + "outputs": { + "0": { + "name": "audio", + "tooltip": null + } } }, "JoinImageWithAlpha": { @@ -3787,6 +4511,53 @@ } } }, + "Kandinsky5ImageToVideo": { + "display_name": "Kandinsky5ImageToVideo", + "inputs": { + "positive": { + "name": "positive" + }, + "negative": { + "name": "negative" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "width" + }, + "height": { + "name": "height" + }, + "length": { + "name": "length" + }, + "batch_size": { + "name": "batch_size" + }, + "start_image": { + "name": "start_image" + } + }, + "outputs": { + "0": { + "name": "positive", + "tooltip": null + }, + "1": { + "name": "negative", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": "Empty video latent" + }, + "3": { + "name": "cond_latent", + "tooltip": "Clean encoded start images, used to replace the noisy start of the model output latents" + } + } + }, "KarrasScheduler": { "display_name": "KarrasScheduler", "inputs": { @@ -3802,6 +4573,11 @@ "rho": { "name": "rho" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "KlingCameraControlI2VNode": { @@ -3957,8 +4733,7 @@ } }, "KlingImage2VideoNode": { - "display_name": "Kling Image to Video", - "description": "Kling Image to Video Node", + "display_name": "Kling Image(First Frame) to Video", "inputs": { "start_frame": { "name": "start_frame", @@ -4045,6 +4820,35 @@ } } }, + "KlingImageToVideoWithAudio": { + "display_name": "Kling Image(First Frame) to Video with Audio", + "inputs": { + "model_name": { + "name": "model_name" + }, + "start_frame": { + "name": "start_frame" + }, + "prompt": { + "name": "prompt", + "tooltip": "Positive text prompt." + }, + "mode": { + "name": "mode" + }, + "duration": { + "name": "duration" + }, + "generate_audio": { + "name": "generate_audio" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingLipSyncAudioToVideoNode": { "display_name": "Kling Lip Sync Video with Audio", "description": "Kling Lip Sync Audio to Video Node. Syncs mouth movements in a video file to the audio content of an audio file. When using, ensure that the audio contains clearly distinguishable vocals and that the video contains a distinct face. The audio file should not be larger than 5MB. The video file should not be larger than 100MB, should have height/width between 720px and 1920px, and should be between 2s and 10s in length.", @@ -4106,6 +4910,227 @@ } } }, + "KlingMotionControl": { + "display_name": "Kling Motion Control", + "inputs": { + "prompt": { + "name": "prompt" + }, + "reference_image": { + "name": "reference_image" + }, + "reference_video": { + "name": "reference_video", + "tooltip": "Motion reference video used to drive movement/expression.\nDuration limits depend on character_orientation:\n - image: 3–10s (max 10s)\n - video: 3–30s (max 30s)" + }, + "keep_original_sound": { + "name": "keep_original_sound" + }, + "character_orientation": { + "name": "character_orientation", + "tooltip": "Controls where the character's facing/orientation comes from.\nvideo: movements, expressions, camera moves, and orientation follow the motion reference video (other details via prompt).\nimage: movements and expressions still follow the motion reference video, but the character orientation matches the reference image (camera/other details via prompt)." + }, + "mode": { + "name": "mode" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProEditVideoNode": { + "display_name": "Kling Omni Edit Video (Pro)", + "description": "Edit an existing video with the latest model from Kling.", + "inputs": { + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "A text prompt describing the video content. This can include both positive and negative descriptions." + }, + "video": { + "name": "video", + "tooltip": "Video for editing. The output video length will be the same." + }, + "keep_original_sound": { + "name": "keep_original_sound" + }, + "reference_images": { + "name": "reference_images", + "tooltip": "Up to 4 additional reference images." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProFirstLastFrameNode": { + "display_name": "Kling Omni First-Last-Frame to Video (Pro)", + "description": "Use a start frame, an optional end frame, or reference images with the latest Kling model.", + "inputs": { + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "A text prompt describing the video content. This can include both positive and negative descriptions." + }, + "duration": { + "name": "duration" + }, + "first_frame": { + "name": "first_frame" + }, + "end_frame": { + "name": "end_frame", + "tooltip": "An optional end frame for the video. This cannot be used simultaneously with 'reference_images'." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "Up to 6 additional reference images." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageNode": { + "display_name": "Kling Omni Image (Pro)", + "description": "Create or edit images with the latest model from Kling.", + "inputs": { + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "A text prompt describing the image content. This can include both positive and negative descriptions." + }, + "resolution": { + "name": "resolution" + }, + "aspect_ratio": { + "name": "aspect_ratio" + }, + "reference_images": { + "name": "reference_images", + "tooltip": "Up to 10 additional reference images." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageToVideoNode": { + "display_name": "Kling Omni Image to Video (Pro)", + "description": "Use up to 7 reference images to generate a video with the latest Kling model.", + "inputs": { + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "A text prompt describing the video content. This can include both positive and negative descriptions." + }, + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "reference_images": { + "name": "reference_images", + "tooltip": "Up to 7 reference images." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProTextToVideoNode": { + "display_name": "Kling Omni Text to Video (Pro)", + "description": "Use text prompts to generate videos with the latest Kling model.", + "inputs": { + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "A text prompt describing the video content. This can include both positive and negative descriptions." + }, + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProVideoToVideoNode": { + "display_name": "Kling Omni Video to Video (Pro)", + "description": "Use a video and up to 4 reference images to generate a video with the latest Kling model.", + "inputs": { + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "A text prompt describing the video content. This can include both positive and negative descriptions." + }, + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "reference_video": { + "name": "reference_video", + "tooltip": "Video to use as a reference." + }, + "keep_original_sound": { + "name": "keep_original_sound" + }, + "reference_images": { + "name": "reference_images", + "tooltip": "Up to 4 additional reference images." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingSingleImageVideoEffectNode": { "display_name": "Kling Video Effects", "description": "Achieve different special effects when generating a video based on the effect_scene.", @@ -4220,6 +5245,35 @@ } } }, + "KlingTextToVideoWithAudio": { + "display_name": "Kling Text to Video with Audio", + "inputs": { + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Positive text prompt." + }, + "mode": { + "name": "mode" + }, + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "generate_audio": { + "name": "generate_audio" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingVideoExtendNode": { "display_name": "Kling Video Extend", "description": "Kling Video Extend Node. Extend videos made by other Kling nodes. The video_id is created by using other Kling Nodes.", @@ -4381,6 +5435,11 @@ "sampler_name": { "name": "sampler_name" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LaplaceScheduler": { @@ -4401,6 +5460,11 @@ "beta": { "name": "beta" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LatentAdd": { @@ -4538,6 +5602,11 @@ "mask": { "name": "mask" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LatentConcat": { @@ -4601,6 +5670,25 @@ } } }, + "LatentCutToBatch": { + "display_name": "LatentCutToBatch", + "inputs": { + "samples": { + "name": "samples" + }, + "dim": { + "name": "dim" + }, + "slice_size": { + "name": "slice_size" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LatentFlip": { "display_name": "Flip Latent", "inputs": { @@ -4799,7 +5887,7 @@ } }, "Load3D": { - "display_name": "Load 3D", + "display_name": "Load 3D & Animation", "inputs": { "model_file": { "name": "model_file" @@ -4819,62 +5907,28 @@ }, "outputs": { "0": { - "name": "image" + "name": "image", + "tooltip": null }, "1": { - "name": "mask" + "name": "mask", + "tooltip": null }, "2": { - "name": "mesh_path" + "name": "mesh_path", + "tooltip": null }, "3": { - "name": "normal" + "name": "normal", + "tooltip": null }, "4": { - "name": "lineart" + "name": "camera_info", + "tooltip": null }, "5": { - "name": "camera_info" - }, - "6": { - "name": "recording_video" - } - } - }, - "Load3DAnimation": { - "display_name": "Load 3D - Animation", - "inputs": { - "model_file": { - "name": "model_file" - }, - "image": { - "name": "image" - }, - "width": { - "name": "width" - }, - "height": { - "name": "height" - } - }, - "outputs": { - "0": { - "name": "image" - }, - "1": { - "name": "mask" - }, - "2": { - "name": "mesh_path" - }, - "3": { - "name": "normal" - }, - "4": { - "name": "camera_info" - }, - "5": { - "name": "recording_video" + "name": "recording_video", + "tooltip": null } } }, @@ -4890,6 +5944,11 @@ "upload": { "name": "choose file to upload" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LoadImage": { @@ -4903,6 +5962,21 @@ } } }, + "LoadImageDataSetFromFolder": { + "display_name": "Load Image Dataset from Folder", + "inputs": { + "folder": { + "name": "folder", + "tooltip": "The folder to load images from." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "List of loaded images" + } + } + }, "LoadImageMask": { "display_name": "Load Image (as Mask)", "inputs": { @@ -4931,41 +6005,22 @@ } } }, - "LoadImageSetFromFolderNode": { - "display_name": "Load Image Dataset from Folder", - "description": "Loads a batch of images from a directory for training.", - "inputs": { - "folder": { - "name": "folder", - "tooltip": "The folder to load images from." - }, - "resize_method": { - "name": "resize_method" - } - } - }, - "LoadImageTextSetFromFolderNode": { + "LoadImageTextDataSetFromFolder": { "display_name": "Load Image and Text Dataset from Folder", - "description": "Loads a batch of images and caption from a directory for training.", "inputs": { "folder": { "name": "folder", "tooltip": "The folder to load images from." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "List of loaded images" }, - "clip": { - "name": "clip", - "tooltip": "The CLIP model used for encoding the text." - }, - "resize_method": { - "name": "resize_method" - }, - "width": { - "name": "width", - "tooltip": "The width to resize the images to. -1 means use the original width." - }, - "height": { - "name": "height", - "tooltip": "The height to resize the images to. -1 means use the original height." + "1": { + "name": "texts", + "tooltip": "List of text captions" } } }, @@ -4977,6 +6032,25 @@ } } }, + "LoadTrainingDataset": { + "display_name": "Load Training Dataset", + "inputs": { + "folder_name": { + "name": "folder_name", + "tooltip": "Name of folder containing the saved dataset (inside output directory)." + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "List of latent dicts" + }, + "1": { + "name": "conditioning", + "tooltip": "List of conditioning lists" + } + } + }, "LoadVideo": { "display_name": "Load Video", "inputs": { @@ -5049,7 +6123,6 @@ }, "LoraModelLoader": { "display_name": "Load LoRA Model", - "description": "Load Trained LoRA weights from Train LoRA node.", "inputs": { "model": { "name": "model", @@ -5066,6 +6139,7 @@ }, "outputs": { "0": { + "name": "model", "tooltip": "The modified diffusion model." } } @@ -5097,13 +6171,14 @@ }, "LossGraphNode": { "display_name": "Plot Loss Graph", - "description": "Plots the loss graph and saves it to the output directory.", "inputs": { "loss": { - "name": "loss" + "name": "loss", + "tooltip": "Loss map from training node." }, "filename_prefix": { - "name": "filename_prefix" + "name": "filename_prefix", + "tooltip": "Prefix for the saved loss graph image." } } }, @@ -5116,6 +6191,26 @@ } } }, + "LTXAVTextEncoderLoader": { + "display_name": "LTXV Audio Text Encoder Loader", + "description": "[Recipes]\n\nltxav: gemma 3 12B", + "inputs": { + "text_encoder": { + "name": "text_encoder" + }, + "ckpt_name": { + "name": "ckpt_name" + }, + "device": { + "name": "device" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LTXVAddGuide": { "display_name": "LTXVAddGuide", "inputs": { @@ -5222,6 +6317,76 @@ } } }, + "LTXVAudioVAEDecode": { + "display_name": "LTXV Audio VAE Decode", + "inputs": { + "samples": { + "name": "samples", + "tooltip": "The latent to be decoded." + }, + "audio_vae": { + "name": "audio_vae", + "tooltip": "The Audio VAE model used for decoding the latent." + } + }, + "outputs": { + "0": { + "name": "Audio", + "tooltip": null + } + } + }, + "LTXVAudioVAEEncode": { + "display_name": "LTXV Audio VAE Encode", + "inputs": { + "audio": { + "name": "audio", + "tooltip": "The audio to be encoded." + }, + "audio_vae": { + "name": "audio_vae", + "tooltip": "The Audio VAE model to use for encoding." + } + }, + "outputs": { + "0": { + "name": "Audio Latent", + "tooltip": null + } + } + }, + "LTXVAudioVAELoader": { + "display_name": "LTXV Audio VAE Loader", + "inputs": { + "ckpt_name": { + "name": "ckpt_name", + "tooltip": "Audio VAE checkpoint to load." + } + }, + "outputs": { + "0": { + "name": "Audio VAE", + "tooltip": null + } + } + }, + "LTXVConcatAVLatent": { + "display_name": "LTXVConcatAVLatent", + "inputs": { + "video_latent": { + "name": "video_latent" + }, + "audio_latent": { + "name": "audio_latent" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, "LTXVConditioning": { "display_name": "LTXVConditioning", "inputs": { @@ -5274,6 +6439,33 @@ } } }, + "LTXVEmptyLatentAudio": { + "display_name": "LTXV Empty Latent Audio", + "inputs": { + "frames_number": { + "name": "frames_number", + "tooltip": "Number of frames." + }, + "frame_rate": { + "name": "frame_rate", + "tooltip": "Number of frames per second." + }, + "batch_size": { + "name": "batch_size", + "tooltip": "The number of latent audio samples in the batch." + }, + "audio_vae": { + "name": "audio_vae", + "tooltip": "The Audio VAE model to get configuration from." + } + }, + "outputs": { + "0": { + "name": "Latent", + "tooltip": null + } + } + }, "LTXVImgToVideo": { "display_name": "LTXVImgToVideo", "inputs": { @@ -5320,6 +6512,47 @@ } } }, + "LTXVImgToVideoInplace": { + "display_name": "LTXVImgToVideoInplace", + "inputs": { + "vae": { + "name": "vae" + }, + "image": { + "name": "image" + }, + "latent": { + "name": "latent" + }, + "strength": { + "name": "strength" + }, + "bypass": { + "name": "bypass", + "tooltip": "Bypass the conditioning." + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, + "LTXVLatentUpsampler": { + "display_name": "LTXVLatentUpsampler", + "inputs": { + "samples": { + "name": "samples" + }, + "upscale_model": { + "name": "upscale_model" + }, + "vae": { + "name": "vae" + } + } + }, "LTXVPreprocess": { "display_name": "LTXVPreprocess", "inputs": { @@ -5368,6 +6601,25 @@ } } }, + "LTXVSeparateAVLatent": { + "display_name": "LTXVSeparateAVLatent", + "description": "LTXV Separate AV Latent", + "inputs": { + "av_latent": { + "name": "av_latent" + } + }, + "outputs": { + "0": { + "name": "video_latent", + "tooltip": null + }, + "1": { + "name": "audio_latent", + "tooltip": null + } + } + }, "LumaConceptsNode": { "display_name": "Luma Concepts", "description": "Camera Concepts for use with Luma Text to Video and Luma Image to Video nodes.", @@ -5583,7 +6835,7 @@ } }, "Mahiro": { - "display_name": "Mahiro is so cute that she deserves a better guidance function!! (。・ω・。)", + "display_name": "Mahiro CFG", "description": "Modify the guidance to scale more on the 'direction' of the positive prompt rather than the difference between the negative prompt.", "inputs": { "model": { @@ -5597,6 +6849,50 @@ } } }, + "MakeTrainingDataset": { + "display_name": "Make Training Dataset", + "inputs": { + "images": { + "name": "images", + "tooltip": "List of images to encode." + }, + "vae": { + "name": "vae", + "tooltip": "VAE model for encoding images to latents." + }, + "clip": { + "name": "clip", + "tooltip": "CLIP model for encoding text to conditioning." + }, + "texts": { + "name": "texts", + "tooltip": "List of text captions. Can be length n (matching images), 1 (repeated for all), or omitted (uses empty string)." + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "List of latent dicts" + }, + "1": { + "name": "conditioning", + "tooltip": "List of conditioning lists" + } + } + }, + "ManualSigmas": { + "display_name": "ManualSigmas", + "inputs": { + "sigmas": { + "name": "sigmas" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "MaskComposite": { "display_name": "MaskComposite", "inputs": { @@ -5615,10 +6911,15 @@ "operation": { "name": "operation" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "MaskPreview": { - "display_name": "MaskPreview", + "display_name": "Preview Mask", "description": "Saves the input images to your ComfyUI output directory.", "inputs": { "mask": { @@ -5632,6 +6933,315 @@ "mask": { "name": "mask" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MergeImageLists": { + "display_name": "Merge Image Lists", + "inputs": { + "images": { + "name": "images", + "tooltip": "List of images to process." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Processed images" + } + } + }, + "MergeTextLists": { + "display_name": "Merge Text Lists", + "inputs": { + "texts": { + "name": "texts", + "tooltip": "List of texts to process." + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "Processed texts" + } + } + }, + "MeshyAnimateModelNode": { + "display_name": "Meshy: Animate Model", + "description": "Apply a specific animation action to a previously rigged character.", + "inputs": { + "rig_task_id": { + "name": "rig_task_id" + }, + "action_id": { + "name": "action_id", + "tooltip": "Visit https://docs.meshy.ai/en/api/animation-library for a list of available values." + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + } + } + }, + "MeshyImageToModelNode": { + "display_name": "Meshy: Image to Model", + "inputs": { + "model": { + "name": "model" + }, + "image": { + "name": "image" + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "When set to false, returns an unprocessed triangular mesh." + }, + "symmetry_mode": { + "name": "symmetry_mode" + }, + "should_texture": { + "name": "should_texture", + "tooltip": "Determines whether textures are generated. Setting it to false skips the texture phase and returns a mesh without textures." + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "Specify the pose mode for the generated model." + }, + "seed": { + "name": "seed", + "tooltip": "Seed controls whether the node should re-run; results are non-deterministic regardless of seed." + }, + "control_after_generate": { + "name": "control after generate" + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "should_texture_enable_pbr": { + "name": "enable_pbr" + }, + "should_texture_texture_prompt": { + "name": "texture_prompt" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyMultiImageToModelNode": { + "display_name": "Meshy: Multi-Image to Model", + "inputs": { + "model": { + "name": "model" + }, + "images": { + "name": "images" + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "When set to false, returns an unprocessed triangular mesh." + }, + "symmetry_mode": { + "name": "symmetry_mode" + }, + "should_texture": { + "name": "should_texture", + "tooltip": "Determines whether textures are generated. Setting it to false skips the texture phase and returns a mesh without textures." + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "Specify the pose mode for the generated model." + }, + "seed": { + "name": "seed", + "tooltip": "Seed controls whether the node should re-run; results are non-deterministic regardless of seed." + }, + "control_after_generate": { + "name": "control after generate" + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "should_texture_enable_pbr": { + "name": "enable_pbr" + }, + "should_texture_texture_prompt": { + "name": "texture_prompt" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRefineNode": { + "display_name": "Meshy: Refine Draft Model", + "description": "Refine a previously created draft model.", + "inputs": { + "model": { + "name": "model" + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "enable_pbr": { + "name": "enable_pbr", + "tooltip": "Generate PBR Maps (metallic, roughness, normal) in addition to the base color. Note: this should be set to false when using Sculpture style, as Sculpture style generates its own set of PBR maps." + }, + "texture_prompt": { + "name": "texture_prompt", + "tooltip": "Provide a text prompt to guide the texturing process. Maximum 600 characters. Cannot be used at the same time as 'texture_image'." + }, + "texture_image": { + "name": "texture_image", + "tooltip": "Only one of 'texture_image' or 'texture_prompt' may be used at the same time." + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRigModelNode": { + "display_name": "Meshy: Rig Model", + "description": "Provides a rigged character in standard formats. Auto-rigging is currently not suitable for untextured meshes, non-humanoid assets, or humanoid assets with unclear limb and body structure.", + "inputs": { + "meshy_task_id": { + "name": "meshy_task_id" + }, + "height_meters": { + "name": "height_meters", + "tooltip": "The approximate height of the character model in meters. This aids in scaling and rigging accuracy." + }, + "texture_image": { + "name": "texture_image", + "tooltip": "The model's UV-unwrapped base color texture image." + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "rig_task_id", + "tooltip": null + } + } + }, + "MeshyTextToModelNode": { + "display_name": "Meshy: Text to Model", + "inputs": { + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt" + }, + "style": { + "name": "style" + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "When set to false, returns an unprocessed triangular mesh." + }, + "symmetry_mode": { + "name": "symmetry_mode" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "Specify the pose mode for the generated model." + }, + "seed": { + "name": "seed", + "tooltip": "Seed controls whether the node should re-run; results are non-deterministic regardless of seed." + }, + "control_after_generate": { + "name": "control after generate" + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyTextureNode": { + "display_name": "Meshy: Texture Model", + "inputs": { + "model": { + "name": "model" + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "enable_original_uv": { + "name": "enable_original_uv", + "tooltip": "Use the original UV of the model instead of generating new UVs. When enabled, Meshy preserves existing textures from the uploaded model. If the model has no original UV, the quality of the output might not be as good." + }, + "pbr": { + "name": "pbr" + }, + "text_style_prompt": { + "name": "text_style_prompt", + "tooltip": "Describe your desired texture style of the object using text. Maximum 600 characters.Maximum 600 characters. Cannot be used at the same time as 'image_style'." + }, + "image_style": { + "name": "image_style", + "tooltip": "A 2d image to guide the texturing process. Can not be used at the same time with 'text_style_prompt'." + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } } }, "MinimaxHailuoVideoNode": { @@ -8075,6 +9685,52 @@ } } }, + "NormalizeImages": { + "display_name": "Normalize Images", + "inputs": { + "images": { + "name": "images", + "tooltip": "Image to process." + }, + "mean": { + "name": "mean", + "tooltip": "Mean value for normalization." + }, + "std": { + "name": "std", + "tooltip": "Standard deviation for normalization." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Processed images" + } + } + }, + "NormalizeVideoLatentStart": { + "display_name": "NormalizeVideoLatentStart", + "description": "Normalizes the initial frames of a video latent to match the mean and standard deviation of subsequent reference frames. Helps reduce differences between the starting frames and the rest of the video.", + "inputs": { + "latent": { + "name": "latent" + }, + "start_frame_count": { + "name": "start_frame_count", + "tooltip": "Number of latent frames to normalize, counted from the start" + }, + "reference_frame_count": { + "name": "reference_frame_count", + "tooltip": "Number of latent frames after the start frames to use as reference" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, "OpenAIChatConfig": { "display_name": "OpenAI ChatGPT Advanced Options", "description": "Allows specifying advanced configuration options for the OpenAI Chat Nodes.", @@ -8206,12 +9862,12 @@ } }, "OpenAIGPTImage1": { - "display_name": "OpenAI GPT Image 1", - "description": "Generates images synchronously via OpenAI's GPT Image 1 endpoint.", + "display_name": "OpenAI GPT Image 1.5", + "description": "Generates images synchronously via OpenAI's GPT Image endpoint.", "inputs": { "prompt": { "name": "prompt", - "tooltip": "Text prompt for GPT Image 1" + "tooltip": "Text prompt for GPT Image" }, "seed": { "name": "seed", @@ -8241,6 +9897,9 @@ "name": "mask", "tooltip": "Optional mask for inpainting (white areas will be replaced)" }, + "model": { + "name": "model" + }, "control_after_generate": { "name": "control after generate" } @@ -8582,265 +10241,6 @@ } } }, - "Pikadditions": { - "display_name": "Pikadditions (Video Object Insertion)", - "description": "Add any object or image into your video. Upload a video and specify what you'd like to add to create a seamlessly integrated result.", - "inputs": { - "video": { - "name": "video", - "tooltip": "The video to add an image to." - }, - "image": { - "name": "image", - "tooltip": "The image to add to the video." - }, - "prompt_text": { - "name": "prompt_text" - }, - "negative_prompt": { - "name": "negative_prompt" - }, - "seed": { - "name": "seed" - }, - "control_after_generate": { - "name": "control after generate" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikaffects": { - "display_name": "Pikaffects (Video Effects)", - "description": "Generate a video with a specific Pikaffect. Supported Pikaffects: Cake-ify, Crumble, Crush, Decapitate, Deflate, Dissolve, Explode, Eye-pop, Inflate, Levitate, Melt, Peel, Poke, Squish, Ta-da, Tear", - "inputs": { - "image": { - "name": "image", - "tooltip": "The reference image to apply the Pikaffect to." - }, - "pikaffect": { - "name": "pikaffect" - }, - "prompt_text": { - "name": "prompt_text" - }, - "negative_prompt": { - "name": "negative_prompt" - }, - "seed": { - "name": "seed" - }, - "control_after_generate": { - "name": "control after generate" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaImageToVideoNode2_2": { - "display_name": "Pika Image to Video", - "description": "Sends an image and prompt to the Pika API v2.2 to generate a video.", - "inputs": { - "image": { - "name": "image", - "tooltip": "The image to convert to video" - }, - "prompt_text": { - "name": "prompt_text" - }, - "negative_prompt": { - "name": "negative_prompt" - }, - "seed": { - "name": "seed" - }, - "resolution": { - "name": "resolution" - }, - "duration": { - "name": "duration" - }, - "control_after_generate": { - "name": "control after generate" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaScenesV2_2": { - "display_name": "Pika Scenes (Video Image Composition)", - "description": "Combine your images to create a video with the objects in them. Upload multiple images as ingredients and generate a high-quality video that incorporates all of them.", - "inputs": { - "prompt_text": { - "name": "prompt_text" - }, - "negative_prompt": { - "name": "negative_prompt" - }, - "seed": { - "name": "seed" - }, - "resolution": { - "name": "resolution" - }, - "duration": { - "name": "duration" - }, - "ingredients_mode": { - "name": "ingredients_mode" - }, - "aspect_ratio": { - "name": "aspect_ratio", - "tooltip": "Aspect ratio (width / height)" - }, - "image_ingredient_1": { - "name": "image_ingredient_1", - "tooltip": "Image that will be used as ingredient to create a video." - }, - "image_ingredient_2": { - "name": "image_ingredient_2", - "tooltip": "Image that will be used as ingredient to create a video." - }, - "image_ingredient_3": { - "name": "image_ingredient_3", - "tooltip": "Image that will be used as ingredient to create a video." - }, - "image_ingredient_4": { - "name": "image_ingredient_4", - "tooltip": "Image that will be used as ingredient to create a video." - }, - "image_ingredient_5": { - "name": "image_ingredient_5", - "tooltip": "Image that will be used as ingredient to create a video." - }, - "control_after_generate": { - "name": "control after generate" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaStartEndFrameNode2_2": { - "display_name": "Pika Start and End Frame to Video", - "description": "Generate a video by combining your first and last frame. Upload two images to define the start and end points, and let the AI create a smooth transition between them.", - "inputs": { - "image_start": { - "name": "image_start", - "tooltip": "The first image to combine." - }, - "image_end": { - "name": "image_end", - "tooltip": "The last image to combine." - }, - "prompt_text": { - "name": "prompt_text" - }, - "negative_prompt": { - "name": "negative_prompt" - }, - "seed": { - "name": "seed" - }, - "resolution": { - "name": "resolution" - }, - "duration": { - "name": "duration" - }, - "control_after_generate": { - "name": "control after generate" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikaswaps": { - "display_name": "Pika Swaps (Video Object Replacement)", - "description": "Swap out any object or region of your video with a new image or object. Define areas to replace either with a mask or coordinates.", - "inputs": { - "video": { - "name": "video", - "tooltip": "The video to swap an object in." - }, - "image": { - "name": "image", - "tooltip": "The image used to replace the masked object in the video." - }, - "mask": { - "name": "mask", - "tooltip": "Use the mask to define areas in the video to replace." - }, - "prompt_text": { - "name": "prompt_text" - }, - "negative_prompt": { - "name": "negative_prompt" - }, - "seed": { - "name": "seed" - }, - "region_to_modify": { - "name": "region_to_modify", - "tooltip": "Plaintext description of the object / region to modify." - }, - "control_after_generate": { - "name": "control after generate" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaTextToVideoNode2_2": { - "display_name": "Pika Text to Video", - "description": "Sends a text prompt to the Pika API v2.2 to generate a video.", - "inputs": { - "prompt_text": { - "name": "prompt_text" - }, - "negative_prompt": { - "name": "negative_prompt" - }, - "seed": { - "name": "seed" - }, - "resolution": { - "name": "resolution" - }, - "duration": { - "name": "duration" - }, - "aspect_ratio": { - "name": "aspect_ratio", - "tooltip": "Aspect ratio (width / height)" - }, - "control_after_generate": { - "name": "control after generate" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, "PixverseImageToVideoNode": { "display_name": "PixVerse Image to Video", "description": "Generates videos based on prompt and output_size.", @@ -8995,6 +10395,11 @@ "rho": { "name": "rho" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "PorterDuffImageComposite": { @@ -9026,7 +10431,7 @@ } }, "Preview3D": { - "display_name": "Preview 3D", + "display_name": "Preview 3D & Animation", "inputs": { "model_file": { "name": "model_file" @@ -9034,29 +10439,22 @@ "camera_info": { "name": "camera_info" }, + "bg_image": { + "name": "bg_image" + }, "image": { "name": "image" } } }, - "Preview3DAnimation": { - "display_name": "Preview 3D - Animation", - "inputs": { - "model_file": { - "name": "model_file" - }, - "camera_info": { - "name": "camera_info" - } - } - }, "PreviewAny": { "display_name": "Preview as Text", "inputs": { "source": { "name": "source" }, - "preview": {} + "preview": {}, + "previewMode": {} } }, "PreviewAudio": { @@ -9193,6 +10591,36 @@ } } }, + "RandomCropImages": { + "display_name": "Random Crop Images", + "inputs": { + "images": { + "name": "images", + "tooltip": "Image to process." + }, + "width": { + "name": "width", + "tooltip": "Crop width." + }, + "height": { + "name": "height", + "tooltip": "Crop height." + }, + "seed": { + "name": "seed", + "tooltip": "Random seed." + }, + "control_after_generate": { + "name": "control after generate" + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Processed images" + } + } + }, "RandomNoise": { "display_name": "RandomNoise", "inputs": { @@ -9202,6 +10630,11 @@ "control_after_generate": { "name": "control after generate" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RebatchImages": { @@ -9242,6 +10675,11 @@ "audio": { "name": "audio" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RecraftColorRGB": { @@ -9746,6 +11184,11 @@ "amount": { "name": "amount" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RepeatLatentBatch": { @@ -9759,6 +11202,51 @@ } } }, + "ReplaceText": { + "display_name": "Replace Text", + "inputs": { + "texts": { + "name": "texts", + "tooltip": "Text to process." + }, + "find": { + "name": "find", + "tooltip": "Text to find." + }, + "replace": { + "name": "replace", + "tooltip": "Text to replace with." + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "Processed texts" + } + } + }, + "ReplaceVideoLatentFrames": { + "display_name": "ReplaceVideoLatentFrames", + "inputs": { + "destination": { + "name": "destination", + "tooltip": "The destination latent where frames will be replaced." + }, + "index": { + "name": "index", + "tooltip": "The starting latent frame index in the destination latent where the source latent frames will be placed. Negative values count from the end." + }, + "source": { + "name": "source", + "tooltip": "The source latent providing frames to insert into the destination latent. If not provided, the destination latent is returned unchanged." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "RescaleCFG": { "display_name": "RescaleCFG", "inputs": { @@ -9788,6 +11276,104 @@ "interpolation": { "name": "interpolation" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ResizeImageMaskNode": { + "display_name": "Resize Image/Mask", + "description": "Resize an image or mask using various scaling methods.", + "inputs": { + "input": { + "name": "input" + }, + "resize_type": { + "name": "resize_type", + "tooltip": "Select how to resize: by exact dimensions, scale factor, matching another image, etc." + }, + "scale_method": { + "name": "scale_method", + "tooltip": "Interpolation algorithm. 'area' is best for downscaling, 'lanczos' for upscaling, 'nearest-exact' for pixel art." + }, + "resize_type_crop": { + "name": "crop" + }, + "resize_type_height": { + "name": "height" + }, + "resize_type_width": { + "name": "width" + } + }, + "outputs": { + "0": { + "name": "resized", + "tooltip": null + } + } + }, + "ResizeImagesByLongerEdge": { + "display_name": "Resize Images by Longer Edge", + "inputs": { + "images": { + "name": "images", + "tooltip": "Image to process." + }, + "longer_edge": { + "name": "longer_edge", + "tooltip": "Target length for the longer edge." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Processed images" + } + } + }, + "ResizeImagesByShorterEdge": { + "display_name": "Resize Images by Shorter Edge", + "inputs": { + "images": { + "name": "images", + "tooltip": "Image to process." + }, + "shorter_edge": { + "name": "shorter_edge", + "tooltip": "Target length for the shorter edge." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Processed images" + } + } + }, + "ResolutionBucket": { + "display_name": "Resolution Bucket", + "inputs": { + "latents": { + "name": "latents", + "tooltip": "List of latent dicts to bucket by resolution." + }, + "conditioning": { + "name": "conditioning", + "tooltip": "List of conditioning lists (must match latents length)." + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "List of batched latent dicts, one per resolution bucket." + }, + "1": { + "name": "conditioning", + "tooltip": "List of condition lists, one per resolution bucket." + } } }, "Rodin3D_Detail": { @@ -10065,10 +11651,12 @@ }, "outputs": { "0": { - "name": "output" + "name": "output", + "tooltip": null }, "1": { - "name": "denoised_output" + "name": "denoised_output", + "tooltip": null } } }, @@ -10093,10 +11681,12 @@ }, "outputs": { "0": { - "name": "output" + "name": "output", + "tooltip": null }, "1": { - "name": "denoised_output" + "name": "denoised_output", + "tooltip": null } } }, @@ -10133,6 +11723,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_2M_SDE": { @@ -10150,6 +11745,11 @@ "noise_device": { "name": "noise_device" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_2S_Ancestral": { @@ -10161,6 +11761,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_3M_SDE": { @@ -10175,6 +11780,11 @@ "noise_device": { "name": "noise_device" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_SDE": { @@ -10192,6 +11802,11 @@ "noise_device": { "name": "noise_device" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerER_SDE": { @@ -10210,6 +11825,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerAncestral": { @@ -10221,6 +11841,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerAncestralCFGPP": { @@ -10232,6 +11857,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerCFGpp": { @@ -10272,6 +11902,11 @@ "order": { "name": "order" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerSASolver": { @@ -10304,6 +11939,37 @@ "simple_order_2": { "name": "simple_order_2" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerSEEDS2": { + "display_name": "SamplerSEEDS2", + "description": "This sampler node can represent multiple samplers:\n\nseeds_2\n- default setting\n\nexp_heun_2_x0\n- solver_type=phi_2, r=1.0, eta=0.0\n\nexp_heun_2_x0_sde\n- solver_type=phi_2, r=1.0, eta=1.0, s_noise=1.0", + "inputs": { + "solver_type": { + "name": "solver_type" + }, + "eta": { + "name": "eta", + "tooltip": "Stochastic strength" + }, + "s_noise": { + "name": "s_noise", + "tooltip": "SDE noise multiplier" + }, + "r": { + "name": "r", + "tooltip": "Relative step size for the intermediate stage (c2 node)" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplingPercentToSigma": { @@ -10322,7 +11988,8 @@ }, "outputs": { "0": { - "name": "sigma_value" + "name": "sigma_value", + "tooltip": null } } }, @@ -10442,6 +12109,44 @@ } } }, + "SaveImageDataSetToFolder": { + "display_name": "Save Image Dataset to Folder", + "inputs": { + "images": { + "name": "images", + "tooltip": "List of images to save." + }, + "folder_name": { + "name": "folder_name", + "tooltip": "Name of the folder to save images to (inside output directory)." + }, + "filename_prefix": { + "name": "filename_prefix", + "tooltip": "Prefix for saved image filenames." + } + } + }, + "SaveImageTextDataSetToFolder": { + "display_name": "Save Image and Text Dataset to Folder", + "inputs": { + "images": { + "name": "images", + "tooltip": "List of images to save." + }, + "texts": { + "name": "texts", + "tooltip": "List of text captions to save." + }, + "folder_name": { + "name": "folder_name", + "tooltip": "Name of the folder to save images to (inside output directory)." + }, + "filename_prefix": { + "name": "filename_prefix", + "tooltip": "Prefix for saved image filenames." + } + } + }, "SaveImageWebsocket": { "display_name": "SaveImageWebsocket", "inputs": { @@ -10461,7 +12166,7 @@ } } }, - "SaveLoRANode": { + "SaveLoRA": { "display_name": "Save LoRA Weights", "inputs": { "lora": { @@ -10491,6 +12196,27 @@ } } }, + "SaveTrainingDataset": { + "display_name": "Save Training Dataset", + "inputs": { + "latents": { + "name": "latents", + "tooltip": "List of latent dicts from MakeTrainingDataset." + }, + "conditioning": { + "name": "conditioning", + "tooltip": "List of conditioning lists from MakeTrainingDataset." + }, + "folder_name": { + "name": "folder_name", + "tooltip": "Name of folder to save dataset (inside output directory)." + }, + "shard_size": { + "name": "shard_size", + "tooltip": "Number of samples per shard file." + } + } + }, "SaveVideo": { "display_name": "Save Video", "description": "Saves the input images to your ComfyUI output directory.", @@ -10612,6 +12338,11 @@ "denoise": { "name": "denoise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SelfAttentionGuidance": { @@ -10659,6 +12390,11 @@ "sigma": { "name": "sigma" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SetHookKeyframes": { @@ -10699,6 +12435,58 @@ } } }, + "ShuffleDataset": { + "display_name": "Shuffle Image Dataset", + "inputs": { + "images": { + "name": "images", + "tooltip": "List of images to process." + }, + "seed": { + "name": "seed", + "tooltip": "Random seed." + }, + "control_after_generate": { + "name": "control after generate" + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Processed images" + } + } + }, + "ShuffleImageTextDataset": { + "display_name": "Shuffle Image-Text Dataset", + "inputs": { + "images": { + "name": "images", + "tooltip": "List of images to shuffle." + }, + "texts": { + "name": "texts", + "tooltip": "List of texts to shuffle." + }, + "seed": { + "name": "seed", + "tooltip": "Random seed." + }, + "control_after_generate": { + "name": "control after generate" + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Shuffled images" + }, + "1": { + "name": "texts", + "tooltip": "Shuffled texts" + } + } + }, "SkipLayerGuidanceDiT": { "display_name": "SkipLayerGuidanceDiT", "description": "Generic version of SkipLayerGuidance node that can be used on every DiT model.", @@ -10795,6 +12583,11 @@ "height": { "name": "height" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SplitAudioChannels": { @@ -10807,10 +12600,12 @@ }, "outputs": { "0": { - "name": "left" + "name": "left", + "tooltip": null }, "1": { - "name": "right" + "name": "right", + "tooltip": null } } }, @@ -10842,10 +12637,12 @@ }, "outputs": { "0": { - "name": "high_sigmas" + "name": "high_sigmas", + "tooltip": null }, "1": { - "name": "low_sigmas" + "name": "low_sigmas", + "tooltip": null } } }, @@ -10861,10 +12658,12 @@ }, "outputs": { "0": { - "name": "high_sigmas" + "name": "high_sigmas", + "tooltip": null }, "1": { - "name": "low_sigmas" + "name": "low_sigmas", + "tooltip": null } } }, @@ -11468,6 +13267,21 @@ } } }, + "StripWhitespace": { + "display_name": "Strip Whitespace", + "inputs": { + "texts": { + "name": "texts", + "tooltip": "Text to process." + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "Processed texts" + } + } + }, "StyleModelApply": { "display_name": "Apply Style Model", "inputs": { @@ -11636,6 +13450,84 @@ } } }, + "TencentImageToModelNode": { + "display_name": "Hunyuan3D: Image(s) to Model (Pro)", + "inputs": { + "model": { + "name": "model", + "tooltip": "The LowPoly option is unavailable for the `3.1` model." + }, + "image": { + "name": "image" + }, + "face_count": { + "name": "face_count" + }, + "generate_type": { + "name": "generate_type" + }, + "seed": { + "name": "seed", + "tooltip": "Seed controls whether the node should re-run; results are non-deterministic regardless of seed." + }, + "image_left": { + "name": "image_left" + }, + "image_right": { + "name": "image_right" + }, + "image_back": { + "name": "image_back" + }, + "control_after_generate": { + "name": "control after generate" + }, + "generate_type_pbr": { + "name": "pbr" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + } + } + }, + "TencentTextToModelNode": { + "display_name": "Hunyuan3D: Text to Model (Pro)", + "inputs": { + "model": { + "name": "model", + "tooltip": "The LowPoly option is unavailable for the `3.1` model." + }, + "prompt": { + "name": "prompt", + "tooltip": "Supports up to 1024 characters." + }, + "face_count": { + "name": "face_count" + }, + "generate_type": { + "name": "generate_type" + }, + "seed": { + "name": "seed", + "tooltip": "Seed controls whether the node should re-run; results are non-deterministic regardless of seed." + }, + "control_after_generate": { + "name": "control after generate" + }, + "generate_type_pbr": { + "name": "pbr" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + } + } + }, "TextEncodeAceStepAudio": { "display_name": "TextEncodeAceStepAudio", "inputs": { @@ -11731,6 +13623,70 @@ } } }, + "TextEncodeZImageOmni": { + "display_name": "TextEncodeZImageOmni", + "inputs": { + "clip": { + "name": "clip" + }, + "prompt": { + "name": "prompt" + }, + "auto_resize_images": { + "name": "auto_resize_images" + }, + "image_encoder": { + "name": "image_encoder" + }, + "vae": { + "name": "vae" + }, + "image1": { + "name": "image1" + }, + "image2": { + "name": "image2" + }, + "image3": { + "name": "image3" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TextToLowercase": { + "display_name": "Text to Lowercase", + "inputs": { + "texts": { + "name": "texts", + "tooltip": "Text to process." + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "Processed texts" + } + } + }, + "TextToUppercase": { + "display_name": "Text to Uppercase", + "inputs": { + "texts": { + "name": "texts", + "tooltip": "Text to process." + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "Processed texts" + } + } + }, "ThresholdMask": { "display_name": "ThresholdMask", "inputs": { @@ -11740,6 +13696,11 @@ "value": { "name": "value" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "TomePatchModel": { @@ -11953,22 +13914,30 @@ "name": "existing_lora", "tooltip": "The existing LoRA to append to. Set to None for new LoRA." }, + "bucket_mode": { + "name": "bucket_mode", + "tooltip": "Enable resolution bucket mode. When enabled, expects pre-bucketed latents from ResolutionBucket node." + }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { - "name": "model_with_lora" + "name": "model", + "tooltip": "Model with LoRA applied" }, "1": { - "name": "lora" + "name": "lora", + "tooltip": "LoRA weights" }, "2": { - "name": "loss" + "name": "loss_map", + "tooltip": "Loss history" }, "3": { - "name": "steps" + "name": "steps", + "tooltip": "Total training steps" } } }, @@ -11987,6 +13956,11 @@ "name": "duration", "tooltip": "Duration in seconds" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "TrimVideoLatent": { @@ -12045,6 +14019,45 @@ }, "texture_format": { "name": "texture_format" + }, + "force_symmetry": { + "name": "force_symmetry" + }, + "flatten_bottom": { + "name": "flatten_bottom" + }, + "flatten_bottom_threshold": { + "name": "flatten_bottom_threshold" + }, + "pivot_to_center_bottom": { + "name": "pivot_to_center_bottom" + }, + "scale_factor": { + "name": "scale_factor" + }, + "with_animation": { + "name": "with_animation" + }, + "pack_uv": { + "name": "pack_uv" + }, + "bake": { + "name": "bake" + }, + "part_names": { + "name": "part_names" + }, + "fbx_preset": { + "name": "fbx_preset" + }, + "export_vertex_colors": { + "name": "export_vertex_colors" + }, + "export_orientation": { + "name": "export_orientation" + }, + "animate_in_place": { + "name": "animate_in_place" } } }, @@ -12087,6 +14100,9 @@ }, "quad": { "name": "quad" + }, + "geometry_quality": { + "name": "geometry_quality" } }, "outputs": { @@ -12145,6 +14161,9 @@ }, "quad": { "name": "quad" + }, + "geometry_quality": { + "name": "geometry_quality" } }, "outputs": { @@ -12255,6 +14274,9 @@ }, "quad": { "name": "quad" + }, + "geometry_quality": { + "name": "geometry_quality" } }, "outputs": { @@ -12301,6 +14323,25 @@ } } }, + "TruncateText": { + "display_name": "Truncate Text", + "inputs": { + "texts": { + "name": "texts", + "tooltip": "Text to process." + }, + "max_length": { + "name": "max_length", + "tooltip": "Maximum text length." + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "Processed texts" + } + } + }, "unCLIPCheckpointLoader": { "display_name": "unCLIPCheckpointLoader", "inputs": { @@ -12467,6 +14508,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEDecodeHunyuan3D": { @@ -12536,6 +14582,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEEncodeForInpaint": { @@ -12599,6 +14650,58 @@ } } }, + "Veo3FirstLastFrameNode": { + "display_name": "Google Veo 3 First-Last-Frame to Video", + "description": "Generate video using prompt and first and last frames.", + "inputs": { + "prompt": { + "name": "prompt", + "tooltip": "Text description of the video" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Negative text prompt to guide what to avoid in the video" + }, + "resolution": { + "name": "resolution" + }, + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "Aspect ratio of the output video" + }, + "duration": { + "name": "duration", + "tooltip": "Duration of the output video in seconds" + }, + "seed": { + "name": "seed", + "tooltip": "Seed for video generation" + }, + "first_frame": { + "name": "first_frame", + "tooltip": "Start frame" + }, + "last_frame": { + "name": "last_frame", + "tooltip": "End frame" + }, + "model": { + "name": "model" + }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "Generate audio for the video." + }, + "control_after_generate": { + "name": "control after generate" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "Veo3VideoGenerationNode": { "display_name": "Google Veo 3 Video Generation", "description": "Generates videos from text prompts using Google's Veo 3 API", @@ -12621,7 +14724,7 @@ }, "enhance_prompt": { "name": "enhance_prompt", - "tooltip": "Whether to enhance the prompt with AI assistance" + "tooltip": "This parameter is deprecated and ignored." }, "person_generation": { "name": "person_generation", @@ -12725,6 +14828,166 @@ } } }, + "Vidu2ImageToVideoNode": { + "display_name": "Vidu2 Image-to-Video Generation", + "description": "Generate a video from an image and an optional prompt.", + "inputs": { + "model": { + "name": "model" + }, + "image": { + "name": "image", + "tooltip": "An image to be used as the start frame of the generated video." + }, + "prompt": { + "name": "prompt", + "tooltip": "An optional text prompt for video generation (max 2000 characters)." + }, + "duration": { + "name": "duration" + }, + "seed": { + "name": "seed" + }, + "resolution": { + "name": "resolution" + }, + "movement_amplitude": { + "name": "movement_amplitude", + "tooltip": "The movement amplitude of objects in the frame." + }, + "control_after_generate": { + "name": "control after generate" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2ReferenceVideoNode": { + "display_name": "Vidu2 Reference-to-Video Generation", + "description": "Generate a video from multiple reference images and a prompt.", + "inputs": { + "model": { + "name": "model" + }, + "subjects": { + "name": "subjects", + "tooltip": "For each subject, provide up to 3 reference images (7 images total across all subjects). Reference them in prompts via @subject{subject_id}." + }, + "prompt": { + "name": "prompt", + "tooltip": "When enabled, the video will include generated speech and background music based on the prompt." + }, + "audio": { + "name": "audio", + "tooltip": "When enabled video will contain generated speech and background music based on the prompt." + }, + "duration": { + "name": "duration" + }, + "seed": { + "name": "seed" + }, + "aspect_ratio": { + "name": "aspect_ratio" + }, + "resolution": { + "name": "resolution" + }, + "movement_amplitude": { + "name": "movement_amplitude", + "tooltip": "The movement amplitude of objects in the frame." + }, + "control_after_generate": { + "name": "control after generate" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2StartEndToVideoNode": { + "display_name": "Vidu2 Start/End Frame-to-Video Generation", + "description": "Generate a video from a start frame, an end frame, and a prompt.", + "inputs": { + "model": { + "name": "model" + }, + "first_frame": { + "name": "first_frame" + }, + "end_frame": { + "name": "end_frame" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt description (max 2000 characters)." + }, + "duration": { + "name": "duration" + }, + "seed": { + "name": "seed" + }, + "resolution": { + "name": "resolution" + }, + "movement_amplitude": { + "name": "movement_amplitude", + "tooltip": "The movement amplitude of objects in the frame." + }, + "control_after_generate": { + "name": "control after generate" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2TextToVideoNode": { + "display_name": "Vidu2 Text-to-Video Generation", + "description": "Generate video from a text prompt", + "inputs": { + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "A textual description for video generation, with a maximum length of 2000 characters." + }, + "duration": { + "name": "duration" + }, + "seed": { + "name": "seed" + }, + "aspect_ratio": { + "name": "aspect_ratio" + }, + "resolution": { + "name": "resolution" + }, + "background_music": { + "name": "background_music", + "tooltip": "Whether to add background music to the generated video." + }, + "control_after_generate": { + "name": "control after generate" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "ViduImageToVideoNode": { "display_name": "Vidu Image To Video Generation", "description": "Generate video from image and optional prompt", @@ -12769,7 +15032,7 @@ }, "ViduReferenceVideoNode": { "display_name": "Vidu Reference To Video Generation", - "description": "Generate video from multiple images and prompt", + "description": "Generate video from multiple images and a prompt", "inputs": { "model": { "name": "model", @@ -12861,7 +15124,7 @@ }, "ViduTextToVideoNode": { "display_name": "Vidu Text To Video Generation", - "description": "Generate video from text prompt", + "description": "Generate video from a text prompt", "inputs": { "model": { "name": "model", @@ -12951,6 +15214,11 @@ "eps_s": { "name": "eps_s" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Wan22FunControlToVideo": { @@ -13251,6 +15519,10 @@ "fuse_method": { "name": "fuse_method", "tooltip": "The method to use to fuse the context windows." + }, + "freenoise": { + "name": "freenoise", + "tooltip": "Whether to apply FreeNoise noise shuffling, improves window blending." } }, "outputs": { @@ -13457,7 +15729,7 @@ }, "WanImageToImageApi": { "display_name": "Wan Image to Image", - "description": "Generates an image from one or two input images and a text prompt. The output image is currently fixed at 1.6 MP; its aspect ratio matches the input image(s).", + "description": "Generates an image from one or two input images and a text prompt. The output image is currently fixed at 1.6 MP, and its aspect ratio matches the input image(s).", "inputs": { "model": { "name": "model", @@ -13465,15 +15737,15 @@ }, "image": { "name": "image", - "tooltip": "Single-image editing or multi-image fusion, maximum 2 images." + "tooltip": "Single-image editing or multi-image fusion. Maximum 2 images." }, "prompt": { "name": "prompt", - "tooltip": "Prompt used to describe the elements and visual features, supports English/Chinese." + "tooltip": "Prompt describing the elements and visual features. Supports English and Chinese." }, "negative_prompt": { "name": "negative_prompt", - "tooltip": "Negative text prompt to guide what to avoid." + "tooltip": "Negative prompt describing what to avoid." }, "seed": { "name": "seed", @@ -13481,7 +15753,7 @@ }, "watermark": { "name": "watermark", - "tooltip": "Whether to add an \"AI generated\" watermark to the result." + "tooltip": "Whether to add an AI-generated watermark to the result." }, "control_after_generate": { "name": "control after generate" @@ -13541,7 +15813,7 @@ }, "WanImageToVideoApi": { "display_name": "Wan Image to Video", - "description": "Generates video based on the first frame and text prompt.", + "description": "Generates a video from the first frame and a text prompt.", "inputs": { "model": { "name": "model", @@ -13552,22 +15824,22 @@ }, "prompt": { "name": "prompt", - "tooltip": "Prompt used to describe the elements and visual features, supports English/Chinese." + "tooltip": "Prompt describing the elements and visual features. Supports English and Chinese." }, "negative_prompt": { "name": "negative_prompt", - "tooltip": "Negative text prompt to guide what to avoid." + "tooltip": "Negative prompt describing what to avoid." }, "resolution": { "name": "resolution" }, "duration": { "name": "duration", - "tooltip": "Available durations: 5 and 10 seconds" + "tooltip": "Duration 15 available only for WAN2.6 model." }, "audio": { "name": "audio", - "tooltip": "Audio must contain a clear, loud voice, without extraneous noise, background music." + "tooltip": "Audio must contain a clear, loud voice, without extraneous noise or background music." }, "seed": { "name": "seed", @@ -13575,7 +15847,7 @@ }, "generate_audio": { "name": "generate_audio", - "tooltip": "If there is no audio input, generate audio automatically." + "tooltip": "If no audio input is provided, generate audio automatically." }, "prompt_extend": { "name": "prompt_extend", @@ -13583,7 +15855,11 @@ }, "watermark": { "name": "watermark", - "tooltip": "Whether to add an \"AI generated\" watermark to the result." + "tooltip": "Whether to add an AI-generated watermark to the result." + }, + "shot_type": { + "name": "shot_type", + "tooltip": "Specifies the shot type for the generated video, that is, whether the video is a single continuous shot or multiple shots with cuts. This parameter takes effect only when prompt_extend is True." }, "control_after_generate": { "name": "control after generate" @@ -13595,6 +15871,196 @@ } } }, + "WanInfiniteTalkToVideo": { + "display_name": "WanInfiniteTalkToVideo", + "inputs": { + "mode": { + "name": "mode" + }, + "model": { + "name": "model" + }, + "model_patch": { + "name": "model_patch" + }, + "positive": { + "name": "positive" + }, + "negative": { + "name": "negative" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "width" + }, + "height": { + "name": "height" + }, + "length": { + "name": "length" + }, + "audio_encoder_output_1": { + "name": "audio_encoder_output_1" + }, + "motion_frame_count": { + "name": "motion_frame_count", + "tooltip": "Number of previous frames to use as motion context." + }, + "audio_scale": { + "name": "audio_scale" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "start_image": { + "name": "start_image" + }, + "previous_frames": { + "name": "previous_frames" + } + }, + "outputs": { + "0": { + "name": "model", + "tooltip": null + }, + "1": { + "name": "positive", + "tooltip": null + }, + "2": { + "name": "negative", + "tooltip": null + }, + "3": { + "name": "latent", + "tooltip": null + }, + "4": { + "name": "trim_image", + "tooltip": null + } + } + }, + "WanMoveConcatTrack": { + "display_name": "WanMoveConcatTrack", + "inputs": { + "tracks_1": { + "name": "tracks_1" + }, + "tracks_2": { + "name": "tracks_2" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanMoveTracksFromCoords": { + "display_name": "WanMoveTracksFromCoords", + "inputs": { + "track_coords": { + "name": "track_coords" + }, + "track_mask": { + "name": "track_mask" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "track_length", + "tooltip": null + } + } + }, + "WanMoveTrackToVideo": { + "display_name": "WanMoveTrackToVideo", + "inputs": { + "positive": { + "name": "positive" + }, + "negative": { + "name": "negative" + }, + "vae": { + "name": "vae" + }, + "strength": { + "name": "strength", + "tooltip": "Strength of the track conditioning." + }, + "width": { + "name": "width" + }, + "height": { + "name": "height" + }, + "length": { + "name": "length" + }, + "batch_size": { + "name": "batch_size" + }, + "start_image": { + "name": "start_image" + }, + "tracks": { + "name": "tracks" + }, + "clip_vision_output": { + "name": "clip_vision_output" + } + }, + "outputs": { + "0": { + "name": "positive", + "tooltip": null + }, + "1": { + "name": "negative", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "WanMoveVisualizeTracks": { + "display_name": "WanMoveVisualizeTracks", + "inputs": { + "images": { + "name": "images" + }, + "line_resolution": { + "name": "line_resolution" + }, + "circle_size": { + "name": "circle_size" + }, + "opacity": { + "name": "opacity" + }, + "line_width": { + "name": "line_width" + }, + "tracks": { + "name": "tracks" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WanPhantomSubjectToVideo": { "display_name": "WanPhantomSubjectToVideo", "inputs": { @@ -13642,6 +16108,51 @@ } } }, + "WanReferenceVideoApi": { + "display_name": "Wan Reference to Video", + "description": "Use the character and voice from input videos, combined with a prompt, to generate a new video that maintains character consistency.", + "inputs": { + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt describing the elements and visual features. Supports English and Chinese. Use identifiers such as `character1` and `character2` to refer to the reference characters." + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Negative prompt describing what to avoid." + }, + "reference_videos": { + "name": "reference_videos" + }, + "size": { + "name": "size" + }, + "duration": { + "name": "duration" + }, + "seed": { + "name": "seed" + }, + "shot_type": { + "name": "shot_type", + "tooltip": "Specifies the shot type for the generated video, that is, whether the video is a single continuous shot or multiple shots with cuts." + }, + "watermark": { + "name": "watermark", + "tooltip": "Whether to add an AI-generated watermark to the result." + }, + "control_after_generate": { + "name": "control after generate" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WanSoundImageToVideo": { "display_name": "WanSoundImageToVideo", "inputs": { @@ -13739,7 +16250,7 @@ }, "WanTextToImageApi": { "display_name": "Wan Text to Image", - "description": "Generates image based on text prompt.", + "description": "Generates an image based on a text prompt.", "inputs": { "model": { "name": "model", @@ -13747,11 +16258,11 @@ }, "prompt": { "name": "prompt", - "tooltip": "Prompt used to describe the elements and visual features, supports English/Chinese." + "tooltip": "Prompt describing the elements and visual features. Supports English and Chinese." }, "negative_prompt": { "name": "negative_prompt", - "tooltip": "Negative text prompt to guide what to avoid." + "tooltip": "Negative prompt describing what to avoid." }, "width": { "name": "width" @@ -13769,7 +16280,7 @@ }, "watermark": { "name": "watermark", - "tooltip": "Whether to add an \"AI generated\" watermark to the result." + "tooltip": "Whether to add an AI-generated watermark to the result." }, "control_after_generate": { "name": "control after generate" @@ -13783,7 +16294,7 @@ }, "WanTextToVideoApi": { "display_name": "Wan Text to Video", - "description": "Generates video based on text prompt.", + "description": "Generates a video based on a text prompt.", "inputs": { "model": { "name": "model", @@ -13791,22 +16302,22 @@ }, "prompt": { "name": "prompt", - "tooltip": "Prompt used to describe the elements and visual features, supports English/Chinese." + "tooltip": "Prompt describing the elements and visual features. Supports English and Chinese." }, "negative_prompt": { "name": "negative_prompt", - "tooltip": "Negative text prompt to guide what to avoid." + "tooltip": "Negative prompt describing what to avoid." }, "size": { "name": "size" }, "duration": { "name": "duration", - "tooltip": "Available durations: 5 and 10 seconds" + "tooltip": "A 15-second duration is available only for the Wan 2.6 model." }, "audio": { "name": "audio", - "tooltip": "Audio must contain a clear, loud voice, without extraneous noise, background music." + "tooltip": "Audio must contain a clear, loud voice, without extraneous noise or background music." }, "seed": { "name": "seed", @@ -13814,7 +16325,7 @@ }, "generate_audio": { "name": "generate_audio", - "tooltip": "If there is no audio input, generate audio automatically." + "tooltip": "If no audio input is provided, generate audio automatically." }, "prompt_extend": { "name": "prompt_extend", @@ -13822,7 +16333,11 @@ }, "watermark": { "name": "watermark", - "tooltip": "Whether to add an \"AI generated\" watermark to the result." + "tooltip": "Whether to add an AI-generated watermark to the result." + }, + "shot_type": { + "name": "shot_type", + "tooltip": "Specifies the shot type for the generated video, that is, whether the video is a single continuous shot or multiple shots with cuts. This parameter takes effect only when prompt_extend is True." }, "control_after_generate": { "name": "control after generate" @@ -13945,6 +16460,43 @@ } } }, + "WavespeedFlashVSRNode": { + "display_name": "FlashVSR Video Upscale", + "description": "Fast, high-quality video upscaler that boosts resolution and restores clarity for low-resolution or blurry footage.", + "inputs": { + "video": { + "name": "video" + }, + "target_resolution": { + "name": "target_resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WavespeedImageUpscaleNode": { + "display_name": "WaveSpeed Image Upscale", + "description": "Boost image resolution and quality, upscaling photos to 4K or 8K for sharp, detailed results.", + "inputs": { + "model": { + "name": "model" + }, + "image": { + "name": "image" + }, + "target_resolution": { + "name": "target_resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WebcamCapture": { "display_name": "Webcam Capture", "inputs": { @@ -13962,5 +16514,31 @@ }, "waiting for camera___": {} } + }, + "ZImageFunControlnet": { + "display_name": "ZImageFunControlnet", + "inputs": { + "model": { + "name": "model" + }, + "model_patch": { + "name": "model_patch" + }, + "vae": { + "name": "vae" + }, + "strength": { + "name": "strength" + }, + "image": { + "name": "image" + }, + "inpaint_image": { + "name": "inpaint_image" + }, + "mask": { + "name": "mask" + } + } } } \ No newline at end of file diff --git a/src/locales/en/settings.json b/src/locales/en/settings.json index 06dcb50e5..a82e2cb51 100644 --- a/src/locales/en/settings.json +++ b/src/locales/en/settings.json @@ -1,4 +1,30 @@ { + "Comfy-Desktop_AutoUpdate": { + "name": "Automatically check for updates" + }, + "Comfy-Desktop_SendStatistics": { + "name": "Send anonymous usage metrics" + }, + "Comfy-Desktop_UV_PypiInstallMirror": { + "name": "Pypi Install Mirror", + "tooltip": "Default pip install mirror" + }, + "Comfy-Desktop_UV_PythonInstallMirror": { + "name": "Python Install Mirror", + "tooltip": "Managed Python installations are downloaded from the Astral python-build-standalone project. This variable can be set to a mirror URL to use a different source for Python installations. The provided URL will replace https://github.com/astral-sh/python-build-standalone/releases/download in, e.g., https://github.com/astral-sh/python-build-standalone/releases/download/20240713/cpython-3.12.4%2B20240713-aarch64-apple-darwin-install_only.tar.gz. Distributions can be read from a local directory by using the file:// URL scheme." + }, + "Comfy-Desktop_UV_TorchInstallMirror": { + "name": "Torch Install Mirror", + "tooltip": "Pip install mirror for pytorch" + }, + "Comfy-Desktop_WindowStyle": { + "name": "Window Style", + "tooltip": "Custom: Replace the system title bar with ComfyUI's Top menu", + "options": { + "default": "default", + "custom": "custom" + } + }, "Comfy_Canvas_BackgroundImage": { "name": "Canvas background image", "tooltip": "Image URL for the canvas background. You can right-click an image in the outputs panel and select \"Set as Background\" to use it, or upload your own image using the upload button." @@ -53,6 +79,17 @@ "Comfy_EnableWorkflowViewRestore": { "name": "Save and restore canvas position and zoom level in workflows" }, + "Comfy_Execution_PreviewMethod": { + "name": "Live preview method", + "tooltip": "Live preview method during image generation. \"default\" uses the server CLI setting.", + "options": { + "default": "default", + "none": "none", + "auto": "auto", + "latent2rgb": "latent2rgb", + "taesd": "taesd" + } + }, "Comfy_FloatRoundingPrecision": { "name": "Float widget rounding decimal places [0 = auto].", "tooltip": "(requires page reload)" @@ -74,6 +111,10 @@ "Arrow": "Arrow" } }, + "Comfy_Graph_LiveSelection": { + "name": "Live selection", + "tooltip": "When enabled, nodes are selected/deselected in real-time as you drag the selection rectangle, similar to other design tools." + }, "Comfy_Graph_ZoomSpeed": { "name": "Canvas zoom speed" }, @@ -140,6 +181,15 @@ "name": "Light Intensity Minimum", "tooltip": "Sets the minimum allowable light intensity value for 3D scenes. This defines the lower brightness limit that can be set when adjusting lighting in any 3D widget." }, + "Comfy_Load3D_PLYEngine": { + "name": "PLY Engine", + "tooltip": "Select the engine for loading PLY files. \"threejs\" uses the native Three.js PLYLoader (best for mesh PLY files). \"fastply\" uses an optimized loader for ASCII point cloud PLY files. \"sparkjs\" uses Spark.js for 3D Gaussian Splatting PLY files.", + "options": { + "threejs": "threejs", + "fastply": "fastply", + "sparkjs": "sparkjs" + } + }, "Comfy_Load3D_ShowGrid": { "name": "Initial Grid Visibility", "tooltip": "Controls whether the grid is visible by default when a new 3D widget is created. This default can still be toggled individually for each widget after creation." @@ -155,10 +205,6 @@ "name": "Lock brush adjustment to dominant axis", "tooltip": "When enabled, brush adjustments will only affect size OR hardness based on which direction you move more" }, - "Comfy_MaskEditor_UseNewEditor": { - "name": "Use new mask editor", - "tooltip": "Switch to the new mask editor interface" - }, "Comfy_ModelLibrary_AutoLoadAll": { "name": "Automatically load all model folders", "tooltip": "If true, all folders will load as soon as you open the model library (this may cause delays while it loads). If false, root level model folders will only load once you click on them." @@ -174,6 +220,10 @@ "Comfy_Node_AllowImageSizeDraw": { "name": "Show width × height below the image preview" }, + "Comfy_Node_AlwaysShowAdvancedWidgets": { + "name": "Always show advanced widgets on all nodes", + "tooltip": "When enabled, advanced widgets are always visible on all nodes without needing to expand them individually." + }, "Comfy_Node_AutoSnapLinkToSlot": { "name": "Auto snap link to node slot", "tooltip": "When dragging a link over a node, the link automatically snap to a viable input slot on the node" @@ -282,6 +332,10 @@ "name": "Queue history size", "tooltip": "The maximum number of tasks that show in the queue history." }, + "Comfy_Queue_QPOV2": { + "name": "Use the unified job queue in the Assets side panel", + "tooltip": "Replaces the floating job queue panel with an equivalent job queue embedded in the Assets side panel. You can disable this to return to the floating panel layout." + }, "Comfy_QueueButton_BatchCountLimit": { "name": "Batch count limit", "tooltip": "The maximum number of tasks added to the queue at one button click" @@ -323,9 +377,17 @@ "Comfy_TreeExplorer_ItemPadding": { "name": "Tree explorer item padding" }, + "Comfy_UI_TabBarLayout": { + "name": "Tab Bar Layout", + "tooltip": "Controls the layout of the tab bar. \"Integrated\" moves Help and User controls into the tab bar area.", + "options": { + "Default": "Default", + "Integrated": "Integrated" + } + }, "Comfy_UseNewMenu": { "name": "Use new menu", - "tooltip": "Menu bar position. On mobile devices, the menu is always shown at the top.", + "tooltip": "Enable the redesigned top menu bar.", "options": { "Disabled": "Disabled", "Top": "Top" diff --git a/src/locales/es/commands.json b/src/locales/es/commands.json index ebd076a40..642a7a4dd 100644 --- a/src/locales/es/commands.json +++ b/src/locales/es/commands.json @@ -1,4 +1,40 @@ { + "Comfy-Desktop_CheckForUpdates": { + "label": "Buscar actualizaciones" + }, + "Comfy-Desktop_Folders_OpenCustomNodesFolder": { + "label": "Abrir carpeta de nodos personalizados" + }, + "Comfy-Desktop_Folders_OpenInputsFolder": { + "label": "Abrir carpeta de entradas" + }, + "Comfy-Desktop_Folders_OpenLogsFolder": { + "label": "Abrir carpeta de registros" + }, + "Comfy-Desktop_Folders_OpenModelConfig": { + "label": "Abrir extra_model_paths.yaml" + }, + "Comfy-Desktop_Folders_OpenModelsFolder": { + "label": "Abrir carpeta de modelos" + }, + "Comfy-Desktop_Folders_OpenOutputsFolder": { + "label": "Abrir carpeta de salidas" + }, + "Comfy-Desktop_OpenDevTools": { + "label": "Abrir herramientas de desarrollo" + }, + "Comfy-Desktop_OpenUserGuide": { + "label": "Guía de usuario de escritorio" + }, + "Comfy-Desktop_Quit": { + "label": "Salir" + }, + "Comfy-Desktop_Reinstall": { + "label": "Reinstalar" + }, + "Comfy-Desktop_Restart": { + "label": "Reiniciar" + }, "Comfy_3DViewer_Open3DViewer": { "label": "Abrir visor 3D (Beta) para el nodo seleccionado" }, @@ -155,18 +191,30 @@ "Comfy_Manager_ShowUpdateAvailablePacks": { "label": "Buscar actualizaciones" }, - "Comfy_Manager_ToggleManagerProgressDialog": { - "label": "Alternar diálogo de progreso del administrador" - }, "Comfy_MaskEditor_BrushSize_Decrease": { "label": "Disminuir tamaño del pincel en MaskEditor" }, "Comfy_MaskEditor_BrushSize_Increase": { "label": "Aumentar tamaño del pincel en MaskEditor" }, + "Comfy_MaskEditor_ColorPicker": { + "label": "Abrir selector de color en MaskEditor" + }, + "Comfy_MaskEditor_Mirror_Horizontal": { + "label": "Espejar horizontalmente en MaskEditor" + }, + "Comfy_MaskEditor_Mirror_Vertical": { + "label": "Espejar verticalmente en MaskEditor" + }, "Comfy_MaskEditor_OpenMaskEditor": { "label": "Abrir editor de máscara para el nodo seleccionado" }, + "Comfy_MaskEditor_Rotate_Left": { + "label": "Girar a la izquierda en MaskEditor" + }, + "Comfy_MaskEditor_Rotate_Right": { + "label": "Girar a la derecha en MaskEditor" + }, "Comfy_Memory_UnloadModels": { "label": "Descargar modelos" }, @@ -197,12 +245,18 @@ "Comfy_QueueSelectedOutputNodes": { "label": "Encolar nodos de salida seleccionados" }, + "Comfy_Queue_ToggleOverlay": { + "label": "Alternar historial de trabajos" + }, "Comfy_Redo": { "label": "Rehacer" }, "Comfy_RefreshNodeDefinitions": { "label": "Actualizar Definiciones de Nodo" }, + "Comfy_RenameWorkflow": { + "label": "Renombrar flujo de trabajo" + }, "Comfy_SaveWorkflow": { "label": "Guardar Flujo de Trabajo" }, @@ -221,6 +275,12 @@ "Comfy_ToggleHelpCenter": { "label": "Centro de ayuda" }, + "Comfy_ToggleLinear": { + "label": "alternar modo lineal" + }, + "Comfy_ToggleQPOV2": { + "label": "Alternar Panel de Cola V2" + }, "Comfy_ToggleTheme": { "label": "Cambiar Tema (Oscuro/Claro)" }, diff --git a/src/locales/es/main.json b/src/locales/es/main.json index 4a831e93a..2a0d6d09e 100644 --- a/src/locales/es/main.json +++ b/src/locales/es/main.json @@ -1,6 +1,8 @@ { "actionbar": { - "dockToTop": "Acoplar en la parte superior" + "dockToTop": "Acoplar en la parte superior", + "feedback": "Comentarios", + "feedbackTooltip": "Comentarios" }, "apiNodesCostBreakdown": { "costPerRun": "Costo por ejecución", @@ -18,23 +20,141 @@ "assetCard": "Recurso {name} - {type}", "loadingAsset": "Cargando recurso" }, + "assetCollection": "Colección de activos", "assets": "Recursos", "baseModels": "Modelos base", "browseAssets": "Explorar recursos", + "byType": "Por tipo", + "checkpoints": "Checkpoints", + "civitaiLinkExample": "{example} {link}", + "civitaiLinkExampleStrong": "Ejemplo:", + "civitaiLinkExampleUrl": "https://civitai.com/models/10706/luisap-z-image-and-qwen-pixel-art-refiner?modelVersionId=2225295", + "civitaiLinkLabel": "Enlace de {download} del modelo de Civitai", + "civitaiLinkLabelDownload": "descarga", + "civitaiLinkPlaceholder": "Pega el enlace aquí", + "confirmModelDetails": "Confirmar detalles del modelo", "connectionError": "Por favor, revisa tu conexión e inténtalo de nuevo", + "deletion": { + "body": "Este modelo se eliminará permanentemente de tu biblioteca.", + "complete": "{assetName} ha sido eliminado.", + "failed": "No se pudo eliminar {assetName}.", + "header": "¿Eliminar este modelo?", + "inProgress": "Eliminando {assetName}..." + }, + "download": { + "complete": "Descarga completa", + "failed": "La descarga falló", + "inProgress": "Descargando {assetName}..." + }, + "emptyImported": { + "canImport": "Aún no hay modelos importados. Haz clic en \"Importar modelo\" para añadir el tuyo.", + "restricted": "Los modelos personales solo están disponibles en el nivel Creador o superior." + }, + "errorFileTooLarge": "El archivo excede el tamaño máximo permitido", + "errorFormatNotAllowed": "Solo se permite el formato SafeTensor", + "errorModelTypeNotSupported": "Este tipo de modelo no es compatible", + "errorUnknown": "Ocurrió un error inesperado", + "errorUnsafePickleScan": "CivitAI detectó código potencialmente inseguro en este archivo", + "errorUnsafeVirusScan": "CivitAI detectó malware o contenido sospechoso en este archivo", + "errorUploadFailed": "No se pudo importar el activo. Por favor, inténtalo de nuevo.", "failedToCreateNode": "No se pudo crear el nodo. Inténtalo de nuevo o revisa la consola para más detalles.", "fileFormats": "Formatos de archivo", + "fileName": "Nombre del archivo", + "fileSize": "Tamaño del archivo", + "filterBy": "Filtrar por", + "findInLibrary": "Encuéntralo en la sección {type} de la biblioteca de modelos.", + "finish": "Finalizar", + "genericLinkPlaceholder": "Pega el enlace aquí", + "importAnother": "Importar otro", + "imported": "Importado", + "jobId": "ID de tarea", "loadingModels": "Cargando {type}...", + "maxFileSize": "Tamaño máximo de archivo: {size}", + "maxFileSizeValue": "1 GB", + "media": { + "audioPlaceholder": "Audio", + "threeDModelPlaceholder": "Modelo 3D" + }, + "modelAssociatedWithLink": "El modelo asociado con el enlace que proporcionaste:", + "modelInfo": { + "addBaseModel": "Agregar modelo base...", + "addTag": "Agregar etiqueta...", + "additionalTags": "Etiquetas adicionales", + "baseModelUnknown": "Modelo base desconocido", + "basicInfo": "Información básica", + "compatibleBaseModels": "Modelos base compatibles", + "description": "Descripción", + "descriptionNotSet": "Sin descripción", + "descriptionPlaceholder": "Agrega una descripción para este modelo...", + "displayName": "Nombre para mostrar", + "editDisplayName": "Editar nombre para mostrar", + "fileName": "Nombre de archivo", + "modelDescription": "Descripción del modelo", + "modelTagging": "Etiquetado del modelo", + "modelType": "Tipo de modelo", + "noAdditionalTags": "Sin etiquetas adicionales", + "selectModelPrompt": "Selecciona un modelo para ver su información", + "selectModelType": "Selecciona el tipo de modelo...", + "source": "Fuente", + "title": "Información del modelo", + "triggerPhrases": "Frases de activación", + "viewOnSource": "Ver en {source}" + }, + "modelName": "Nombre del modelo", + "modelNamePlaceholder": "Introduce un nombre para este modelo", + "modelTypeSelectorLabel": "¿Qué tipo de modelo es este?", + "modelTypeSelectorPlaceholder": "Selecciona el tipo de modelo", + "modelUploaded": "Modelo importado correctamente.", "noAssetsFound": "No se encontraron recursos", "noModelsInFolder": "No hay {type} disponibles en esta carpeta", - "searchAssetsPlaceholder": "Buscar recursos...", + "noValidSourceDetected": "No se detectó una fuente de importación válida", + "notSureLeaveAsIs": "¿No estás seguro? Déjalo como está", + "onlyCivitaiUrlsSupported": "Solo se admiten URLs de Civitai", + "ownership": "Propiedad", + "ownershipAll": "Todos", + "ownershipMyModels": "Mis modelos", + "ownershipPublicModels": "Modelos públicos", + "processingModel": "Descarga iniciada", + "processingModelDescription": "Puedes cerrar este diálogo. La descarga continuará en segundo plano.", + "providerCivitai": "Civitai", + "providerHuggingFace": "Hugging Face", + "rename": { + "failed": "No se pudo renombrar el activo." + }, + "selectFrameworks": "Seleccionar frameworks", + "selectModelType": "Seleccionar tipo de modelo", + "selectProjects": "Seleccionar proyectos", "sortAZ": "A-Z", "sortBy": "Ordenar por", "sortPopular": "Popular", "sortRecent": "Reciente", "sortZA": "Z-A", + "sortingType": "Tipo de ordenación", + "tags": "Etiquetas", + "tagsHelp": "Separa las etiquetas con comas", + "tagsPlaceholder": "ej., modelos, checkpoint", "tryAdjustingFilters": "Intenta ajustar tu búsqueda o filtros", - "unknown": "Desconocido" + "unknown": "Desconocido", + "unsupportedUrlSource": "Solo se admiten URLs de {sources}", + "upgradeFeatureDescription": "Esta función solo está disponible con los planes Creator o Pro.", + "upgradeToUnlockFeature": "Actualiza para desbloquear esta función", + "upload": "Importar", + "uploadFailed": "La importación falló", + "uploadModel": "Importar", + "uploadModelDescription1": "Pega un enlace de descarga de modelo de Civitai para añadirlo a tu biblioteca.", + "uploadModelDescription1Generic": "Pega un enlace de descarga de modelo para añadirlo a tu biblioteca.", + "uploadModelDescription2": "Solo se admiten enlaces de {link} por el momento", + "uploadModelDescription2Generic": "Solo se admiten URLs de los siguientes proveedores:", + "uploadModelDescription2Link": "https://civitai.com/models", + "uploadModelDescription3": "Tamaño máximo de archivo: {size}", + "uploadModelFailedToRetrieveMetadata": "No se pudo obtener la metadata. Por favor, revisa el enlace e inténtalo de nuevo.", + "uploadModelFromCivitai": "Importar un modelo de Civitai", + "uploadModelGeneric": "Importar un modelo", + "uploadModelHelpFooterText": "¿Necesitas ayuda para encontrar las URLs? Haz clic en un proveedor abajo para ver un video tutorial.", + "uploadModelHelpVideo": "Video de ayuda para importar modelos", + "uploadModelHowDoIFindThis": "¿Cómo encuentro esto?", + "uploadSuccess": "¡Modelo importado con éxito!", + "uploadingModel": "Importando modelo..." }, "auth": { "apiKey": { @@ -148,12 +268,19 @@ "title": "Crea una cuenta" } }, + "boundingBox": { + "height": "Alto", + "width": "Ancho", + "x": "X", + "y": "Y" + }, "breadcrumbsMenu": { "clearWorkflow": "Limpiar flujo de trabajo", "deleteBlueprint": "Eliminar Plano", "deleteWorkflow": "Eliminar flujo de trabajo", "duplicate": "Duplicar", - "enterNewName": "Ingrese un nuevo nombre" + "enterNewName": "Ingrese un nuevo nombre", + "missingNodesWarning": "El flujo de trabajo contiene nodos no compatibles (resaltados en rojo)." }, "clipboard": { "errorMessage": "Error al copiar al portapapeles", @@ -207,6 +334,7 @@ }, "retry": "Intentar de nuevo", "retrying": "Reintentando...", + "skipToCloudApp": "Saltar a la aplicación en la nube", "start": { "desc": "Cero configuración requerida. Funciona en cualquier dispositivo.", "download": "Descargar ComfyUI", @@ -340,6 +468,8 @@ "Edit Subgraph Widgets": "Editar Widgets de Subgrafo", "Expand": "Expandir", "Expand Node": "Expandir Nodo", + "Extensions": "Extensiones", + "FavoriteWidget": "Marcar widget como favorito", "Horizontal": "Horizontal", "Inputs": "Entradas", "Left": "Izquierda", @@ -359,6 +489,7 @@ "Remove": "Eliminar", "Remove Bypass": "Eliminar Bypass", "Rename": "Renombrar", + "RenameWidget": "Renombrar widget", "Resize": "Redimensionar", "Right": "Derecha", "Run Branch": "Ejecutar Rama", @@ -369,6 +500,7 @@ "Shapes": "Formas", "Title": "Título", "Top": "Arriba", + "UnfavoriteWidget": "Quitar widget de favoritos", "Unpack Subgraph": "Desempaquetar Subgrafo", "Unpin": "Desanclar", "Vertical": "Vertical", @@ -382,6 +514,7 @@ "additionalInfo": "Información adicional", "apiPricing": "Precios de la API", "credits": "Créditos", + "creditsAvailable": "Créditos disponibles", "details": "Detalles", "eventType": "Tipo de evento", "faqs": "Preguntas frecuentes", @@ -390,15 +523,46 @@ "messageSupport": "Contactar soporte", "model": "Modelo", "purchaseCredits": "Comprar créditos", + "refreshes": "Se actualiza el {date}", "time": "Hora", "topUp": { + "addMoreCredits": "Agregar más créditos", + "addMoreCreditsToRun": "Agrega más créditos para ejecutar", + "amountToPayLabel": "Cantidad a pagar en dólares", + "buy": "Comprar", + "buyCredits": "Continuar al pago", "buyNow": "Comprar ahora", + "contactUs": "Contáctanos", + "creditsDescription": "Los créditos se utilizan para ejecutar flujos de trabajo o nodos de socios.", + "creditsPerDollar": "créditos por dólar", + "creditsToReceiveLabel": "Créditos a recibir", + "howManyCredits": "¿Cuántos créditos te gustaría agregar?", "insufficientMessage": "No tienes suficientes créditos para ejecutar este flujo de trabajo.", "insufficientTitle": "Créditos insuficientes", + "insufficientWorkflowMessage": "No tienes suficientes créditos para ejecutar este flujo de trabajo.", + "maxAllowed": "Máximo {credits} créditos.", "maxAmount": "(Máx. $1,000 USD)", + "maximumAmount": "Máximo ${amount}.", + "minRequired": "Mínimo {credits} créditos", + "minimumPurchase": "Mínimo ${amount} ({credits} créditos)", + "needMore": "¿Necesitas más?", + "purchaseError": "Compra fallida", + "purchaseErrorDetail": "No se pudo comprar créditos: {error}", "quickPurchase": "Compra rápida", "seeDetails": "Ver detalles", - "topUp": "Recargar" + "selectAmount": "Seleccionar cantidad", + "templateNote": "*Generado con la plantilla Wan Fun Control", + "topUp": "Recargar", + "unknownError": "Ocurrió un error desconocido", + "usdAmount": "${amount}", + "videosEstimate": "~{count} videos", + "viewPricing": "Ver detalles de precios", + "youGet": "Créditos", + "youPay": "Cantidad (USD)" + }, + "unified": { + "message": "Los créditos han sido unificados", + "tooltip": "Hemos unificado los pagos en Comfy. Ahora todo funciona con Comfy Credits:\n- Nodos de socios (antes nodos API)\n- Flujos de trabajo en la nube\n\nTu saldo existente de nodos de socios se ha convertido en créditos." }, "yourCreditBalance": "Tu saldo de créditos" }, @@ -414,6 +578,9 @@ "CLIP_VISION": "CLIP_VISION", "CLIP_VISION_OUTPUT": "SALIDA_CLIP_VISION", "COMBO": "COMBO", + "COMFY_AUTOGROW_V3": "COMFY_AUTOGROW_V3", + "COMFY_DYNAMICCOMBO_V3": "COMFY_DYNAMICCOMBO_V3", + "COMFY_MATCHTYPE_V3": "COMFY_MATCHTYPE_V3", "CONDITIONING": "ACONDICIONAMIENTO", "CONTROL_NET": "RED_DE_CONTROL", "FLOAT": "FLOTANTE", @@ -424,18 +591,21 @@ "HOOKS": "GANCHOS", "HOOK_KEYFRAMES": "GANCHO_FOTOGRAMAS_CLAVE", "IMAGE": "IMAGEN", + "IMAGECOMPARE": "COMPARARIMÁGENES", "INT": "ENTERO", "LATENT": "LATENTE", "LATENT_OPERATION": "OPERACIÓN_LATENTE", + "LATENT_UPSCALE_MODEL": "LATENT_UPSCALE_MODEL", "LOAD3D_CAMERA": "CARGAR CÁMARA 3D", "LOAD_3D": "CARGAR_3D", - "LOAD_3D_ANIMATION": "CARGAR_ANIMACIÓN_3D", "LORA_MODEL": "MODELO_LORA", "LOSS_MAP": "MAPA_PÉRDIDAS", "LUMA_CONCEPTS": "CONCEPTOS LUMA", "LUMA_REF": "REFERENCIA LUMA", "MASK": "MASK", "MESH": "MALLA", + "MESHY_RIGGED_TASK_ID": "MESHY_RIGGED_TASK_ID", + "MESHY_TASK_ID": "MESHY_TASK_ID", "MODEL": "MODELO", "MODEL_PATCH": "PARCHE_MODELO", "MODEL_TASK_ID": "ID_TAREA_MODELO", @@ -455,6 +625,7 @@ "STYLE_MODEL": "MODELO_DE_ESTILO", "SVG": "SVG", "TIMESTEPS_RANGE": "RANGO_DE_PASOS_DE_TIEMPO", + "TRACKS": "PISTAS", "UPSCALE_MODEL": "MODELO_DE_ESCALADO", "VAE": "VAE", "VIDEO": "VÍDEO", @@ -523,14 +694,17 @@ "amount": "Cantidad", "apply": "Aplicar", "architecture": "Arquitectura", + "asset": "{count} recursos | {count} recurso | {count} recursos", "audioFailedToLoad": "No se pudo cargar el audio", "audioProgress": "Progreso de audio", "author": "Autor", "back": "Atrás", + "batchRename": "Renombrar en lote", "beta": "BETA", "bookmark": "Guardar en Biblioteca", "calculatingDimensions": "Calculando dimensiones", "cancel": "Cancelar", + "cancelled": "Cancelado", "capture": "captura", "category": "Categoría", "chart": "Gráfico", @@ -540,6 +714,7 @@ "clearAll": "Borrar todo", "clearFilters": "Borrar filtros", "close": "Cerrar", + "closeDialog": "Cerrar diálogo", "color": "Color", "comfy": "Comfy", "comfyOrgLogoAlt": "Logo de ComfyOrg", @@ -556,13 +731,17 @@ "control_before_generate": "control antes de generar", "copied": "Copiado", "copy": "Copiar", + "copyAll": "Copiar todo", "copyJobId": "Copiar ID de trabajo", "copyToClipboard": "Copiar al portapapeles", "copyURL": "Copiar URL", + "core": "Núcleo", "currentUser": "Usuario actual", + "custom": "Personalizado", "customBackground": "Fondo personalizado", "customize": "Personalizar", "customizeFolder": "Personalizar carpeta", + "decrement": "Disminuir", "defaultBanner": "banner predeterminado", "delete": "Eliminar", "deleteAudioFile": "Eliminar archivo de audio", @@ -571,27 +750,35 @@ "description": "Descripción", "devices": "Dispositivos", "disableAll": "Deshabilitar todo", + "disableSelected": "Deshabilitar seleccionados", + "disableThirdParty": "Deshabilitar terceros", "disabling": "Deshabilitando", "dismiss": "Descartar", "download": "Descargar", "downloadImage": "Descargar imagen", "downloadVideo": "Descargar video", + "downloading": "Descargando", "dropYourFileOr": "Suelta tu archivo o", "duplicate": "Duplicar", "edit": "Editar", "editImage": "Editar imagen", "editOrMaskImage": "Editar o enmascarar imagen", + "emDash": "—", "empty": "Vacío", "enableAll": "Habilitar todo", "enableOrDisablePack": "Activar o desactivar paquete", + "enableSelected": "Habilitar seleccionados", "enabled": "Habilitado", "enabling": "Habilitando", + "enterBaseName": "Introduce el nombre base", + "enterNewName": "Introduce el nuevo nombre", "error": "Error", "errorLoadingImage": "Error al cargar imagen", "errorLoadingVideo": "Error al cargar video", "experimental": "BETA", "export": "Exportar", "extensionName": "Nombre de la extensión", + "failed": "Fallido", "failedToCopyJobId": "Error al copiar el ID de trabajo", "failedToDownloadImage": "Falló la descarga de imagen", "failedToDownloadVideo": "Falló la descarga de video", @@ -607,12 +794,15 @@ "goToNode": "Ir al nodo", "graphNavigation": "Navegación de gráficos", "halfSpeed": "0.5x", + "hideLeftPanel": "Ocultar panel izquierdo", + "hideRightPanel": "Ocultar panel derecho", "icon": "Icono", "imageFailedToLoad": "Falló la carga de la imagen", "imagePreview": "Vista previa de imagen - Usa las teclas de flecha para navegar entre imágenes", "imageUrl": "URL de la imagen", "import": "Importar", "inProgress": "En progreso", + "increment": "Incrementar", "info": "Información del Nodo", "insert": "Insertar", "install": "Instalar", @@ -620,7 +810,9 @@ "installing": "Instalando", "interrupted": "Interrumpido", "itemSelected": "{selectedCount} elemento seleccionado", + "itemsCopiedToClipboard": "Elementos copiados al portapapeles", "itemsSelected": "{selectedCount} elementos seleccionados", + "job": "Tarea", "jobIdCopied": "ID de trabajo copiado al portapapeles", "keybinding": "Combinación de teclas", "keybindingAlreadyExists": "La combinación de teclas ya existe en", @@ -638,14 +830,18 @@ "micPermissionDenied": "Permiso de micrófono denegado", "migrate": "Migrar", "missing": "Faltante", + "more": "Más", "moreOptions": "Más Opciones", "moreWorkflows": "Más flujos de trabajo", "multiSelectDropdown": "Menú desplegable de selección múltiple", "name": "Nombre", "newFolder": "Nueva carpeta", "next": "Siguiente", + "nightly": "NIGHTLY", "no": "No", "noAudioRecorded": "No se grabó audio", + "noItems": "Sin elementos", + "noResults": "Sin resultados", "noResultsFound": "No se encontraron resultados", "noTasksFound": "No se encontraron tareas", "noTasksFoundMessage": "No hay tareas en la cola.", @@ -656,26 +852,45 @@ "nodeSlotsError": "Error de Ranuras del Nodo", "nodeWidgetsError": "Error de Widgets del Nodo", "nodes": "Nodos", + "nodesCount": "{count} nodos | {count} nodo | {count} nodos", "nodesRunning": "nodos en ejecución", "none": "Ninguno", + "nothingToCopy": "Nada para copiar", + "nothingToDelete": "Nada para eliminar", + "nothingToDuplicate": "Nada para duplicar", + "nothingToRename": "Nada para renombrar", "ok": "OK", "openManager": "Abrir administrador", "openNewIssue": "Abrir nuevo problema", + "or": "o", "overwrite": "Sobrescribir", + "playPause": "Reproducir/Pausar", "playRecording": "Reproducir grabación", "playbackSpeed": "Velocidad de reproducción", "playing": "Reproduciendo", "pressKeysForNewBinding": "Presiona teclas para nueva asignación", "preview": "VISTA PREVIA", + "profile": "Perfil", "progressCountOf": "de", + "queued": "En cola", "ready": "Listo", "reconnected": "Reconectado", "reconnecting": "Reconectando", "refresh": "Actualizar", "refreshNode": "Actualizar Nodo", + "relativeTime": { + "daysAgo": "hace {count}d", + "hoursAgo": "hace {count}h", + "minutesAgo": "hace {count}min", + "monthsAgo": "hace {count}m", + "now": "ahora", + "weeksAgo": "hace {count}s", + "yearsAgo": "hace {count}a" + }, "releaseTitle": "Lanzamiento de {package} {version}", "reloadToApplyChanges": "Recargar para aplicar cambios", "removeImage": "Eliminar imagen", + "removeTag": "Eliminar etiqueta", "removeVideo": "Eliminar video", "rename": "Renombrar", "reportIssue": "Enviar informe", @@ -690,21 +905,31 @@ "resizeFromTopRight": "Redimensionar desde la esquina superior derecha", "restart": "Reiniciar", "resultsCount": "Encontrados {count} resultados", + "running": "En ejecución", "save": "Guardar", "saving": "Guardando", + "scrollLeft": "Desplazar a la izquierda", + "scrollRight": "Desplazar a la derecha", "search": "Buscar", "searchExtensions": "Buscar extensiones", "searchFailedMessage": "No pudimos encontrar ninguna configuración que coincida con tu búsqueda. Intenta ajustar tus términos de búsqueda.", "searchKeybindings": "Buscar combinaciones de teclas", "searchModels": "Buscar modelos", "searchNodes": "Buscar nodos", + "searchPlaceholder": "Buscar...", "searchSettings": "Buscar configuraciones", "searchWorkflows": "Buscar flujos de trabajo", "seeTutorial": "Ver un tutorial", + "selectItemsToCopy": "Selecciona elementos para copiar", + "selectItemsToDelete": "Selecciona elementos para eliminar", + "selectItemsToDuplicate": "Selecciona elementos para duplicar", + "selectItemsToRename": "Selecciona elementos para renombrar", "selectedFile": "Archivo seleccionado", "setAsBackground": "Establecer como fondo", "settings": "Configuraciones", + "showLeftPanel": "Mostrar panel izquierdo", "showReport": "Mostrar informe", + "showRightPanel": "Mostrar panel derecho", "singleSelectDropdown": "Menú desplegable de selección única", "sort": "Ordenar", "source": "Fuente", @@ -712,12 +937,14 @@ "status": "Estado", "stopPlayback": "Detener reproducción", "stopRecording": "Detener grabación", + "submit": "Enviar", "success": "Éxito", "systemInfo": "Información del sistema", "terminal": "Terminal", "title": "Título", "triggerPhrase": "Frase de activación", "unknownError": "Error desconocido", + "untitled": "Sin título", "update": "Actualizar", "updateAvailable": "Actualización Disponible", "updateFrontend": "Actualizar frontend", @@ -725,6 +952,7 @@ "updating": "Actualizando", "upload": "Subir", "usageHint": "Sugerencia de uso", + "use": "Usar", "user": "Usuario", "versionMismatchWarning": "Advertencia de compatibilidad de versión", "versionMismatchWarningMessage": "{warning}: {detail} Visita https://docs.comfy.org/installation/update_comfyui#common-update-issues para obtener instrucciones de actualización.", @@ -732,11 +960,10 @@ "videoPreview": "Vista previa de video - Usa las teclas de flecha para navegar entre videos", "viewImageOfTotal": "Ver imagen {index} de {total}", "viewVideoOfTotal": "Ver video {index} de {total}", - "vitePreloadErrorMessage": "Se ha lanzado una nueva versión de la aplicación. ¿Deseas recargar?\nSi no lo haces, algunas partes de la aplicación podrían no funcionar correctamente.\nPuedes rechazar y guardar tu progreso antes de recargar.", - "vitePreloadErrorTitle": "Nueva versión disponible", "volume": "Volumen", "warning": "Advertencia", - "workflow": "Flujo de trabajo" + "workflow": "Flujo de trabajo", + "you": "Tú" }, "graphCanvasMenu": { "fitView": "Ajustar vista", @@ -758,12 +985,17 @@ "create": "Crear nodo de grupo", "enterName": "Introduzca el nombre" }, + "help": { + "helpCenterMenu": "Menú del Centro de Ayuda", + "recentReleases": "Lanzamientos recientes" + }, "helpCenter": { "clickToLearnMore": "Haz clic para saber más →", "desktopUserGuide": "Guía de usuario de escritorio", "docs": "Documentación", + "feedback": "Enviar comentarios", "github": "Github", - "helpFeedback": "Ayuda y comentarios", + "help": "Ayuda y soporte", "loadingReleases": "Cargando versiones...", "managerExtension": "Extensión del Administrador", "more": "Más...", @@ -772,6 +1004,12 @@ "recentReleases": "Lanzamientos recientes", "reinstall": "Reinstalar", "updateAvailable": "Actualizar", + "updateComfyUI": "Actualizar ComfyUI", + "updateComfyUIFailed": "No se pudo actualizar ComfyUI. Por favor, inténtelo de nuevo.", + "updateComfyUIStarted": "Actualización iniciada", + "updateComfyUIStartedDetail": "La actualización de ComfyUI ha sido puesta en cola. Por favor, espere...", + "updateComfyUISuccess": "Actualización completa", + "updateComfyUISuccessDetail": "ComfyUI ha sido actualizado. Reiniciando...", "whatsNew": "¿Qué hay de nuevo?" }, "icon": { @@ -785,6 +1023,18 @@ "inbox": "Bandeja de entrada", "star": "Estrella" }, + "imageCompare": { + "noImages": "No hay imágenes para comparar" + }, + "imageCrop": { + "cropPreviewAlt": "Vista previa del recorte", + "loading": "Cargando...", + "noInputImage": "No hay imagen de entrada conectada" + }, + "importFailed": { + "copyError": "Error al copiar", + "title": "Error de importación" + }, "install": { "appDataLocationTooltip": "Directorio de datos de la aplicación ComfyUI. Almacena:\n- Registros\n- Configuraciones del servidor", "appPathLocationTooltip": "Directorio de activos de la aplicación ComfyUI. Almacena el código y los activos de ComfyUI", @@ -798,6 +1048,8 @@ "failedToSelectDirectory": "Falló al seleccionar el directorio", "gpu": "GPU", "gpuPicker": { + "amdDescription": "Utiliza tu GPU AMD con aceleración ROCm™ para el mejor rendimiento.", + "amdSubtitle": "AMD ROCm™", "appleMetalDescription": "Aprovecha la GPU de tu Mac para mayor velocidad y una mejor experiencia general", "cpuDescription": "Usa el modo CPU para compatibilidad cuando la aceleración GPU no esté disponible", "cpuSubtitle": "Modo CPU", @@ -824,6 +1076,8 @@ "selectGpuDescription": "Selecciona el tipo de GPU que tienes" }, "helpImprove": "Por favor ayuda a mejorar ComfyUI", + "insideAppInstallDir": "Esta carpeta está dentro del paquete de la aplicación ComfyUI Desktop y se eliminará durante las actualizaciones. Elige un directorio fuera de la carpeta de instalación, como Documentos/ComfyUI.", + "insideUpdaterCache": "Esta carpeta está dentro de la caché de actualización de ComfyUI, que se borra en cada actualización. Selecciona una ubicación diferente para tus datos.", "installLocation": "Ubicación de Instalación", "installLocationDescription": "Selecciona el directorio para los datos de usuario de ComfyUI. Un entorno de python será instalado en la ubicación seleccionada.", "installLocationTooltip": "Directorio de datos de usuario de ComfyUI. Almacena:\n- Entorno Python\n- Modelos\n- Nodos personalizados\n", @@ -898,6 +1152,16 @@ "issueReport": { "helpFix": "Ayuda a Solucionar Esto" }, + "linearMode": { + "beta": "Beta - Enviar comentarios", + "downloadAll": "Descargar todo", + "dragAndDropImage": "Arrastra y suelta una imagen", + "graphMode": "Modo gráfico", + "linearMode": "Modo simple", + "rerun": "Volver a ejecutar", + "reuseParameters": "Reutilizar parámetros", + "runCount": "Número de ejecuciones:" + }, "load3d": { "applyingTexture": "Aplicando textura...", "backgroundColor": "Color de fondo", @@ -924,20 +1188,24 @@ "lineart": "Arte lineal", "normal": "Normal", "original": "Original", + "pointCloud": "Nube de puntos", "wireframe": "Malla" }, "model": "Modelo", "openIn3DViewer": "Abrir en Visor 3D", + "panoramaMode": "Panorama", "previewOutput": "Vista previa de salida", "reloadingModel": "Recargando modelo...", "removeBackgroundImage": "Eliminar imagen de fondo", "resizeNodeMatchOutput": "Redimensionar nodo para coincidir con la salida", "scene": "Escena", "showGrid": "Mostrar cuadrícula", + "showSkeleton": "Mostrar esqueleto", "startRecording": "Iniciar grabación", "stopRecording": "Detener grabación", "switchCamera": "Cambiar cámara", "switchingMaterialMode": "Cambiando modo de material...", + "tiledMode": "Mosaico", "unsupportedFileType": "Tipo de archivo no compatible (admite .gltf, .glb, .obj, .fbx, .stl)", "upDirection": "Dirección hacia arriba", "upDirections": { @@ -958,6 +1226,11 @@ "title": "Visor 3D (Beta)" } }, + "loadWorkflowWarning": { + "coreNodesFromVersion": "Nodos principales de la versión {version}:", + "outdatedVersion": "Este flujo de trabajo fue creado con una versión más reciente de ComfyUI ({version}). Es posible que algunos nodos no funcionen correctamente.", + "outdatedVersionGeneric": "Este flujo de trabajo fue creado con una versión más reciente de ComfyUI. Es posible que algunos nodos no funcionen correctamente." + }, "maintenance": { "None": "Ninguno", "OK": "OK", @@ -976,7 +1249,15 @@ "showManual": "Mostrar tareas de mantenimiento", "status": "Estado", "terminalDefaultMessage": "Cuando ejecutes un comando de solución de problemas, cualquier salida se mostrará aquí.", - "title": "Mantenimiento" + "title": "Mantenimiento", + "unsafeMigration": { + "action": "Utilice la tarea de mantenimiento \"Ruta base\" a continuación para mover ComfyUI a una ubicación segura.", + "appInstallDir": "Su ruta base está dentro del paquete de la aplicación ComfyUI Desktop. Esta carpeta puede ser eliminada o sobrescrita durante las actualizaciones. Elija un directorio fuera de la carpeta de instalación, como Documentos/ComfyUI.", + "generic": "La ruta base actual de ComfyUI está en una ubicación que puede ser eliminada o modificada durante las actualizaciones. Para evitar la pérdida de datos, muévala a una carpeta segura.", + "oneDrive": "Su ruta base está en OneDrive, lo que puede causar problemas de sincronización y pérdida accidental de datos. Elija una carpeta local que no esté gestionada por OneDrive.", + "title": "Ubicación de instalación no segura detectada", + "updaterCache": "Su ruta base está dentro de la caché del actualizador de ComfyUI, que se borra en cada actualización. Elija una ubicación diferente para sus datos." + } }, "manager": { "allMissingNodesInstalled": "Todos los nodos faltantes se han instalado exitosamente", @@ -1077,6 +1358,8 @@ "totalNodes": "Total de Nodos", "tryAgainLater": "Por favor intenta de nuevo más tarde.", "tryDifferentSearch": "Por favor intenta con una consulta de búsqueda diferente.", + "tryUpdate": "Intentar actualizar", + "tryUpdateTooltip": "Obtén los últimos cambios del repositorio. Las versiones nocturnas pueden tener actualizaciones que no se detectan automáticamente.", "uninstall": "Desinstalar", "uninstallSelected": "Desinstalar Seleccionado", "uninstalling": "Desinstalando", @@ -1087,31 +1370,110 @@ "version": "Versión" }, "maskEditor": { + "activateLayer": "Activar capa", + "applyToWholeImage": "Aplicar a toda la imagen", + "baseImageLayer": "Capa de imagen base", + "baseLayerPreview": "Vista previa de la capa base", + "black": "Negro", + "brushSettings": "Configuración del pincel", + "brushShape": "Forma del pincel", + "clear": "Limpiar", + "clickToResetZoom": "Haz clic para restablecer el zoom", + "colorSelectSettings": "Configuración de selección de color", + "colorSelector": "Selector de color", + "fillOpacity": "Opacidad de relleno", + "hardness": "Dureza", + "imageLayer": "Capa de imagen", + "invert": "Invertir", + "layers": "Capas", + "livePreview": "Vista previa en vivo", + "maskBlendingOptions": "Opciones de fusión de máscara", + "maskLayer": "Capa de máscara", + "maskOpacity": "Opacidad de máscara", + "maskTolerance": "Tolerancia de máscara", + "method": "Método", + "mirrorHorizontal": "Espejar horizontalmente", + "mirrorVertical": "Espejar verticalmente", + "negative": "Negativo", + "opacity": "Opacidad", + "paintBucketSettings": "Configuración del bote de pintura", + "paintLayer": "Capa de pintura", + "redo": "Rehacer", + "resetToDefault": "Restablecer a valores predeterminados", + "rotateLeft": "Girar a la izquierda", + "rotateRight": "Girar a la derecha", + "selectionOpacity": "Opacidad de selección", + "smoothingPrecision": "Precisión de suavizado", + "stepSize": "Tamaño del paso", + "stopAtMask": "Detener en la máscara", + "thickness": "Grosor", + "title": "Editor de máscara", + "tolerance": "Tolerancia", + "undo": "Deshacer", + "white": "Blanco" }, "mediaAsset": { + "actions": { + "copyJobId": "Copiar ID de trabajo", + "delete": "Eliminar", + "download": "Descargar", + "exportWorkflow": "Exportar flujo de trabajo", + "insertAsNodeInWorkflow": "Insertar como nodo en el flujo de trabajo", + "inspect": "Inspeccionar recurso", + "more": "Más opciones", + "moreOptions": "Más opciones", + "openWorkflow": "Abrir como flujo de trabajo en una nueva pestaña", + "seeMoreOutputs": "Ver más resultados", + "zoom": "Acercar" + }, "assetDeletedSuccessfully": "Recurso eliminado exitosamente", "deleteAssetDescription": "Este recurso será eliminado permanentemente.", "deleteAssetTitle": "¿Eliminar este recurso?", "deleteSelectedDescription": "{count} recurso(s) será(n) eliminado(s) permanentemente.", "deleteSelectedTitle": "¿Eliminar los recursos seleccionados?", "deletingImportedFilesCloudOnly": "La eliminación de archivos importados solo es compatible en la versión cloud", + "failedToCreateNode": "No se pudo crear el nodo", "failedToDeleteAsset": "Error al eliminar el recurso", + "failedToExportWorkflow": "No se pudo exportar el flujo de trabajo", "jobIdToast": { "copied": "Copiado", "error": "Error", "jobIdCopied": "ID de trabajo copiado al portapapeles", "jobIdCopyFailed": "Error al copiar el ID de trabajo" }, + "noJobIdFound": "No se encontró ID de trabajo para este recurso", + "noWorkflowDataFound": "No se encontraron datos de flujo de trabajo en este recurso", + "nodeAddedToWorkflow": "Nodo {nodeType} agregado al flujo de trabajo", + "nodeTypeNotFound": "Tipo de nodo {nodeType} no encontrado", "selection": { "assetsDeletedSuccessfully": "{count} recurso(s) eliminado(s) exitosamente", "deleteSelected": "Eliminar", + "deleteSelectedAll": "Eliminar todo", "deselectAll": "Deseleccionar todo", "downloadSelected": "Descargar", + "downloadSelectedAll": "Descargar todo", "downloadStarted": "Descargando {count} archivos...", "downloadsStarted": "Se inició la descarga de {count} archivo(s)", + "exportWorkflowAll": "Exportar todos los flujos de trabajo", + "failedToAddNodes": "No se pudieron añadir nodos al flujo de trabajo", "failedToDeleteAssets": "Error al eliminar los recursos seleccionados", - "selectedCount": "Recursos seleccionados: {count}" - } + "insertAllAssetsAsNodes": "Insertar todos los recursos como nodos", + "multipleSelectedAssets": "Varios recursos seleccionados", + "noWorkflowsFound": "No se encontraron datos de flujo de trabajo en los recursos seleccionados", + "noWorkflowsToExport": "No se encontraron datos de flujo de trabajo para exportar", + "nodesAddedToWorkflow": "{count} nodo(s) añadido(s) al flujo de trabajo", + "openWorkflowAll": "Abrir todos los flujos de trabajo", + "partialAddNodesSuccess": "{succeeded} añadido(s) correctamente, {failed} fallido(s)", + "partialDeleteSuccess": "{succeeded} eliminados correctamente, {failed} fallidos", + "partialWorkflowsExported": "{succeeded} exportado(s) correctamente, {failed} fallido(s)", + "partialWorkflowsOpened": "{succeeded} flujo(s) de trabajo abierto(s), {failed} fallido(s)", + "selectedCount": "Recursos seleccionados: {count}", + "workflowsExported": "{count} flujo(s) de trabajo exportado(s) correctamente", + "workflowsOpened": "{count} flujo(s) de trabajo abierto(s) en nuevas pestañas" + }, + "unsupportedFileType": "Tipo de archivo no compatible para el nodo de carga", + "workflowExportedSuccessfully": "Flujo de trabajo exportado correctamente", + "workflowOpenedInNewTab": "Flujo de trabajo abierto en una nueva pestaña" }, "menu": { "autoQueue": "Cola automática", @@ -1119,11 +1481,13 @@ "batchCountTooltip": "El número de veces que la generación del flujo de trabajo debe ser encolada", "clear": "Limpiar flujo de trabajo", "clipspace": "Abrir Clipspace", + "customNodesManager": "Gestor de nodos personalizados", "dark": "Oscuro", "disabled": "Deshabilitado", "disabledTooltip": "El flujo de trabajo no se encolará automáticamente", "execute": "Ejecutar", "help": "Ayuda", + "helpAndFeedback": "Ayuda y comentarios", "hideMenu": "Ocultar menú", "instant": "Instantáneo", "instantTooltip": "El flujo de trabajo se encolará instantáneamente después de que finalice una generación", @@ -1137,6 +1501,7 @@ "resetView": "Restablecer vista del lienzo", "run": "Ejecutar", "runWorkflow": "Ejecutar flujo de trabajo (Shift para encolar al frente)", + "runWorkflowDisabled": "El flujo de trabajo contiene nodos no compatibles (resaltados en rojo). Elimínelos para ejecutar el flujo de trabajo.", "runWorkflowFront": "Ejecutar flujo de trabajo (Encolar al frente)", "settings": "Configuración", "showMenu": "Mostrar menú", @@ -1152,6 +1517,7 @@ "Canvas Performance": "Rendimiento del Lienzo", "Canvas Toggle Lock": "Alternar bloqueo en lienzo", "Check for Custom Node Updates": "Buscar actualizaciones de nodos personalizados", + "Check for Updates": "Buscar actualizaciones", "Clear Pending Tasks": "Borrar tareas pendientes", "Clear Workflow": "Borrar flujo de trabajo", "Clipspace": "Espacio de clip", @@ -1168,13 +1534,14 @@ "Custom Nodes Manager": "Administrador de Nodos Personalizados", "Decrease Brush Size in MaskEditor": "Disminuir tamaño del pincel en MaskEditor", "Delete Selected Items": "Eliminar elementos seleccionados", + "Desktop User Guide": "Guía de usuario de escritorio", "Duplicate Current Workflow": "Duplicar flujo de trabajo actual", "Edit": "Editar", "Edit Subgraph Widgets": "Editar widgets de subgrafo", "Exit Subgraph": "Salir de Subgrafo", "Experimental: Browse Model Assets": "Experimental: Explorar recursos de modelos", "Experimental: Enable AssetAPI": "Experimental: Habilitar AssetAPI", - "Experimental: Enable Vue Nodes": "Experimental: Habilitar nodos Vue", + "Experimental: Enable Nodes 2_0": "Experimental: Activar Nodes 2.0", "Export": "Exportar", "Export (API)": "Exportar (API)", "File": "Archivo", @@ -1186,12 +1553,15 @@ "Increase Brush Size in MaskEditor": "Aumentar tamaño del pincel en MaskEditor", "Install Missing Custom Nodes": "Instalar nodos personalizados faltantes", "Interrupt": "Interrumpir", + "Job History": "Historial de trabajos", "Load Default Workflow": "Cargar flujo de trabajo predeterminado", "Lock Canvas": "Bloquear Lienzo", "Manage group nodes": "Gestionar nodos de grupo", "Manager": "Administrador", "Manager Menu (Legacy)": "Menú de gestión (heredado)", "Minimap": "Minimapa", + "Mirror Horizontal in MaskEditor": "Espejar horizontalmente en el editor de máscaras", + "Mirror Vertical in MaskEditor": "Espejar verticalmente en el editor de máscaras", "Model Library": "Biblioteca de Modelos", "Move Selected Nodes Down": "Mover nodos seleccionados hacia abajo", "Move Selected Nodes Left": "Mover nodos seleccionados hacia la izquierda", @@ -1204,8 +1574,16 @@ "Node Links": "Enlaces de nodos", "Open": "Abrir", "Open 3D Viewer (Beta) for Selected Node": "Abrir Visor 3D (Beta) para Nodo Seleccionado", + "Open Color Picker in MaskEditor": "Abrir selector de color en MaskEditor", + "Open Custom Nodes Folder": "Abrir carpeta de nodos personalizados", + "Open DevTools": "Abrir herramientas de desarrollo", + "Open Inputs Folder": "Abrir carpeta de entradas", + "Open Logs Folder": "Abrir carpeta de registros", "Open Mask Editor for Selected Node": "Abrir el editor de mask para el nodo seleccionado", + "Open Models Folder": "Abrir carpeta de modelos", + "Open Outputs Folder": "Abrir carpeta de salidas", "Open Sign In Dialog": "Abrir diálogo de inicio de sesión", + "Open extra_model_paths_yaml": "Abrir extra_model_paths.yaml", "Pin/Unpin Selected Items": "Anclar/Desanclar elementos seleccionados", "Pin/Unpin Selected Nodes": "Anclar/Desanclar nodos seleccionados", "Previous Opened Workflow": "Flujo de trabajo abierto anterior", @@ -1213,10 +1591,16 @@ "Queue Prompt": "Indicador de cola", "Queue Prompt (Front)": "Indicador de cola (Frente)", "Queue Selected Output Nodes": "Encolar nodos de salida seleccionados", + "Quit": "Salir", "Redo": "Rehacer", "Refresh Node Definitions": "Actualizar definiciones de nodo", + "Reinstall": "Reinstalar", + "Rename": "Renombrar", "Reset View": "Restablecer vista", "Resize Selected Nodes": "Redimensionar Nodos Seleccionados", + "Restart": "Reiniciar", + "Rotate Left in MaskEditor": "Girar a la izquierda en el editor de máscaras", + "Rotate Right in MaskEditor": "Girar a la derecha en el editor de máscaras", "Save": "Guardar", "Save As": "Guardar como", "Show Keybindings Dialog": "Mostrar diálogo de combinaciones de teclas", @@ -1225,12 +1609,13 @@ "Sign Out": "Cerrar sesión", "Toggle Essential Bottom Panel": "Alternar panel inferior esencial", "Toggle Logs Bottom Panel": "Alternar panel inferior de registros", + "Toggle Queue Panel V2": "Alternar panel de cola V2", "Toggle Search Box": "Alternar caja de búsqueda", + "Toggle Simple Mode": "Alternar modo simple", "Toggle Terminal Bottom Panel": "Alternar panel inferior de terminal", "Toggle Theme (Dark/Light)": "Alternar tema (Oscuro/Claro)", "Toggle View Controls Bottom Panel": "Alternar panel inferior de controles de vista", "Toggle promotion of hovered widget": "Alternar promoción del widget sobre el que se pasa el cursor", - "Toggle the Custom Nodes Manager Progress Bar": "Alternar la Barra de Progreso del Administrador de Nodos Personalizados", "Undo": "Deshacer", "Ungroup selected group nodes": "Desagrupar nodos de grupo seleccionados", "Unload Models": "Descargar modelos", @@ -1255,30 +1640,56 @@ "missingModels": "Modelos faltantes", "missingModelsMessage": "Al cargar el gráfico, no se encontraron los siguientes modelos" }, + "missingNodes": { + "cloud": { + "description": "Este flujo de trabajo utiliza nodos personalizados que aún no son compatibles con la versión Cloud.", + "gotIt": "Entendido", + "learnMore": "Saber más", + "priorityMessage": "Hemos marcado automáticamente estos nodos para priorizar su incorporación.", + "replacementInstruction": "Mientras tanto, reemplaza estos nodos (resaltados en rojo en el lienzo) por otros compatibles si es posible, o prueba con otro flujo de trabajo.", + "title": "Estos nodos aún no están disponibles en Comfy Cloud" + }, + "oss": { + "description": "Este flujo de trabajo utiliza nodos personalizados que aún no has instalado.", + "replacementInstruction": "Instala estos nodos para ejecutar este flujo de trabajo, o reemplázalos por alternativas ya instaladas. Los nodos faltantes están resaltados en rojo en el lienzo.", + "title": "Este flujo de trabajo tiene nodos faltantes" + } + }, + "nightly": { + "badge": { + "label": "Versión preliminar", + "tooltip": "Estás usando una versión nightly de ComfyUI. Por favor, utiliza el botón de comentarios para compartir tus opiniones sobre estas funciones." + } + }, "nodeCategories": { + "": "", "3d": "3d", "3d_models": "modelos_3d", "BFL": "BFL", + "Bria": "Bria", "ByteDance": "ByteDance", "Gemini": "Gemini", "Ideogram": "Ideogram", "Kling": "Kling", "LTXV": "LTXV", "Luma": "Luma", + "Meshy": "Meshy", "MiniMax": "MiniMax", "Moonvalley Marey": "Moonvalley Marey", "OpenAI": "OpenAI", - "Pika": "Pika", "PixVerse": "PixVerse", "Recraft": "Recraft", "Rodin": "Rodin", "Runway": "Runway", "Sora": "Sora", "Stability AI": "Stability AI", + "Tencent": "Tencent", + "Topaz": "Topaz", "Tripo": "Tripo", "Veo": "Veo", "Vidu": "Vidu", "Wan": "Wan", + "WaveSpeed": "WaveSpeed", "_for_testing": "_para_pruebas", "advanced": "avanzado", "animation": "animación", @@ -1299,6 +1710,7 @@ "controlnet": "controlnet", "create": "crear", "custom_sampling": "muestreo_personalizado", + "dataset": "conjunto de datos", "debug": "depurar", "deprecated": "obsoleto", "edit_models": "editar_modelos", @@ -1310,8 +1722,10 @@ "image": "imagen", "inpaint": "pintar", "instructpix2pix": "instruirpix2pix", + "kandinsky5": "kandinsky5", "latent": "latent", "loaders": "cargadores", + "logic": "lógica", "lotus": "lotus", "ltxv": "ltxv", "mask": "mask", @@ -1345,7 +1759,15 @@ "upscaling": "escalado", "utils": "utilidades", "video": "video", - "video_models": "modelos_de_video" + "video_models": "modelos_de_video", + "zimage": "zimage" + }, + "nodeErrors": { + "content": "Error de contenido del nodo", + "header": "Error de encabezado del nodo", + "render": "Error de renderizado del nodo", + "slots": "Error de ranuras del nodo", + "widgets": "Error de widgets del nodo" }, "nodeHelpPage": { "documentationPage": "página de documentación", @@ -1362,6 +1784,7 @@ "notSupported": { "continue": "Continuar", "continueTooltip": "Estoy seguro de que mi dispositivo es compatible", + "illustrationAlt": "Ilustración de chica triste", "learnMore": "Aprender más", "message": "Solo los siguientes dispositivos son compatibles:", "reportIssue": "Reportar problema", @@ -1371,12 +1794,136 @@ }, "title": "Tu dispositivo no es compatible" }, + "progressToast": { + "allDownloadsCompleted": "Todas las descargas completadas", + "downloadingModel": "Descargando modelo...", + "downloadsFailed": "{count} descargas fallidas | {count} descarga fallida | {count} descargas fallidas", + "failed": "Fallido", + "filter": { + "all": "Todo", + "completed": "Completado", + "failed": "Fallido" + }, + "finished": "Finalizado", + "importingModels": "Importando modelos", + "noImportsInQueue": "No hay {filter} en la cola", + "pending": "Pendiente", + "progressCount": "{completed} de {total}" + }, + "queue": { + "completedIn": "Finalizado en {duration}", + "inQueue": "En cola...", + "initializingAlmostReady": "Inicializando - Casi listo", + "jobAddedToQueue": "Trabajo añadido a la cola", + "jobDetails": { + "computeHoursUsed": "Horas de cómputo usadas", + "errorMessage": "Mensaje de error", + "estimatedFinishIn": "Finalización estimada en", + "estimatedStartIn": "Inicio estimado en", + "eta": { + "minutes": "~{count} minuto | ~{count} minutos", + "minutesRange": "~{lo}-{hi} minutos", + "seconds": "~{count} segundo | ~{count} segundos", + "secondsRange": "~{lo}-{hi} segundos" + }, + "failedAfter": "Falló después de", + "generatedOn": "Generado en", + "header": "Detalles del trabajo", + "jobId": "ID de trabajo", + "queuePosition": "Posición en la cola", + "queuePositionValue": "~{count} trabajo antes que el tuyo | ~{count} trabajos antes que el tuyo", + "queuedAt": "En cola a las", + "report": "Reportar", + "timeElapsed": "Tiempo transcurrido", + "totalGenerationTime": "Tiempo total de generación", + "workflow": "Flujo de trabajo" + }, + "jobHistory": "Historial de trabajos", + "jobList": { + "sortComputeHoursUsed": "Horas de cómputo usadas (primero el mayor)", + "sortMostRecent": "Más reciente", + "sortTotalGenerationTime": "Tiempo total de generación (primero el más largo)", + "undated": "Sin fecha" + }, + "jobMenu": { + "addToCurrentWorkflow": "Agregar al flujo de trabajo actual", + "cancelJob": "Cancelar trabajo", + "copyErrorMessage": "Copiar mensaje de error", + "copyJobId": "Copiar ID de trabajo", + "delete": "Eliminar", + "deleteAsset": "Eliminar recurso", + "download": "Descargar", + "exportWorkflow": "Exportar flujo de trabajo", + "inspectAsset": "Inspeccionar recurso", + "openAsWorkflowNewTab": "Abrir como flujo de trabajo en nueva pestaña", + "openWorkflowNewTab": "Abrir flujo de trabajo en nueva pestaña", + "removeJob": "Quitar trabajo", + "reportError": "Reportar error" + }, + "toggleJobHistory": "Alternar historial de trabajos" + }, "releaseToast": { + "description": "Descubre las últimas mejoras y funciones en esta actualización.", "newVersionAvailable": "¡Nueva versión disponible!", "skip": "Omitir", "update": "Actualizar", "whatsNew": "¿Qué hay de nuevo?" }, + "rightSidePanel": { + "addFavorite": "Favorito", + "advancedInputs": "ENTRADAS AVANZADAS", + "bypass": "Omitir", + "color": "Color del nodo", + "fallbackGroupTitle": "Grupo", + "fallbackNodeTitle": "Nodo", + "favorites": "ENTRADAS FAVORITAS", + "favoritesNone": "SIN ENTRADAS FAVORITAS", + "favoritesNoneDesc": "Las entradas que marques como favoritas aparecerán aquí", + "favoritesNoneTooltip": "Marca widgets con estrella para acceder rápidamente sin seleccionar nodos", + "globalSettings": { + "canvas": "LIENZO", + "connectionLinks": "ENLACES DE CONEXIÓN", + "gridSpacing": "Espaciado de la cuadrícula", + "linkShape": "Forma del enlace", + "nodes": "NODOS", + "nodes2": "Nodos 2.0", + "searchPlaceholder": "Buscar configuración rápida...", + "showAdvanced": "Mostrar parámetros avanzados", + "showAdvancedTooltip": "Esta es una configuración importante que, al activarse, muestra todos los parámetros avanzados de los nodos", + "showConnectedLinks": "Mostrar enlaces conectados", + "showInfoBadges": "Mostrar insignias de información", + "showToolbox": "Mostrar caja de herramientas al seleccionar", + "snapNodesToGrid": "Ajustar nodos a la cuadrícula", + "title": "Configuración global", + "viewAllSettings": "Ver toda la configuración" + }, + "groupSettings": "Configuración de grupo", + "groups": "Grupos", + "hideAdvancedInputsButton": "Ocultar entradas avanzadas", + "hideInput": "Ocultar entrada", + "info": "Información", + "inputs": "ENTRADAS", + "inputsNone": "SIN ENTRADAS", + "inputsNoneTooltip": "El nodo no tiene entradas", + "locateNode": "Localizar nodo en el lienzo", + "mute": "Silenciar", + "noSelection": "Selecciona un nodo para ver sus propiedades e información.", + "nodeState": "Estado del nodo", + "nodes": "Nodos", + "nodesNoneDesc": "SIN NODOS", + "noneSearchDesc": "Ningún elemento coincide con tu búsqueda", + "normal": "Normal", + "parameters": "Parámetros", + "pinned": "Fijado", + "properties": "Propiedades", + "removeFavorite": "Quitar de favoritos", + "settings": "Configuración", + "showAdvancedInputsButton": "Mostrar entradas avanzadas", + "showInput": "Mostrar entrada", + "title": "Ningún nodo seleccionado | 1 nodo seleccionado | {count} nodos seleccionados", + "togglePanel": "Mostrar/ocultar panel de propiedades", + "workflowOverview": "Resumen del flujo de trabajo" + }, "selectionToolbox": { "Bypass Group Nodes": "Omitir nodos de grupo", "Set Group Nodes to Always": "Establecer nodos de grupo en Siempre", @@ -1389,6 +1936,8 @@ "serverConfig": { "modifiedConfigs": "Has modificado las siguientes configuraciones del servidor. Reinicia para aplicar los cambios.", "restart": "Reiniciar", + "restartRequiredToastDetail": "Reinicia la aplicación para aplicar los cambios de configuración del servidor.", + "restartRequiredToastSummary": "Reinicio requerido", "revertChanges": "Revertir cambios" }, "serverConfigCategories": { @@ -1457,6 +2006,10 @@ "enable-cors-header": { "name": "Habilitar encabezado CORS: Use \"*\" para todos los orígenes o especifique el dominio" }, + "enable-manager-legacy-ui": { + "name": "Usar interfaz de usuario Manager heredada", + "tooltip": "Utiliza la interfaz de usuario heredada de ComfyUI-Manager en lugar de la nueva interfaz." + }, "fast": { "name": "Habilitar algunas optimizaciones no probadas y potencialmente deteriorantes de la calidad." }, @@ -1558,6 +2111,7 @@ "CustomColorPalettes": "Paletas de Colores Personalizadas", "DevMode": "Modo de Desarrollo", "EditTokenWeight": "Editar Peso del Token", + "Execution": "Ejecución", "Extension": "Extensión", "General": "General", "Graph": "Gráfico", @@ -1572,12 +2126,14 @@ "Mask Editor": "Editor de Máscara", "Menu": "Menú", "ModelLibrary": "Biblioteca de Modelos", - "NewEditor": "Nuevo Editor", "Node": "Nodo", "Node Search Box": "Caja de Búsqueda de Nodo", "Node Widget": "Widget de Nodo", "NodeLibrary": "Biblioteca de Nodos", + "Nodes 2_0": "Nodes 2.0", "Notification Preferences": "Preferencias de notificación", + "Other": "Otros", + "PLY": "PLY", "PlanCredits": "Plan y créditos", "Pointer": "Puntero", "Queue": "Cola", @@ -1596,7 +2152,8 @@ "Vue Nodes": "Nodos Vue", "VueNodes": "Nodos Vue", "Window": "Ventana", - "Workflow": "Flujo de Trabajo" + "Workflow": "Flujo de Trabajo", + "Workspace": "Espacio de trabajo" }, "shape": { "CARD": "Card", @@ -1622,11 +2179,14 @@ "viewControls": "Controles de vista" }, "sideToolbar": { + "activeJobStatus": "Trabajo activo: {status}", "assets": "Recursos", "backToAssets": "Volver a todos los recursos", "browseTemplates": "Explorar plantillas de ejemplo", "downloads": "Descargas", + "generatedAssetsHeader": "Recursos generados", "helpCenter": "Centro de ayuda", + "importedAssetsHeader": "Recursos importados", "labels": { "assets": "Recursos", "console": "Consola", @@ -1669,6 +2229,47 @@ }, "openWorkflow": "Abrir flujo de trabajo en el sistema de archivos local", "queue": "Cola", + "queueProgressOverlay": { + "activeJobs": "{count} trabajo activo | {count} trabajos activos", + "activeJobsShort": "{count} activo(s) | {count} activo(s)", + "activeJobsSuffix": "trabajos activos", + "cancelJobTooltip": "Cancelar trabajo", + "clearHistory": "Limpiar historial de la cola de trabajos", + "clearHistoryDialogAssetsNote": "Los recursos generados por estos trabajos no se eliminarán y siempre podrán verse desde el panel de recursos.", + "clearHistoryDialogDescription": "Todos los trabajos finalizados o fallidos a continuación serán eliminados de este panel de cola de trabajos.", + "clearHistoryDialogTitle": "¿Limpiar el historial de la cola de trabajos?", + "clearQueueTooltip": "Limpiar cola", + "clearQueued": "Limpiar en cola", + "colonPercent": ": {percent}", + "currentNode": "Nodo actual:", + "expandCollapsedQueue": "Expandir cola de trabajos", + "filterAllWorkflows": "Todos los flujos de trabajo", + "filterBy": "Filtrar por", + "filterCurrentWorkflow": "Flujo de trabajo actual", + "filterJobs": "Filtrar trabajos", + "interruptAll": "Interrumpir todos los trabajos en ejecución", + "jobQueue": "Cola de trabajos", + "jobsCompleted": "{count} trabajo completado | {count} trabajos completados", + "jobsFailed": "{count} trabajo fallido | {count} trabajos fallidos", + "moreOptions": "Más opciones", + "noActiveJobs": "No hay trabajos activos", + "preview": "Vista previa", + "queuedSuffix": "en cola", + "running": "en ejecución", + "showAssets": "Mostrar recursos", + "showAssetsPanel": "Mostrar panel de recursos", + "sortBy": "Ordenar por", + "sortJobs": "Ordenar trabajos", + "stubClipTextEncode": "CLIP Text Encode:", + "title": "Progreso de la cola", + "total": "Total: {percent}", + "viewAllJobs": "Ver todos los trabajos", + "viewGrid": "Vista de cuadrícula", + "viewJobHistory": "Ver historial de trabajos", + "viewList": "Vista de lista" + }, + "searchAssets": "Buscar recursos", + "sidebar": "Barra lateral", "templates": "Plantillas", "themeToggle": "Cambiar tema", "workflowTab": { @@ -1711,24 +2312,58 @@ "subscription": { "addApiCredits": "Agregar créditos de API", "addCredits": "Agregar créditos", + "addCreditsLabel": "Agrega más créditos cuando quieras", "benefits": { "benefit1": "Créditos mensuales para Nodos de Socio — recarga cuando sea necesario", "benefit2": "Hasta 30 min de tiempo de ejecución por trabajo" }, "beta": "BETA", + "billedMonthly": "Facturado mensualmente", + "billedYearly": "{total} facturado anualmente", + "billingComingSoon": { + "message": "La facturación para equipos estará disponible pronto. Podrás suscribirte a un plan para tu espacio de trabajo con precios por usuario. Mantente atento para más actualizaciones.", + "title": "Próximamente" + }, + "cancelSubscription": "Cancelar suscripción", + "changeTo": "Cambiar a {plan}", "comfyCloud": "Comfy Cloud", + "comfyCloudLogo": "Logo de Comfy Cloud", + "contactOwnerToSubscribe": "Contacta al propietario del espacio de trabajo para suscribirte", + "contactUs": "Contáctanos", + "creditsRemainingThisMonth": "Créditos restantes este mes", + "creditsRemainingThisYear": "Créditos restantes este año", + "creditsYouveAdded": "Créditos que has agregado", + "currentPlan": "Plan actual", + "customLoRAsLabel": "Importa tus propios LoRAs", + "description": "Elige el mejor plan para ti", "expiresDate": "Caduca el {date}", + "gpuLabel": "RTX 6000 Pro (96GB VRAM)", + "haveQuestions": "¿Tienes preguntas o buscas soluciones empresariales?", "invoiceHistory": "Historial de facturas", "learnMore": "Más información", + "managePayment": "Gestionar pago", + "managePlan": "Gestionar plan", "manageSubscription": "Gestionar suscripción", + "maxDuration": { + "creator": "30 min", + "founder": "30 min", + "pro": "1 h", + "standard": "30 min" + }, + "maxDurationLabel": "Duración máxima de cada ejecución de flujo de trabajo", "messageSupport": "Contactar con soporte", + "monthly": "Mensual", "monthlyBonusDescription": "Bono de créditos mensual", + "monthlyCreditsInfo": "Estos créditos se renuevan mensualmente y no se acumulan", + "monthlyCreditsLabel": "Créditos mensuales", "monthlyCreditsRollover": "Estos créditos se transferirán al próximo mes", + "mostPopular": "Más popular", "nextBillingCycle": "próximo ciclo de facturación", "partnerNodesBalance": "Saldo de créditos de \"Nodos de Partners\"", "partnerNodesCredits": "Créditos de Nodos de Socio", "partnerNodesDescription": "Para ejecutar modelos comerciales/propietarios", "perMonth": "USD / mes", + "plansAndPricing": "Planes y precios", "prepaidCreditsInfo": "Créditos comprados por separado que no expiran", "prepaidDescription": "Créditos prepagados", "renewsDate": "Se renueva el {date}", @@ -1738,14 +2373,46 @@ "waitingForSubscription": "Completa tu suscripción en la nueva pestaña. ¡Detectaremos automáticamente cuando hayas terminado!" }, "subscribeNow": "Suscribirse Ahora", + "subscribeTo": "Suscribirse a {plan}", "subscribeToComfyCloud": "Suscribirse a Comfy Cloud", "subscribeToRun": "Suscribirse", "subscribeToRunFull": "Suscribirse a Ejecutar", + "subscriptionRequiredMessage": "Se requiere una suscripción para que los miembros ejecuten flujos de trabajo en la nube", + "tierNameYearly": "{name} Anual", + "tiers": { + "creator": { + "name": "Creador" + }, + "founder": { + "name": "Edición Fundador" + }, + "pro": { + "name": "Pro" + }, + "standard": { + "name": "Estándar" + } + }, "title": "Suscripción", "titleUnsubscribed": "Suscríbete a Comfy Cloud", "totalCredits": "Créditos totales", + "upgrade": "MEJORAR", + "upgradePlan": "Mejorar plan", + "upgradeTo": "Mejorar a {plan}", + "usdPerMonth": "USD / mes", + "videoEstimateExplanation": "Estas estimaciones se basan en la plantilla Wan 2.2 Imagen a Video usando la configuración predeterminada (5 segundos, 640x640, 16fps, muestreo de 4 pasos).", + "videoEstimateHelp": "Más detalles sobre esta plantilla", + "videoEstimateLabel": "Cantidad aprox. de videos de 5s generados con la plantilla Wan 2.2 Imagen a Video", + "videoEstimateTryTemplate": "Probar esta plantilla", + "videoTemplateBasedCredits": "Videos generados con Wan 2.2 Imagen a Video", + "viewEnterprise": "Ver empresa", "viewMoreDetails": "Ver más detalles", + "viewMoreDetailsPlans": "Ver más detalles sobre planes y precios", "viewUsageHistory": "Ver historial de uso", + "workspaceNotSubscribed": "Este espacio de trabajo no tiene una suscripción", + "yearly": "Anual", + "yearlyCreditsLabel": "Total de créditos anuales", + "yearlyDiscount": "20% DESCUENTO", "yourPlanIncludes": "Tu plan incluye:" }, "tabMenu": { @@ -1757,8 +2424,14 @@ "duplicateTab": "Duplicar pestaña", "removeFromBookmarks": "Eliminar de marcadores" }, + "templateWidgets": { + "sort": { + "searchPlaceholder": "Buscar..." + } + }, "templateWorkflows": { "activeFilters": "Filtros:", + "allTemplates": "Todas las plantillas", "categories": "Categorías", "category": { "3D": "3D", @@ -1785,6 +2458,7 @@ "error": { "templateNotFound": "Plantilla \"{templateName}\" no encontrada" }, + "licenseFilter": "Licencia", "loading": "Cargando plantillas...", "loadingMore": "Cargando más plantillas...", "modelFilter": "Filtro de modelo", @@ -1801,12 +2475,14 @@ "default": "Predeterminado", "modelSizeLowToHigh": "Tamaño del modelo (de bajo a alto)", "newest": "Más reciente", + "popular": "Popular", "recommended": "Recomendado", "searchPlaceholder": "Buscar...", "vramLowToHigh": "Uso de VRAM (de bajo a alto)" }, "sorting": "Ordenar por", "title": "Comienza con una Plantilla", + "useCaseFilter": "Tareas", "useCasesSelected": "{count} casos de uso" }, "toastMessages": { @@ -1835,9 +2511,22 @@ "failedToLoadModel": "Error al cargar el modelo 3D", "failedToPurchaseCredits": "No se pudo comprar créditos: {error}", "failedToQueue": "Error al encolar", + "failedToToggleCamera": "No se pudo alternar la cámara", + "failedToToggleGrid": "No se pudo alternar la cuadrícula", + "failedToUpdateBackgroundColor": "No se pudo actualizar el color de fondo", + "failedToUpdateBackgroundImage": "No se pudo actualizar la imagen de fondo", + "failedToUpdateBackgroundRenderMode": "No se pudo actualizar el modo de renderizado de fondo a {mode}", + "failedToUpdateEdgeThreshold": "No se pudo actualizar el umbral de borde", + "failedToUpdateFOV": "No se pudo actualizar el campo de visión", + "failedToUpdateLightIntensity": "No se pudo actualizar la intensidad de la luz", + "failedToUpdateMaterialMode": "No se pudo actualizar el modo de material", + "failedToUpdateUpDirection": "No se pudo actualizar la dirección superior", + "failedToUploadBackgroundImage": "No se pudo subir la imagen de fondo", "fileLoadError": "No se puede encontrar el flujo de trabajo en {fileName}", + "fileTooLarge": "Archivo demasiado grande ({size} MB). El tamaño máximo soportado es {maxSize} MB", "fileUploadFailed": "Error al subir el archivo", "interrupted": "La ejecución ha sido interrumpida", + "legacyMaskEditorDeprecated": "El editor de máscaras heredado está obsoleto y se eliminará pronto.", "migrateToLitegraphReroute": "Los nodos de reroute se eliminarán en futuras versiones. Haz clic para migrar a reroute nativo de litegraph.", "modelLoadedSuccessfully": "Modelo 3D cargado exitosamente", "no3dScene": "No hay escena 3D para aplicar textura", @@ -1864,12 +2553,14 @@ "selectUser": "Selecciona un usuario" }, "userSettings": { + "accountSettings": "Configuración de la cuenta", "email": "Correo electrónico", "name": "Nombre", "notSet": "No establecido", "provider": "Método de inicio de sesión", "title": "Configuración de usuario", - "updatePassword": "Actualizar contraseña" + "updatePassword": "Actualizar contraseña", + "workspaceSettings": "Configuración del espacio de trabajo" }, "validation": { "descriptionRequired": "Descripción es requerida", @@ -1898,22 +2589,32 @@ "updateFrontend": "Actualizar frontend" }, "vueNodesBanner": { - "message": "Los nodos tienen un nuevo aspecto y sensación", + "desc": "– Flujos de trabajo más flexibles, nuevos widgets potentes, diseñado para la extensibilidad", + "title": "Presentamos Nodes 2.0", "tryItOut": "Pruébalo" }, "vueNodesMigration": { "button": "Abrir Configuración", "message": "¿Prefieres el diseño clásico de nodos?" }, + "vueNodesMigrationMainMenu": { + "message": "Cambia a Nodes 2.0 en cualquier momento desde el menú principal." + }, "welcome": { "getStarted": "Empezar", "title": "Bienvenido a ComfyUI" }, "whatsNewPopup": { + "later": "Más tarde", "learnMore": "Aprende más", "noReleaseNotes": "No hay notas de la versión disponibles." }, + "widgetFileUpload": { + "browseFiles": "Buscar archivos", + "dropPrompt": "Suelta tu archivo o" + }, "widgets": { + "node2only": "Solo Node 2.0", "selectModel": "Seleccionar modelo", "uploadSelect": { "placeholder": "Seleccionar...", @@ -1922,6 +2623,26 @@ "placeholderModel": "Seleccionar modelo...", "placeholderUnknown": "Seleccionar medio...", "placeholderVideo": "Seleccionar video..." + }, + "valueControl": { + "decrement": "Disminuir valor", + "decrementDesc": "Resta 1 al valor o selecciona la opción anterior", + "editSettings": "Editar configuración de control", + "fixed": "Valor fijo", + "fixedDesc": "Deja el valor sin cambios", + "header": { + "after": "DESPUÉS", + "before": "ANTES", + "postfix": "de ejecutar el flujo de trabajo:", + "prefix": "Actualizar automáticamente el valor" + }, + "increment": "Incrementar valor", + "incrementDesc": "Suma 1 al valor o selecciona la siguiente opción", + "linkToGlobal": "Vincular a", + "linkToGlobalDesc": "Valor único vinculado a la configuración de control del Valor Global", + "linkToGlobalSeed": "Valor Global", + "randomize": "Aleatorizar valor", + "randomizeDesc": "Mezcla el valor aleatoriamente después de cada generación" } }, "workflowService": { @@ -1929,6 +2650,146 @@ "exportWorkflow": "Exportar flujo de trabajo", "saveWorkflow": "Guardar flujo de trabajo" }, + "workspace": { + "addedToWorkspace": "Has sido añadido a {workspaceName}", + "inviteAccepted": "Invitación aceptada", + "inviteFailed": "No se pudo aceptar la invitación", + "unsavedChanges": { + "message": "Tienes cambios no guardados. ¿Quieres descartarlos y cambiar de espacio de trabajo?", + "title": "Cambios no guardados" + } + }, + "workspaceAuth": { + "errors": { + "accessDenied": "No tienes acceso a este espacio de trabajo", + "invalidFirebaseToken": "La autenticación ha fallado. Por favor, intenta iniciar sesión de nuevo.", + "notAuthenticated": "Debes iniciar sesión para acceder a los espacios de trabajo", + "tokenExchangeFailed": "No se pudo autenticar con el espacio de trabajo: {error}", + "workspaceNotFound": "Espacio de trabajo no encontrado" + } + }, + "workspacePanel": { + "createWorkspaceDialog": { + "create": "Crear", + "message": "Los espacios de trabajo permiten a los miembros compartir un único fondo de créditos. Te convertirás en el propietario después de crearlo.", + "nameLabel": "Nombre del espacio de trabajo*", + "namePlaceholder": "Introduce el nombre del espacio de trabajo", + "title": "Crear un nuevo espacio de trabajo" + }, + "dashboard": { + "placeholder": "Configuración del panel del espacio de trabajo" + }, + "deleteDialog": { + "message": "Cualquier crédito no utilizado o recurso no guardado se perderá. Esta acción no se puede deshacer.", + "messageWithName": "¿Eliminar \"{name}\"? Cualquier crédito no utilizado o recurso no guardado se perderá. Esta acción no se puede deshacer.", + "title": "¿Eliminar este espacio de trabajo?" + }, + "editWorkspaceDialog": { + "nameLabel": "Nombre del espacio de trabajo", + "save": "Guardar", + "title": "Editar detalles del espacio de trabajo" + }, + "invite": "Invitar", + "inviteLimitReached": "Has alcanzado el máximo de 50 miembros", + "inviteMember": "Invitar miembro", + "inviteMemberDialog": { + "createLink": "Crear enlace", + "linkCopied": "Copiado", + "linkCopyFailed": "No se pudo copiar el enlace", + "linkStep": { + "copyLink": "Copiar enlace", + "done": "Listo", + "message": "Asegúrate de que su cuenta use este correo electrónico.", + "title": "Envía este enlace a la persona" + }, + "message": "Crea un enlace de invitación para compartir y envíalo a alguien", + "placeholder": "Introduce el correo electrónico de la persona", + "title": "Invitar a una persona a este espacio de trabajo" + }, + "leaveDialog": { + "leave": "Abandonar", + "message": "No podrás unirte de nuevo a menos que contactes al propietario del espacio de trabajo.", + "title": "¿Abandonar este espacio de trabajo?" + }, + "members": { + "actions": { + "copyLink": "Copiar enlace de invitación", + "removeMember": "Eliminar miembro", + "revokeInvite": "Revocar invitación" + }, + "columns": { + "expiryDate": "Fecha de vencimiento", + "inviteDate": "Fecha de invitación", + "joinDate": "Fecha de ingreso" + }, + "createNewWorkspace": "crea uno nuevo.", + "membersCount": "{count}/50 Miembros", + "noInvites": "No hay invitaciones pendientes", + "noMembers": "No hay miembros", + "pendingInvitesCount": "{count} invitación pendiente | {count} invitaciones pendientes", + "personalWorkspaceMessage": "No puedes invitar a otros miembros a tu espacio de trabajo personal en este momento. Para agregar miembros a un espacio de trabajo,", + "tabs": { + "active": "Activo", + "pendingCount": "Pendientes ({count})" + } + }, + "menu": { + "deleteWorkspace": "Eliminar espacio de trabajo", + "deleteWorkspaceDisabledTooltip": "Primero cancela la suscripción activa de tu espacio de trabajo", + "editWorkspace": "Editar detalles del espacio de trabajo", + "leaveWorkspace": "Abandonar espacio de trabajo" + }, + "removeMemberDialog": { + "error": "No se pudo eliminar al miembro", + "message": "Este miembro será eliminado de tu espacio de trabajo. Los créditos que haya utilizado no serán reembolsados.", + "remove": "Eliminar miembro", + "success": "Miembro eliminado", + "title": "¿Eliminar a este miembro?" + }, + "revokeInviteDialog": { + "message": "Este miembro ya no podrá unirse a tu espacio de trabajo. Su enlace de invitación será invalidado.", + "revoke": "Desinvitar", + "title": "¿Desinvitar a esta persona?" + }, + "tabs": { + "dashboard": "Panel", + "membersCount": "Miembros ({count})", + "planCredits": "Plan y créditos" + }, + "toast": { + "failedToCreateWorkspace": "No se pudo crear el espacio de trabajo", + "failedToDeleteWorkspace": "No se pudo eliminar el espacio de trabajo", + "failedToFetchWorkspaces": "No se pudieron cargar los espacios de trabajo", + "failedToLeaveWorkspace": "No se pudo abandonar el espacio de trabajo", + "failedToUpdateWorkspace": "No se pudo actualizar el espacio de trabajo", + "workspaceCreated": { + "message": "Suscríbete a un plan, invita a compañeros y comienza a colaborar.", + "subscribe": "Suscribirse", + "title": "Espacio de trabajo creado" + }, + "workspaceDeleted": { + "message": "El espacio de trabajo ha sido eliminado permanentemente.", + "title": "Espacio de trabajo eliminado" + }, + "workspaceLeft": { + "message": "Has salido del espacio de trabajo.", + "title": "Saliste del espacio de trabajo" + }, + "workspaceUpdated": { + "message": "Los detalles del espacio de trabajo se han guardado.", + "title": "Espacio de trabajo actualizado" + } + } + }, + "workspaceSwitcher": { + "createWorkspace": "Crear nuevo espacio de trabajo", + "maxWorkspacesReached": "Solo puedes ser propietario de 10 espacios de trabajo. Elimina uno para crear uno nuevo.", + "personal": "Personal", + "roleMember": "Miembro", + "roleOwner": "Propietario", + "subscribe": "Suscribirse", + "switchWorkspace": "Cambiar espacio de trabajo" + }, "zoomControls": { "hideMinimap": "Ocultar minimapa", "label": "Controles de zoom", diff --git a/src/locales/es/nodeDefs.json b/src/locales/es/nodeDefs.json index 393a3cd98..11c4f9765 100644 --- a/src/locales/es/nodeDefs.json +++ b/src/locales/es/nodeDefs.json @@ -39,6 +39,87 @@ "sigmas": { "name": "sigmas" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AddTextPrefix": { + "display_name": "Agregar prefijo de texto", + "inputs": { + "prefix": { + "name": "prefijo", + "tooltip": "Prefijo para agregar." + }, + "texts": { + "name": "textos", + "tooltip": "Texto a procesar." + } + }, + "outputs": { + "0": { + "name": "textos", + "tooltip": "Textos procesados" + } + } + }, + "AddTextSuffix": { + "display_name": "Agregar sufijo de texto", + "inputs": { + "suffix": { + "name": "sufijo", + "tooltip": "Sufijo para agregar." + }, + "texts": { + "name": "textos", + "tooltip": "Texto a procesar." + } + }, + "outputs": { + "0": { + "name": "textos", + "tooltip": "Textos procesados" + } + } + }, + "AdjustBrightness": { + "display_name": "Ajustar brillo", + "inputs": { + "factor": { + "name": "factor", + "tooltip": "Factor de brillo. 1.0 = sin cambio, <1.0 = más oscuro, >1.0 = más brillante." + }, + "images": { + "name": "imágenes", + "tooltip": "Imagen a procesar." + } + }, + "outputs": { + "0": { + "name": "imágenes", + "tooltip": "Imágenes procesadas" + } + } + }, + "AdjustContrast": { + "display_name": "Ajustar contraste", + "inputs": { + "factor": { + "name": "factor", + "tooltip": "Factor de contraste. 1.0 = sin cambio, <1.0 = menos contraste, >1.0 = más contraste." + }, + "images": { + "name": "imágenes", + "tooltip": "Imagen a procesar." + } + }, + "outputs": { + "0": { + "name": "imágenes", + "tooltip": "Imágenes procesadas" + } } }, "AlignYourStepsScheduler": { @@ -70,6 +151,11 @@ "name": "volumen", "tooltip": "Ajuste de volumen en decibelios (dB). 0 = sin cambios, +6 = doble, -6 = mitad, etc." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "AudioConcat": { @@ -86,6 +172,11 @@ "name": "dirección", "tooltip": "Si agregar audio2 después o antes de audio1." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "AudioEncoderEncode": { @@ -131,6 +222,11 @@ "name": "método_combinación", "tooltip": "El método utilizado para combinar las formas de onda de audio." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BasicGuider": { @@ -142,6 +238,11 @@ "model": { "name": "modelo" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BasicScheduler": { @@ -159,6 +260,50 @@ "steps": { "name": "pasos" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchImagesNode": { + "display_name": "Procesar imágenes por lotes", + "inputs": { + "images": { + "name": "imágenes" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchLatentsNode": { + "display_name": "Procesar latents por lotes", + "inputs": { + "latents": { + "name": "latents" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchMasksNode": { + "display_name": "Procesar máscaras por lotes", + "inputs": { + "masks": { + "name": "máscaras" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BetaSamplingScheduler": { @@ -176,6 +321,73 @@ "steps": { "name": "pasos" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BriaImageEditNode": { + "description": "Edita imágenes usando el modelo más reciente de Bria", + "display_name": "Edición de Imagen Bria", + "inputs": { + "control_after_generate": { + "name": "control después de generar" + }, + "guidance_scale": { + "name": "escala_de_guía", + "tooltip": "Un valor más alto hace que la imagen siga la instrucción más de cerca." + }, + "image": { + "name": "imagen" + }, + "mask": { + "name": "máscara", + "tooltip": "Si se omite, la edición se aplica a toda la imagen." + }, + "model": { + "name": "modelo" + }, + "moderation": { + "name": "moderación", + "tooltip": "Configuración de moderación" + }, + "moderation_prompt_content_moderation": { + "name": "moderación_de_contenido_de_instrucción" + }, + "moderation_visual_input_moderation": { + "name": "moderación_visual_de_entrada" + }, + "moderation_visual_output_moderation": { + "name": "moderación_visual_de_salida" + }, + "negative_prompt": { + "name": "instrucción_negativa" + }, + "prompt": { + "name": "instrucción", + "tooltip": "Instrucción para editar la imagen" + }, + "seed": { + "name": "semilla" + }, + "steps": { + "name": "pasos" + }, + "structured_prompt": { + "name": "instrucción_estructurada", + "tooltip": "Una cadena que contiene la instrucción de edición estructurada en formato JSON. Usa esto en lugar de la instrucción habitual para un control preciso y programático." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "instrucción_estructurada", + "tooltip": null + } } }, "ByteDanceFirstLastFrameNode": { @@ -201,13 +413,16 @@ "name": "primer_fotograma", "tooltip": "Primer fotograma que se utilizará para el video." }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "Este parámetro se ignora para cualquier modelo excepto seedance-1-5-pro." + }, "last_frame": { "name": "último_fotograma", "tooltip": "Último fotograma que se utilizará para el video." }, "model": { - "name": "modelo", - "tooltip": "Nombre del modelo" + "name": "modelo" }, "prompt": { "name": "prompt", @@ -248,8 +463,7 @@ "tooltip": "La imagen base para editar" }, "model": { - "name": "modelo", - "tooltip": "Nombre del modelo" + "name": "modelo" }, "prompt": { "name": "prompt", @@ -286,8 +500,7 @@ "tooltip": "Alto personalizado para la imagen. El valor solo funciona si `tamaño_predefinido` está establecido en `Personalizado`" }, "model": { - "name": "modelo", - "tooltip": "Nombre del modelo" + "name": "modelo" }, "prompt": { "name": "prompt", @@ -336,8 +549,7 @@ "tooltip": "De una a cuatro imágenes." }, "model": { - "name": "modelo", - "tooltip": "Nombre del modelo" + "name": "modelo" }, "prompt": { "name": "prompt", @@ -381,13 +593,16 @@ "name": "duración", "tooltip": "La duración del video de salida en segundos." }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "Este parámetro se ignora para cualquier modelo excepto seedance-1-5-pro." + }, "image": { "name": "imagen", "tooltip": "Primer fotograma a usar para el video." }, "model": { - "name": "modelo", - "tooltip": "Nombre del modelo" + "name": "modelo" }, "prompt": { "name": "prompt", @@ -489,9 +704,12 @@ "name": "duración", "tooltip": "La duración del video de salida en segundos." }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "Este parámetro se ignora para cualquier modelo excepto seedance-1-5-pro." + }, "model": { - "name": "modelo", - "tooltip": "Nombre del modelo" + "name": "modelo" }, "prompt": { "name": "prompt", @@ -531,6 +749,11 @@ "positive": { "name": "positivo" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "CFGNorm": { @@ -769,6 +992,25 @@ } } }, + "CLIPTextEncodeKandinsky5": { + "display_name": "CLIPTextEncodeKandinsky5", + "inputs": { + "clip": { + "name": "clip" + }, + "clip_l": { + "name": "clip_l" + }, + "qwen25_7b": { + "name": "qwen25_7b" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "CLIPTextEncodeLumina2": { "description": "Codifica un mensaje del sistema y un mensaje del usuario utilizando un modelo CLIP en un embedding que se puede usar para guiar el modelo de difusión hacia la generación de imágenes específicas.", "display_name": "CLIP Text Encode para Lumina2", @@ -959,6 +1201,29 @@ } } }, + "CenterCropImages": { + "display_name": "Recorte central de imágenes", + "inputs": { + "height": { + "name": "alto", + "tooltip": "Alto del recorte." + }, + "images": { + "name": "imágenes", + "tooltip": "Imagen a procesar." + }, + "width": { + "name": "ancho", + "tooltip": "Ancho del recorte." + } + }, + "outputs": { + "0": { + "name": "imágenes", + "tooltip": "Imágenes procesadas" + } + } + }, "CheckpointLoader": { "display_name": "Cargar Punto de Control Con Configuración (OBSOLETO)", "inputs": { @@ -1095,6 +1360,26 @@ } } }, + "ComfySwitchNode": { + "display_name": "Interruptor", + "inputs": { + "on_false": { + "name": "en_falso" + }, + "on_true": { + "name": "en_verdadero" + }, + "switch": { + "name": "interruptor" + } + }, + "outputs": { + "0": { + "name": "salida", + "tooltip": null + } + } + }, "ConditioningAverage": { "display_name": "Promedio de Acondicionamiento", "inputs": { @@ -1327,14 +1612,14 @@ "name": "segundos_total" } }, - "outputs": { - "0": { - "name": "positivo" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "negativo" + { + "tooltip": null } - } + ] }, "ConditioningTimestepsRange": { "display_name": "Rango de Pasos de Tiempo", @@ -1391,6 +1676,10 @@ "name": "dimensión", "tooltip": "La dimensión a la que aplicar las ventanas de contexto." }, + "freenoise": { + "name": "ruido_libre", + "tooltip": "Si se aplica el barajado de ruido FreeNoise, mejora la mezcla de ventanas." + }, "fuse_method": { "name": "método_de_fusión", "tooltip": "El método a utilizar para fusionar las ventanas de contexto." @@ -1791,8 +2080,32 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, + "CustomCombo": { + "display_name": "Combinación personalizada", + "inputs": { + "choice": { + "name": "elección" + }, + "index": { + }, + "option1": { + } + }, + "outputs": [ + null, + { + "name": "ÍNDICE", + "tooltip": null + } + ] + }, "DiffControlNetLoader": { "display_name": "Cargar Modelo ControlNet (diff)", "inputs": { @@ -1829,7 +2142,12 @@ } }, "DisableNoise": { - "display_name": "DesactivarRuido" + "display_name": "DesactivarRuido", + "outputs": { + "0": { + "tooltip": null + } + } }, "DualCFGGuider": { "display_name": "Guía Dual CFG", @@ -1855,6 +2173,11 @@ "style": { "name": "estilo" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "DualCLIPLoader": { @@ -1938,6 +2261,11 @@ "name": "tasa_de_muestreo", "tooltip": "Tasa de muestreo del clip de audio vacío." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyChromaRadianceLatentImage": { @@ -1981,6 +2309,25 @@ } } }, + "EmptyFlux2LatentImage": { + "display_name": "Empty Flux 2 Latent", + "inputs": { + "batch_size": { + "name": "tamaño_lote" + }, + "height": { + "name": "alto" + }, + "width": { + "name": "ancho" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptyHunyuanImageLatent": { "display_name": "ImagenLatenteHunyuanVacía", "inputs": { @@ -2022,6 +2369,28 @@ } } }, + "EmptyHunyuanVideo15Latent": { + "display_name": "Empty HunyuanVideo 1.5 Latent", + "inputs": { + "batch_size": { + "name": "tamaño_lote" + }, + "height": { + "name": "alto" + }, + "length": { + "name": "longitud" + }, + "width": { + "name": "ancho" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptyImage": { "display_name": "EmptyImage", "inputs": { @@ -2071,6 +2440,11 @@ "seconds": { "name": "segundos" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyLatentHunyuan3Dv2": { @@ -2083,6 +2457,11 @@ "resolution": { "name": "resolución" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyLatentImage": { @@ -2130,6 +2509,28 @@ } } }, + "EmptyQwenImageLayeredLatentImage": { + "display_name": "Empty Qwen Image Layered Latent", + "inputs": { + "batch_size": { + "name": "tamaño_lote" + }, + "height": { + "name": "alto" + }, + "layers": { + "name": "capas" + }, + "width": { + "name": "ancho" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptySD3LatentImage": { "display_name": "EmptySD3LatentImage", "inputs": { @@ -2177,6 +2578,11 @@ "steps": { "name": "pasos" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ExtendIntermediateSigmas": { @@ -2197,6 +2603,11 @@ "steps": { "name": "pasos" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FeatherMask": { @@ -2217,6 +2628,11 @@ "top": { "name": "arriba" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FlipSigmas": { @@ -2225,6 +2641,102 @@ "sigmas": { "name": "sigmas" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2MaxImageNode": { + "description": "Genera imágenes de forma sincrónica según el prompt y la resolución.", + "display_name": "Flux.2 [max] Imagen", + "inputs": { + "control_after_generate": { + "name": "control después de generar" + }, + "height": { + "name": "alto" + }, + "images": { + "name": "imágenes", + "tooltip": "Hasta 9 imágenes que se pueden usar como referencia." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para la generación o edición de la imagen" + }, + "prompt_upsampling": { + "name": "aumento_prompt", + "tooltip": "Indica si se realiza aumento de prompt. Si está activo, modifica automáticamente el prompt para una generación más creativa." + }, + "seed": { + "name": "semilla", + "tooltip": "La semilla aleatoria utilizada para crear el ruido." + }, + "width": { + "name": "ancho" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2ProImageNode": { + "description": "Genera imágenes de forma sincrónica según el prompt y la resolución.", + "display_name": "Flux.2 [pro] Imagen", + "inputs": { + "control_after_generate": { + "name": "control después de generar" + }, + "height": { + "name": "alto" + }, + "images": { + "name": "imágenes", + "tooltip": "Hasta 9 imágenes que se pueden usar como referencia." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para la generación o edición de la imagen" + }, + "prompt_upsampling": { + "name": "aumento_prompt", + "tooltip": "Indica si se realiza aumento de prompt. Si está activo, modifica automáticamente el prompt para una generación más creativa." + }, + "seed": { + "name": "semilla", + "tooltip": "La semilla aleatoria utilizada para crear el ruido." + }, + "width": { + "name": "ancho" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2Scheduler": { + "display_name": "Flux2Scheduler", + "inputs": { + "height": { + "name": "alto" + }, + "steps": { + "name": "pasos" + }, + "width": { + "name": "ancho" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FluxDisableGuidance": { @@ -2547,6 +3059,11 @@ "s2": { "name": "s2" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FreeU_V2": { @@ -2567,6 +3084,11 @@ "s2": { "name": "s2" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "GITSScheduler": { @@ -2625,6 +3147,58 @@ } } }, + "GeminiImage2Node": { + "description": "Genera o edita imágenes de forma sincrónica a través de la API de Google Vertex.", + "display_name": "Nano Banana Pro (Google Gemini Image)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "Si se establece en 'auto', coincide con la relación de aspecto de tu imagen de entrada; si no se proporciona imagen, normalmente se genera un cuadrado 16:9." + }, + "control_after_generate": { + "name": "control after generate" + }, + "files": { + "name": "files", + "tooltip": "Archivo(s) opcional(es) para usar como contexto para el modelo. Acepta entradas del nodo Gemini Generate Content Input Files." + }, + "images": { + "name": "images", + "tooltip": "Imagen(es) de referencia opcional(es). Para incluir varias imágenes, utiliza el nodo Batch Images (hasta 14)." + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "Texto descriptivo de la imagen a generar o de las ediciones a aplicar. Incluye cualquier restricción, estilo o detalle que el modelo deba seguir." + }, + "resolution": { + "name": "resolution", + "tooltip": "Resolución de salida objetivo. Para 2K/4K se utiliza el escalador nativo de Gemini." + }, + "response_modalities": { + "name": "response_modalities", + "tooltip": "Elige 'IMAGE' para solo imagen, o 'IMAGE+TEXT' para devolver tanto la imagen generada como una respuesta de texto." + }, + "seed": { + "name": "seed", + "tooltip": "Cuando la semilla se fija a un valor específico, el modelo intenta proporcionar la misma respuesta en solicitudes repetidas. No se garantiza una salida determinista. Además, cambiar el modelo o los parámetros, como la temperatura, puede causar variaciones en la respuesta incluso usando la misma semilla. Por defecto, se utiliza una semilla aleatoria." + }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "Instrucciones fundamentales que dictan el comportamiento de la IA." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "tooltip": null + } + } + }, "GeminiImageNode": { "description": "Editar imágenes sincrónicamente mediante la API de Google.", "display_name": "Imagen de Google Gemini", @@ -2652,9 +3226,17 @@ "name": "prompt", "tooltip": "Prompt de texto para la generación" }, + "response_modalities": { + "name": "response_modalities", + "tooltip": "Elige 'IMAGE' para solo imagen, o 'IMAGE+TEXT' para devolver tanto la imagen generada como una respuesta de texto." + }, "seed": { "name": "semilla", "tooltip": "Cuando la semilla se fija a un valor específico, el modelo hace el mejor esfuerzo para proporcionar la misma respuesta para solicitudes repetidas. No se garantiza una salida determinista. Además, cambiar el modelo o la configuración de parámetros, como la temperatura, puede causar variaciones en la respuesta incluso cuando se utiliza el mismo valor de semilla. Por defecto, se utiliza un valor de semilla aleatorio." + }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "Instrucciones fundamentales que dictan el comportamiento de la IA." } }, "outputs": { @@ -2716,6 +3298,10 @@ "name": "semilla", "tooltip": "Cuando la semilla se fija a un valor específico, el modelo hace el mejor esfuerzo para proporcionar la misma respuesta para solicitudes repetidas. No se garantiza una salida determinista. Además, cambiar el modelo o la configuración de parámetros, como la temperatura, puede causar variaciones en la respuesta incluso cuando se utiliza el mismo valor de semilla. Por defecto, se utiliza un valor de semilla aleatorio." }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "Instrucciones fundamentales que dictan el comportamiento de la IA." + }, "video": { "name": "video", "tooltip": "Video opcional para usar como contexto para el modelo." @@ -2727,6 +3313,72 @@ } } }, + "GenerateTracks": { + "display_name": "GenerateTracks", + "inputs": { + "bezier": { + "name": "bezier", + "tooltip": "Habilitar trayectoria de curva Bezier usando el punto medio como punto de control." + }, + "end_x": { + "name": "fin_x", + "tooltip": "Coordenada X normalizada (0-1) para la posición final." + }, + "end_y": { + "name": "fin_y", + "tooltip": "Coordenada Y normalizada (0-1) para la posición final." + }, + "height": { + "name": "alto" + }, + "interpolation": { + "name": "interpolación", + "tooltip": "Controla el tiempo/velocidad del movimiento a lo largo de la trayectoria." + }, + "mid_x": { + "name": "medio_x", + "tooltip": "Punto de control X normalizado para la curva Bezier. Solo se usa cuando 'bezier' está habilitado." + }, + "mid_y": { + "name": "medio_y", + "tooltip": "Punto de control Y normalizado para la curva Bezier. Solo se usa cuando 'bezier' está habilitado." + }, + "num_frames": { + "name": "número_de_frames" + }, + "num_tracks": { + "name": "número_de_rutas" + }, + "start_x": { + "name": "inicio_x", + "tooltip": "Coordenada X normalizada (0-1) para la posición inicial." + }, + "start_y": { + "name": "inicio_y", + "tooltip": "Coordenada Y normalizada (0-1) para la posición inicial." + }, + "track_mask": { + "name": "máscara_de_ruta", + "tooltip": "Máscara opcional para indicar los frames visibles." + }, + "track_spread": { + "name": "separación_de_rutas", + "tooltip": "Distancia normalizada entre rutas. Las rutas se distribuyen perpendicularmente a la dirección del movimiento." + }, + "width": { + "name": "ancho" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "longitud_de_ruta", + "tooltip": null + } + } + }, "GetImageSize": { "description": "Devuelve el ancho y alto de la imagen, y la pasa sin cambios.", "display_name": "Obtener Tamaño de Imagen", @@ -2735,17 +3387,17 @@ "name": "imagen" } }, - "outputs": { - "0": { - "name": "ancho" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "alto" + { + "tooltip": null }, - "2": { - "name": "tamaño_lote" + { + "tooltip": null } - } + ] }, "GetVideoComponents": { "description": "Extrae todos los componentes de un video: fotogramas, audio y velocidad de fotogramas.", @@ -2783,6 +3435,11 @@ "tapered_corners": { "name": "esquinas_afiladas" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Hunyuan3Dv2Conditioning": { @@ -2792,14 +3449,14 @@ "name": "salida_vision_clip" } }, - "outputs": { - "0": { - "name": "positivo" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "negativo" + { + "tooltip": null } - } + ] }, "Hunyuan3Dv2ConditioningMultiView": { "display_name": "Hunyuan3Dv2ConditioningMultiView", @@ -2817,14 +3474,14 @@ "name": "derecha" } }, - "outputs": { - "0": { - "name": "positivo" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "negativo" + { + "tooltip": null } - } + ] }, "HunyuanImageToVideo": { "display_name": "HunyuanImageToVideo", @@ -2896,6 +3553,120 @@ } } }, + "HunyuanVideo15ImageToVideo": { + "display_name": "HunyuanVideo15ImageToVideo", + "inputs": { + "batch_size": { + "name": "tamaño_de_lote" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "height": { + "name": "alto" + }, + "length": { + "name": "longitud" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "start_image": { + "name": "imagen_inicial" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "ancho" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latente", + "tooltip": null + } + } + }, + "HunyuanVideo15LatentUpscaleWithModel": { + "display_name": "Hunyuan Video 15 Latent Upscale With Model", + "inputs": { + "crop": { + "name": "recorte" + }, + "height": { + "name": "alto" + }, + "model": { + "name": "modelo" + }, + "samples": { + "name": "muestras" + }, + "upscale_method": { + "name": "método_de_escalado" + }, + "width": { + "name": "ancho" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "HunyuanVideo15SuperResolution": { + "display_name": "HunyuanVideo15SuperResolution", + "inputs": { + "clip_vision_output": { + "name": "clip_vision_output" + }, + "latent": { + "name": "latente" + }, + "negative": { + "name": "negativo" + }, + "noise_augmentation": { + "name": "aumento_de_ruido" + }, + "positive": { + "name": "positivo" + }, + "start_image": { + "name": "imagen_inicial" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latente", + "tooltip": null + } + } + }, "HyperTile": { "display_name": "HyperTile", "inputs": { @@ -3100,6 +3871,11 @@ "strength": { "name": "intensidad" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageBatch": { @@ -3163,6 +3939,26 @@ "image": { "name": "imagen" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageCompare": { + "description": "Compara dos imágenes una al lado de la otra con un control deslizante.", + "display_name": "Comparar Imágenes", + "inputs": { + "compare_view": { + "name": "vista_comparar" + }, + "image_a": { + "name": "imagen_a" + }, + "image_b": { + "name": "imagen_b" + } } }, "ImageCompositeMasked": { @@ -3186,6 +3982,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageCrop": { @@ -3206,6 +4007,30 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageDeduplication": { + "display_name": "Eliminación de Imágenes Duplicadas", + "inputs": { + "images": { + "name": "imágenes", + "tooltip": "Lista de imágenes a procesar." + }, + "similarity_threshold": { + "name": "umbral_de_similitud", + "tooltip": "Umbral de similitud (0-1). Un valor más alto significa mayor similitud. Las imágenes por encima de este umbral se consideran duplicadas." + } + }, + "outputs": { + "0": { + "name": "imágenes", + "tooltip": "Imágenes procesadas" + } } }, "ImageFlip": { @@ -3217,6 +4042,11 @@ "image": { "name": "imagen" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageFromBatch": { @@ -3231,6 +4061,42 @@ "length": { "name": "longitud" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageGrid": { + "display_name": "Cuadrícula de Imágenes", + "inputs": { + "cell_height": { + "name": "alto_de_celda", + "tooltip": "Altura de cada celda en la cuadrícula." + }, + "cell_width": { + "name": "ancho_de_celda", + "tooltip": "Ancho de cada celda en la cuadrícula." + }, + "columns": { + "name": "columnas", + "tooltip": "Número de columnas en la cuadrícula." + }, + "images": { + "name": "imágenes", + "tooltip": "Lista de imágenes a procesar." + }, + "padding": { + "name": "espaciado", + "tooltip": "Espaciado entre imágenes." + } + }, + "outputs": { + "0": { + "name": "imágenes", + "tooltip": "Imágenes procesadas" + } } }, "ImageInvert": { @@ -3339,6 +4205,11 @@ "rotation": { "name": "rotación" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageScale": { @@ -3387,6 +4258,11 @@ "upscale_method": { "name": "método_de_escalado" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageScaleToTotalPixels": { @@ -3398,6 +4274,9 @@ "megapixels": { "name": "megapixeles" }, + "resolution_steps": { + "name": "pasos_de_resolución" + }, "upscale_method": { "name": "metodo_ampliacion" } @@ -3452,6 +4331,11 @@ "spacing_width": { "name": "ancho_espaciado" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageToMask": { @@ -3463,6 +4347,11 @@ "image": { "name": "imagen" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageUpscaleWithModel": { @@ -3572,6 +4461,29 @@ "mask": { "name": "máscara" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "JoinAudioChannels": { + "description": "Une los canales de audio mono izquierdo y derecho en un audio estéreo.", + "display_name": "Unir Canales de Audio", + "inputs": { + "audio_left": { + "name": "audio_izquierdo" + }, + "audio_right": { + "name": "audio_derecho" + } + }, + "outputs": { + "0": { + "name": "audio", + "tooltip": null + } } }, "JoinImageWithAlpha": { @@ -3697,6 +4609,58 @@ "sampler_name": { "name": "nombre_del_muestreador" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Kandinsky5ImageToVideo": { + "display_name": "Kandinsky5ImageToVideo", + "inputs": { + "batch_size": { + "name": "tamaño_lote" + }, + "height": { + "name": "alto" + }, + "length": { + "name": "duración" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "start_image": { + "name": "imagen_inicial" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "ancho" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latente", + "tooltip": "Latente de video vacío" + }, + "3": { + "name": "latente_cond", + "tooltip": "Imágenes iniciales codificadas limpias, usadas para reemplazar el inicio ruidoso de los latentes de salida del modelo" + } } }, "KarrasScheduler": { @@ -3714,6 +4678,11 @@ "steps": { "name": "pasos" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "KlingCameraControlI2VNode": { @@ -3869,7 +4838,6 @@ } }, "KlingImage2VideoNode": { - "description": "Nodo Kling Imagen a Video", "display_name": "Kling Imagen a Video", "inputs": { "aspect_ratio": { @@ -3957,6 +4925,35 @@ } } }, + "KlingImageToVideoWithAudio": { + "display_name": "Kling Imagen (Primer fotograma) a Video con Audio", + "inputs": { + "duration": { + "name": "duración" + }, + "generate_audio": { + "name": "generar_audio" + }, + "mode": { + "name": "modo" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto positivo." + }, + "start_frame": { + "name": "fotograma_inicial" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingLipSyncAudioToVideoNode": { "description": "Nodo de Sincronización Labial Kling de Audio a Video. Sincroniza los movimientos de la boca en un archivo de video con el contenido de audio de un archivo de audio.", "display_name": "Sincronización Labial Kling: Video con Audio", @@ -4018,6 +5015,227 @@ } } }, + "KlingMotionControl": { + "display_name": "Kling Control de Movimiento", + "inputs": { + "character_orientation": { + "name": "orientación_personaje", + "tooltip": "Controla de dónde proviene la orientación/posición del personaje.\nvideo: movimientos, expresiones, movimientos de cámara y orientación siguen el video de referencia de movimiento (otros detalles vía prompt).\nimagen: los movimientos y expresiones siguen el video de referencia de movimiento, pero la orientación del personaje coincide con la imagen de referencia (cámara/otros detalles vía prompt)." + }, + "keep_original_sound": { + "name": "mantener_sonido_original" + }, + "mode": { + "name": "modo" + }, + "prompt": { + "name": "prompt" + }, + "reference_image": { + "name": "imagen_referencia" + }, + "reference_video": { + "name": "video_referencia", + "tooltip": "Video de referencia de movimiento usado para guiar el movimiento/expresión.\nLos límites de duración dependen de character_orientation:\n - imagen: 3–10s (máx 10s)\n - video: 3–30s (máx 30s)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProEditVideoNode": { + "description": "Edita un video existente con el modelo más reciente de Kling.", + "display_name": "Kling Omni Editar Video (Pro)", + "inputs": { + "keep_original_sound": { + "name": "mantener_sonido_original" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Un prompt de texto que describe el contenido del video. Puede incluir descripciones positivas y negativas." + }, + "reference_images": { + "name": "imágenes_referencia", + "tooltip": "Hasta 4 imágenes de referencia adicionales." + }, + "resolution": { + "name": "resolución" + }, + "video": { + "name": "video", + "tooltip": "Video para editar. La longitud del video de salida será la misma." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProFirstLastFrameNode": { + "description": "Utiliza un fotograma inicial, un fotograma final opcional o imágenes de referencia con el último modelo de Kling.", + "display_name": "Kling Omni Primer-Último-Frame a Video (Pro)", + "inputs": { + "duration": { + "name": "duration" + }, + "end_frame": { + "name": "end_frame", + "tooltip": "Un fotograma final opcional para el video. No se puede usar simultáneamente con 'reference_images'." + }, + "first_frame": { + "name": "first_frame" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Un prompt de texto que describe el contenido del video. Puede incluir descripciones tanto positivas como negativas." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "Hasta 6 imágenes de referencia adicionales." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageNode": { + "description": "Crea o edita imágenes con el último modelo de Kling.", + "display_name": "Kling Omni Imagen (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Un prompt de texto que describe el contenido de la imagen. Puede incluir descripciones tanto positivas como negativas." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "Hasta 10 imágenes de referencia adicionales." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageToVideoNode": { + "description": "Utiliza hasta 7 imágenes de referencia para generar un video con el último modelo de Kling.", + "display_name": "Kling Omni Imagen a Video (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Un prompt de texto que describe el contenido del video. Puede incluir descripciones tanto positivas como negativas." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "Hasta 7 imágenes de referencia." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProTextToVideoNode": { + "description": "Utiliza prompts de texto para generar videos con el último modelo de Kling.", + "display_name": "Kling Omni Texto a Video (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Un prompt de texto que describe el contenido del video. Puede incluir descripciones tanto positivas como negativas." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProVideoToVideoNode": { + "description": "Utiliza un video y hasta 4 imágenes de referencia para generar un video con el modelo Kling más reciente.", + "display_name": "Kling Omni Video a Video (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "keep_original_sound": { + "name": "keep_original_sound" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Un prompt de texto que describe el contenido del video. Puede incluir descripciones tanto positivas como negativas." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "Hasta 4 imágenes de referencia adicionales." + }, + "reference_video": { + "name": "reference_video", + "tooltip": "Video a usar como referencia." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingSingleImageVideoEffectNode": { "description": "Logra diferentes efectos especiales al generar un video basado en el effect_scene.", "display_name": "Efectos de Video Kling", @@ -4132,6 +5350,35 @@ } } }, + "KlingTextToVideoWithAudio": { + "display_name": "Kling Texto a Video con Audio", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "generate_audio": { + "name": "generate_audio" + }, + "mode": { + "name": "mode" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto positivo." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingVideoExtendNode": { "description": "Nodo Kling Video Extend. Extiende videos creados por otros nodos Kling. El video_id se crea utilizando otros nodos Kling.", "display_name": "Kling Video Extend", @@ -4186,6 +5433,26 @@ } } }, + "LTXAVTextEncoderLoader": { + "description": "[Recetas]\n\nltxav: gemma 3 12B", + "display_name": "Cargador de codificador de texto LTXV Audio", + "inputs": { + "ckpt_name": { + "name": "ckpt_name" + }, + "device": { + "name": "device" + }, + "text_encoder": { + "name": "text_encoder" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LTXVAddGuide": { "display_name": "LTXVAddGuide", "inputs": { @@ -4228,6 +5495,76 @@ } } }, + "LTXVAudioVAEDecode": { + "display_name": "Decodificar LTXV Audio VAE", + "inputs": { + "audio_vae": { + "name": "audio_vae", + "tooltip": "El modelo Audio VAE utilizado para decodificar el latente." + }, + "samples": { + "name": "samples", + "tooltip": "El latente que se va a decodificar." + } + }, + "outputs": { + "0": { + "name": "Audio", + "tooltip": null + } + } + }, + "LTXVAudioVAEEncode": { + "display_name": "Codificar LTXV Audio VAE", + "inputs": { + "audio": { + "name": "audio", + "tooltip": "El audio que se va a codificar." + }, + "audio_vae": { + "name": "audio_vae", + "tooltip": "El modelo Audio VAE a utilizar para la codificación." + } + }, + "outputs": { + "0": { + "name": "Audio Latent", + "tooltip": null + } + } + }, + "LTXVAudioVAELoader": { + "display_name": "Cargador de LTXV Audio VAE", + "inputs": { + "ckpt_name": { + "name": "ckpt_name", + "tooltip": "Punto de control de Audio VAE a cargar." + } + }, + "outputs": { + "0": { + "name": "Audio VAE", + "tooltip": null + } + } + }, + "LTXVConcatAVLatent": { + "display_name": "LTXVConcatAVLatent", + "inputs": { + "audio_latent": { + "name": "audio_latent" + }, + "video_latent": { + "name": "video_latent" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, "LTXVConditioning": { "display_name": "LTXVConditioning", "inputs": { @@ -4280,6 +5617,33 @@ } } }, + "LTXVEmptyLatentAudio": { + "display_name": "LTXV Audio Latente Vacío", + "inputs": { + "audio_vae": { + "name": "audio_vae", + "tooltip": "El modelo Audio VAE del que obtener la configuración." + }, + "batch_size": { + "name": "batch_size", + "tooltip": "El número de muestras de audio latente en el lote." + }, + "frame_rate": { + "name": "frame_rate", + "tooltip": "Número de fotogramas por segundo." + }, + "frames_number": { + "name": "frames_number", + "tooltip": "Número de fotogramas." + } + }, + "outputs": { + "0": { + "name": "Latent", + "tooltip": null + } + } + }, "LTXVImgToVideo": { "display_name": "LTXVImgToVideo", "inputs": { @@ -4326,6 +5690,47 @@ } } }, + "LTXVImgToVideoInplace": { + "display_name": "LTXVImgToVideoInplace", + "inputs": { + "bypass": { + "name": "omitir", + "tooltip": "Omitir el acondicionamiento." + }, + "image": { + "name": "imagen" + }, + "latent": { + "name": "latente" + }, + "strength": { + "name": "fuerza" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "latente", + "tooltip": null + } + } + }, + "LTXVLatentUpsampler": { + "display_name": "LTXVLatentUpsampler", + "inputs": { + "samples": { + "name": "muestras" + }, + "upscale_model": { + "name": "modelo_de_escalado" + }, + "vae": { + "name": "vae" + } + } + }, "LTXVPreprocess": { "display_name": "LTXVPreprocesar", "inputs": { @@ -4374,6 +5779,25 @@ } } }, + "LTXVSeparateAVLatent": { + "description": "LTXV Separar AV Latente", + "display_name": "LTXVSeparateAVLatent", + "inputs": { + "av_latent": { + "name": "av_latente" + } + }, + "outputs": { + "0": { + "name": "latente_video", + "tooltip": null + }, + "1": { + "name": "latente_audio", + "tooltip": null + } + } + }, "LaplaceScheduler": { "display_name": "LaplaceScheduler", "inputs": { @@ -4392,6 +5816,11 @@ "steps": { "name": "pasos" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LatentAdd": { @@ -4529,6 +5958,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LatentConcat": { @@ -4592,6 +6026,25 @@ } } }, + "LatentCutToBatch": { + "display_name": "LatentCutToBatch", + "inputs": { + "dim": { + "name": "dim" + }, + "samples": { + "name": "samples" + }, + "slice_size": { + "name": "slice_size" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LatentFlip": { "display_name": "Voltear Latente", "inputs": { @@ -4745,6 +6198,19 @@ } } }, + "LatentUpscaleModelLoader": { + "display_name": "Cargar modelo de escalado Latent", + "inputs": { + "model_name": { + "name": "model_name" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LazyCache": { "description": "Una versión casera de EasyCache - versión 'más fácil' de EasyCache para implementar. En general funciona peor que EasyCache, pero mejor en algunos casos raros Y compatibilidad universal con todo en ComfyUI.", "display_name": "CachéPerezoso", @@ -4792,70 +6258,32 @@ }, "upload 3d model": { }, - "width": { - "name": "ancho" - } - }, - "outputs": { - "0": { - "name": "imagen" - }, - "1": { - "name": "mask" - }, - "2": { - "name": "ruta_malla" - }, - "3": { - "name": "normal" - }, - "4": { - "name": "lineart" - }, - "5": { - "name": "info_cámara" - }, - "6": { - "name": "grabando_video" - } - } - }, - "Load3DAnimation": { - "display_name": "Cargar 3D - Animación", - "inputs": { - "height": { - "name": "alto" - }, - "image": { - "name": "imagen" - }, - "model_file": { - "name": "archivo_modelo" + "upload extra resources": { }, "width": { "name": "ancho" } }, - "outputs": { - "0": { - "name": "imagen" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "mask" + { + "tooltip": null }, - "2": { - "name": "ruta_malla" + { + "tooltip": null }, - "3": { - "name": "normal" + { + "tooltip": null }, - "4": { - "name": "info_cámara" + { + "tooltip": null }, - "5": { - "name": "grabando_video" + { + "tooltip": null } - } + ] }, "LoadAudio": { "display_name": "CargarAudio", @@ -4869,6 +6297,11 @@ "upload": { "name": "elige archivo para subir" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LoadImage": { @@ -4882,6 +6315,21 @@ } } }, + "LoadImageDataSetFromFolder": { + "display_name": "Cargar conjunto de imágenes desde carpeta", + "inputs": { + "folder": { + "name": "folder", + "tooltip": "La carpeta desde la que cargar las imágenes." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Lista de imágenes cargadas" + } + } + }, "LoadImageMask": { "display_name": "Cargar Imagen (como Máscara)", "inputs": { @@ -4900,6 +6348,8 @@ "description": "Carga una imagen desde la carpeta de salida. Cuando se hace clic en el botón de actualizar, el nodo actualizará la lista de imágenes y seleccionará automáticamente la primera imagen, permitiendo una fácil iteración.", "display_name": "Cargar Imagen (desde Salidas)", "inputs": { + "Auto-refresh after generation": { + }, "image": { "name": "imagen" }, @@ -4910,41 +6360,22 @@ } } }, - "LoadImageSetFromFolderNode": { - "description": "Carga un lote de imágenes desde un directorio para entrenamiento.", - "display_name": "Cargar Conjunto de Imágenes desde Carpeta", + "LoadImageTextDataSetFromFolder": { + "display_name": "Cargar conjunto de imágenes y texto desde carpeta", "inputs": { "folder": { - "name": "carpeta", - "tooltip": "La carpeta desde la que cargar imágenes." - }, - "resize_method": { - "name": "método_redimensionado" + "name": "folder", + "tooltip": "La carpeta desde la que cargar las imágenes." } - } - }, - "LoadImageTextSetFromFolderNode": { - "description": "Carga un lote de imágenes y descripciones desde un directorio para entrenamiento.", - "display_name": "Cargar Conjunto de Imágenes y Texto desde Carpeta", - "inputs": { - "clip": { - "name": "clip", - "tooltip": "El modelo CLIP utilizado para codificar el texto." + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Lista de imágenes cargadas" }, - "folder": { - "name": "carpeta", - "tooltip": "La carpeta desde la cual cargar las imágenes." - }, - "height": { - "name": "altura", - "tooltip": "La altura a la que redimensionar las imágenes. -1 significa usar la altura original." - }, - "resize_method": { - "name": "método_de_redimensionamiento" - }, - "width": { - "name": "ancho", - "tooltip": "El ancho al que redimensionar las imágenes. -1 significa usar el ancho original." + "1": { + "name": "texts", + "tooltip": "Lista de subtítulos de texto" } } }, @@ -4956,6 +6387,25 @@ } } }, + "LoadTrainingDataset": { + "display_name": "Cargar conjunto de datos de entrenamiento", + "inputs": { + "folder_name": { + "name": "folder_name", + "tooltip": "Nombre de la carpeta que contiene el conjunto de datos guardado (dentro del directorio de salida)." + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "Lista de diccionarios de latentes" + }, + "1": { + "name": "conditioning", + "tooltip": "Lista de listas de condicionamiento" + } + } + }, "LoadVideo": { "display_name": "Cargar video", "inputs": { @@ -5027,7 +6477,6 @@ } }, "LoraModelLoader": { - "description": "Carga pesos LoRA entrenados desde el nodo Entrenar LoRA.", "display_name": "Cargar Modelo LoRA", "inputs": { "lora": { @@ -5043,11 +6492,11 @@ "tooltip": "Qué tan fuerte modificar el modelo de difusión. Este valor puede ser negativo." } }, - "outputs": { - "0": { - "tooltip": "El modelo de difusión modificado." + "outputs": [ + { + "name": "model" } - } + ] }, "LoraSave": { "display_name": "Extraer y Guardar Lora", @@ -5075,14 +6524,15 @@ } }, "LossGraphNode": { - "description": "Grafica la pérdida y la guarda en el directorio de salida.", "display_name": "Graficar Pérdida", "inputs": { "filename_prefix": { - "name": "prefijo_nombre_archivo" + "name": "prefijo_nombre_archivo", + "tooltip": "Prefijo para la imagen del gráfico de pérdida guardada." }, "loss": { - "name": "pérdida" + "name": "pérdida", + "tooltip": "Mapa de pérdida del nodo de entrenamiento." } } }, @@ -5388,6 +6838,50 @@ } } }, + "MakeTrainingDataset": { + "display_name": "Crear Conjunto de Datos de Entrenamiento", + "inputs": { + "clip": { + "name": "clip", + "tooltip": "Modelo CLIP para codificar texto a acondicionamiento." + }, + "images": { + "name": "imágenes", + "tooltip": "Lista de imágenes para codificar." + }, + "texts": { + "name": "textos", + "tooltip": "Lista de subtítulos de texto. Puede ser de longitud n (coincidiendo con imágenes), 1 (repetido para todos), o omitido (usa cadena vacía)." + }, + "vae": { + "name": "vae", + "tooltip": "Modelo VAE para codificar imágenes a latentes." + } + }, + "outputs": { + "0": { + "name": "latentes", + "tooltip": "Lista de diccionarios latentes" + }, + "1": { + "name": "acondicionamiento", + "tooltip": "Lista de listas de acondicionamiento" + } + } + }, + "ManualSigmas": { + "display_name": "ManualSigmas", + "inputs": { + "sigmas": { + "name": "sigmas" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "MaskComposite": { "display_name": "Composición de Máscara", "inputs": { @@ -5406,6 +6900,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "MaskPreview": { @@ -5423,6 +6922,315 @@ "mask": { "name": "máscara" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MergeImageLists": { + "display_name": "Unir Listas de Imágenes", + "inputs": { + "images": { + "name": "imágenes", + "tooltip": "Lista de imágenes a procesar." + } + }, + "outputs": { + "0": { + "name": "imágenes", + "tooltip": "Imágenes procesadas" + } + } + }, + "MergeTextLists": { + "display_name": "Unir Listas de Textos", + "inputs": { + "texts": { + "name": "textos", + "tooltip": "Lista de textos a procesar." + } + }, + "outputs": { + "0": { + "name": "textos", + "tooltip": "Textos procesados" + } + } + }, + "MeshyAnimateModelNode": { + "description": "Aplica una acción de animación específica a un personaje previamente riggeado.", + "display_name": "Meshy: Animar Modelo", + "inputs": { + "action_id": { + "name": "action_id", + "tooltip": "Visita https://docs.meshy.ai/en/api/animation-library para ver una lista de valores disponibles." + }, + "rig_task_id": { + "name": "rig_task_id" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + } + } + }, + "MeshyImageToModelNode": { + "display_name": "Meshy: Imagen a Modelo", + "inputs": { + "control_after_generate": { + "name": "controlar después de generar" + }, + "image": { + "name": "imagen" + }, + "model": { + "name": "modelo" + }, + "pose_mode": { + "name": "modo de pose", + "tooltip": "Especifica el modo de pose para el modelo generado." + }, + "seed": { + "name": "semilla", + "tooltip": "La semilla controla si el nodo debe volver a ejecutarse; los resultados son no deterministas independientemente de la semilla." + }, + "should_remesh": { + "name": "remallar", + "tooltip": "Si se establece en falso, devuelve una malla triangular sin procesar." + }, + "should_remesh_target_polycount": { + "name": "recuento de polígonos objetivo" + }, + "should_remesh_topology": { + "name": "topología" + }, + "should_texture": { + "name": "texturizar", + "tooltip": "Determina si se generan texturas. Si se establece en falso, se omite la fase de texturizado y se devuelve una malla sin texturas." + }, + "should_texture_enable_pbr": { + "name": "habilitar PBR" + }, + "should_texture_texture_prompt": { + "name": "prompt de textura" + }, + "symmetry_mode": { + "name": "modo de simetría" + } + }, + "outputs": { + "0": { + "name": "archivo_modelo", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyMultiImageToModelNode": { + "display_name": "Meshy: Multi-Imagen a Modelo", + "inputs": { + "control_after_generate": { + "name": "controlar después de generar" + }, + "images": { + "name": "imágenes" + }, + "model": { + "name": "modelo" + }, + "pose_mode": { + "name": "modo de pose", + "tooltip": "Especifica el modo de pose para el modelo generado." + }, + "seed": { + "name": "semilla", + "tooltip": "La semilla controla si el nodo debe volver a ejecutarse; los resultados son no deterministas independientemente de la semilla." + }, + "should_remesh": { + "name": "remallar", + "tooltip": "Si se establece en falso, devuelve una malla triangular sin procesar." + }, + "should_remesh_target_polycount": { + "name": "recuento de polígonos objetivo" + }, + "should_remesh_topology": { + "name": "topología" + }, + "should_texture": { + "name": "texturizar", + "tooltip": "Determina si se generan texturas. Si se establece en falso, se omite la fase de texturizado y se devuelve una malla sin texturas." + }, + "should_texture_enable_pbr": { + "name": "habilitar PBR" + }, + "should_texture_texture_prompt": { + "name": "prompt de textura" + }, + "symmetry_mode": { + "name": "modo de simetría" + } + }, + "outputs": { + "0": { + "name": "archivo_modelo", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRefineNode": { + "description": "Refina un modelo borrador previamente creado.", + "display_name": "Meshy: Refinar Modelo Borrador", + "inputs": { + "enable_pbr": { + "name": "habilitar_pbr", + "tooltip": "Generar mapas PBR (metálico, rugosidad, normal) además del color base. Nota: esto debe estar en falso al usar el estilo Sculpture, ya que el estilo Sculpture genera su propio conjunto de mapas PBR." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "model": { + "name": "modelo" + }, + "texture_image": { + "name": "imagen_de_textura", + "tooltip": "Solo se puede usar uno de 'texture_image' o 'texture_prompt' al mismo tiempo." + }, + "texture_prompt": { + "name": "texto_de_textura", + "tooltip": "Proporcione un texto para guiar el proceso de texturizado. Máximo 600 caracteres. No se puede usar al mismo tiempo que 'texture_image'." + } + }, + "outputs": { + "0": { + "name": "archivo_modelo", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRigModelNode": { + "description": "Proporciona un personaje riggeado en formatos estándar. El auto-rigging actualmente no es adecuado para mallas sin textura, activos no humanoides o activos humanoides con estructura de extremidades y cuerpo poco clara.", + "display_name": "Meshy: Riggear Modelo", + "inputs": { + "height_meters": { + "name": "altura_metros", + "tooltip": "La altura aproximada del modelo de personaje en metros. Esto ayuda a la precisión de la escala y el rigging." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "texture_image": { + "name": "imagen_de_textura", + "tooltip": "Imagen de textura de color base UV-desplegada del modelo." + } + }, + "outputs": { + "0": { + "name": "archivo_modelo", + "tooltip": null + }, + "1": { + "name": "rig_task_id", + "tooltip": null + } + } + }, + "MeshyTextToModelNode": { + "display_name": "Meshy: Texto a Modelo", + "inputs": { + "control_after_generate": { + "name": "controlar después de generar" + }, + "model": { + "name": "modelo" + }, + "pose_mode": { + "name": "modo_pose", + "tooltip": "Especifique el modo de pose para el modelo generado." + }, + "prompt": { + "name": "prompt" + }, + "seed": { + "name": "semilla", + "tooltip": "La semilla controla si el nodo debe volver a ejecutarse; los resultados no son deterministas independientemente de la semilla." + }, + "should_remesh": { + "name": "debe_remallar", + "tooltip": "Si está en falso, devuelve una malla triangular sin procesar." + }, + "should_remesh_target_polycount": { + "name": "poligonaje_objetivo" + }, + "should_remesh_topology": { + "name": "topología" + }, + "style": { + "name": "estilo" + }, + "symmetry_mode": { + "name": "modo_simetría" + } + }, + "outputs": { + "0": { + "name": "archivo_modelo", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyTextureNode": { + "display_name": "Meshy: Modelo de Textura", + "inputs": { + "enable_original_uv": { + "name": "habilitar_uv_original", + "tooltip": "Usa el UV original del modelo en lugar de generar nuevos UVs. Cuando está activado, Meshy conserva las texturas existentes del modelo cargado. Si el modelo no tiene UV original, la calidad del resultado podría no ser tan buena." + }, + "image_style": { + "name": "estilo_imagen", + "tooltip": "Una imagen 2D para guiar el proceso de texturizado. No se puede usar al mismo tiempo que 'text_style_prompt'." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "model": { + "name": "modelo" + }, + "pbr": { + "name": "pbr" + }, + "text_style_prompt": { + "name": "estilo_texto", + "tooltip": "Describe el estilo de textura deseado para el objeto usando texto. Máximo 600 caracteres. No se puede usar al mismo tiempo que 'image_style'." + } + }, + "outputs": { + "0": { + "name": "archivo_modelo", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } } }, "MinimaxHailuoVideoNode": { @@ -7866,6 +9674,52 @@ } } }, + "NormalizeImages": { + "display_name": "Normalizar Imágenes", + "inputs": { + "images": { + "name": "imágenes", + "tooltip": "Imagen a procesar." + }, + "mean": { + "name": "media", + "tooltip": "Valor medio para la normalización." + }, + "std": { + "name": "desviación estándar", + "tooltip": "Desviación estándar para la normalización." + } + }, + "outputs": { + "0": { + "name": "imágenes", + "tooltip": "Imágenes procesadas" + } + } + }, + "NormalizeVideoLatentStart": { + "description": "Normaliza los fotogramas iniciales de un video latent para que coincidan con la media y la desviación estándar de los fotogramas de referencia posteriores. Ayuda a reducir las diferencias entre los primeros fotogramas y el resto del video.", + "display_name": "NormalizeVideoLatentStart", + "inputs": { + "latent": { + "name": "latent" + }, + "reference_frame_count": { + "name": "reference_frame_count", + "tooltip": "Número de fotogramas latent después de los iniciales que se usarán como referencia" + }, + "start_frame_count": { + "name": "start_frame_count", + "tooltip": "Número de fotogramas latent a normalizar, contados desde el inicio" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, "OpenAIChatConfig": { "description": "Permite especificar opciones de configuración avanzada para los Nodos de Chat de OpenAI.", "display_name": "Opciones Avanzadas de OpenAI ChatGPT", @@ -8015,6 +9869,9 @@ "name": "mask", "tooltip": "Máscara opcional para inpainting (las áreas blancas serán reemplazadas)" }, + "model": { + "name": "model" + }, "n": { "name": "n", "tooltip": "Cuántas imágenes generar" @@ -8373,265 +10230,6 @@ } } }, - "PikaImageToVideoNode2_2": { - "description": "Envía una imagen y un prompt a la API de Pika v2.2 para generar un video.", - "display_name": "Pika Imagen a Video", - "inputs": { - "control_after_generate": { - "name": "control después de generar" - }, - "duration": { - "name": "duración" - }, - "image": { - "name": "imagen", - "tooltip": "La imagen a convertir en video" - }, - "negative_prompt": { - "name": "prompt negativo" - }, - "prompt_text": { - "name": "texto del prompt" - }, - "resolution": { - "name": "resolución" - }, - "seed": { - "name": "semilla" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaScenesV2_2": { - "description": "Combina tus imágenes para crear un video con los objetos que contienen. Sube varias imágenes como ingredientes y genera un video de alta calidad que las incorpore todas.", - "display_name": "Pika Scenes (Composición de Imágenes en Video)", - "inputs": { - "aspect_ratio": { - "name": "aspect_ratio", - "tooltip": "Relación de aspecto (ancho / alto)" - }, - "control_after_generate": { - "name": "control after generate" - }, - "duration": { - "name": "duration" - }, - "image_ingredient_1": { - "name": "image_ingredient_1", - "tooltip": "Imagen que se usará como ingrediente para crear un video." - }, - "image_ingredient_2": { - "name": "image_ingredient_2", - "tooltip": "Imagen que se usará como ingrediente para crear un video." - }, - "image_ingredient_3": { - "name": "image_ingredient_3", - "tooltip": "Imagen que se usará como ingrediente para crear un video." - }, - "image_ingredient_4": { - "name": "image_ingredient_4", - "tooltip": "Imagen que se usará como ingrediente para crear un video." - }, - "image_ingredient_5": { - "name": "image_ingredient_5", - "tooltip": "Imagen que se usará como ingrediente para crear un video." - }, - "ingredients_mode": { - "name": "ingredients_mode" - }, - "negative_prompt": { - "name": "negative_prompt" - }, - "prompt_text": { - "name": "prompt_text" - }, - "resolution": { - "name": "resolution" - }, - "seed": { - "name": "seed" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaStartEndFrameNode2_2": { - "description": "Genera un video combinando tu primer y último fotograma. Sube dos imágenes para definir los puntos de inicio y fin, y deja que la IA cree una transición suave entre ellas.", - "display_name": "Pika: Fotograma Inicial y Final a Video", - "inputs": { - "control_after_generate": { - "name": "control después de generar" - }, - "duration": { - "name": "duración" - }, - "image_end": { - "name": "imagen_final", - "tooltip": "La última imagen a combinar." - }, - "image_start": { - "name": "imagen_inicial", - "tooltip": "La primera imagen a combinar." - }, - "negative_prompt": { - "name": "prompt_negativo" - }, - "prompt_text": { - "name": "texto_de_prompt" - }, - "resolution": { - "name": "resolución" - }, - "seed": { - "name": "semilla" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaTextToVideoNode2_2": { - "description": "Envía un prompt de texto a la API de Pika v2.2 para generar un video.", - "display_name": "Pika Texto a Video", - "inputs": { - "aspect_ratio": { - "name": "relación de aspecto", - "tooltip": "Relación de aspecto (ancho / alto)" - }, - "control_after_generate": { - "name": "controlar después de generar" - }, - "duration": { - "name": "duración" - }, - "negative_prompt": { - "name": "prompt negativo" - }, - "prompt_text": { - "name": "texto del prompt" - }, - "resolution": { - "name": "resolución" - }, - "seed": { - "name": "semilla" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikadditions": { - "description": "Agrega cualquier objeto o imagen a tu video. Sube un video y especifica lo que deseas añadir para crear un resultado perfectamente integrado.", - "display_name": "Pikadditions (Inserción de Objetos en Video)", - "inputs": { - "control_after_generate": { - "name": "control después de generar" - }, - "image": { - "name": "imagen", - "tooltip": "La imagen que se añadirá al video." - }, - "negative_prompt": { - "name": "indicación negativa" - }, - "prompt_text": { - "name": "texto de indicación" - }, - "seed": { - "name": "semilla" - }, - "video": { - "name": "video", - "tooltip": "El video al que se añadirá una imagen." - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikaffects": { - "description": "Genera un video con un Pikaffect específico. Pikaffects soportados: Cake-ify, Crumble, Crush, Decapitate, Deflate, Dissolve, Explode, Eye-pop, Inflate, Levitate, Melt, Peel, Poke, Squish, Ta-da, Tear", - "display_name": "Pikaffects (Efectos de Video)", - "inputs": { - "control_after_generate": { - "name": "control después de generar" - }, - "image": { - "name": "imagen", - "tooltip": "La imagen de referencia a la que se aplicará el Pikaffect." - }, - "negative_prompt": { - "name": "prompt negativo" - }, - "pikaffect": { - "name": "pikaffect" - }, - "prompt_text": { - "name": "texto de prompt" - }, - "seed": { - "name": "semilla" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikaswaps": { - "description": "Sustituye cualquier objeto o región de tu video con una nueva imagen u objeto. Define las áreas a reemplazar usando una máscara o coordenadas.", - "display_name": "Pika Swaps (Reemplazo de Objetos en Video)", - "inputs": { - "control_after_generate": { - "name": "control después de generar" - }, - "image": { - "name": "imagen", - "tooltip": "La imagen utilizada para reemplazar el objeto enmascarado en el video." - }, - "mask": { - "name": "máscara", - "tooltip": "Usa la máscara para definir las áreas del video a reemplazar" - }, - "negative_prompt": { - "name": "prompt negativo" - }, - "prompt_text": { - "name": "texto de prompt" - }, - "region_to_modify": { - "name": "región_a_modificar", - "tooltip": "Descripción en texto plano del objeto/región a modificar." - }, - "seed": { - "name": "semilla" - }, - "video": { - "name": "video", - "tooltip": "El video en el que se va a intercambiar un objeto." - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, "PixverseImageToVideoNode": { "description": "Genera videos de forma sincrónica según el prompt y el tamaño de salida.", "display_name": "PixVerse Imagen a Video", @@ -8786,6 +10384,11 @@ "steps": { "name": "pasos" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "PorterDuffImageComposite": { @@ -8819,6 +10422,9 @@ "Preview3D": { "display_name": "Vista previa 3D", "inputs": { + "bg_image": { + "name": "bg_image" + }, "camera_info": { "name": "camera_info" }, @@ -8830,22 +10436,13 @@ } } }, - "Preview3DAnimation": { - "display_name": "Vista previa 3D - Animación", - "inputs": { - "camera_info": { - "name": "camera_info" - }, - "model_file": { - "name": "archivo_modelo" - } - } - }, "PreviewAny": { "display_name": "Vista previa de cualquier", "inputs": { "preview": { }, + "previewMode": { + }, "source": { "name": "fuente" } @@ -8985,6 +10582,36 @@ } } }, + "RandomCropImages": { + "display_name": "Recorte Aleatorio de Imágenes", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "height": { + "name": "height", + "tooltip": "Alto del recorte." + }, + "images": { + "name": "images", + "tooltip": "Imagen a procesar." + }, + "seed": { + "name": "seed", + "tooltip": "Semilla aleatoria." + }, + "width": { + "name": "width", + "tooltip": "Ancho del recorte." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Imágenes procesadas" + } + } + }, "RandomNoise": { "display_name": "Ruido aleatorio", "inputs": { @@ -8994,6 +10621,11 @@ "noise_seed": { "name": "semilla_ruido" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RebatchImages": { @@ -9034,6 +10666,11 @@ "audio": { "name": "audio" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RecraftColorRGB": { @@ -9538,6 +11175,11 @@ "image": { "name": "imagen" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RepeatLatentBatch": { @@ -9551,6 +11193,51 @@ } } }, + "ReplaceText": { + "display_name": "Reemplazar Texto", + "inputs": { + "find": { + "name": "find", + "tooltip": "Texto a buscar." + }, + "replace": { + "name": "replace", + "tooltip": "Texto con el que reemplazar." + }, + "texts": { + "name": "texts", + "tooltip": "Texto a procesar." + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "Textos procesados" + } + } + }, + "ReplaceVideoLatentFrames": { + "display_name": "ReplaceVideoLatentFrames", + "inputs": { + "destination": { + "name": "destination", + "tooltip": "El latent de destino donde se reemplazarán los fotogramas." + }, + "index": { + "name": "index", + "tooltip": "El índice del fotograma latent inicial en el latent de destino donde se colocarán los fotogramas del latent de origen. Los valores negativos cuentan desde el final." + }, + "source": { + "name": "source", + "tooltip": "El latent de origen que proporciona los fotogramas para insertar en el latent de destino. Si no se proporciona, el latent de destino se devuelve sin cambios." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "RescaleCFG": { "display_name": "ReescalarCFG", "inputs": { @@ -9580,6 +11267,104 @@ "target_width": { "name": "ancho_objetivo" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ResizeImageMaskNode": { + "description": "Redimensiona una imagen o máscara utilizando varios métodos de escalado.", + "display_name": "Redimensionar Imagen/Máscara", + "inputs": { + "input": { + "name": "input" + }, + "resize_type": { + "name": "resize_type", + "tooltip": "Selecciona cómo redimensionar: por dimensiones exactas, factor de escala, igualando otra imagen, etc." + }, + "resize_type_crop": { + "name": "recortar" + }, + "resize_type_height": { + "name": "altura" + }, + "resize_type_width": { + "name": "anchura" + }, + "scale_method": { + "name": "scale_method", + "tooltip": "Algoritmo de interpolación. 'area' es mejor para reducir tamaño, 'lanczos' para aumentar tamaño, 'nearest-exact' para pixel art." + } + }, + "outputs": { + "0": { + "name": "resized", + "tooltip": null + } + } + }, + "ResizeImagesByLongerEdge": { + "display_name": "Redimensionar imágenes por el borde más largo", + "inputs": { + "images": { + "name": "imágenes", + "tooltip": "Imagen a procesar." + }, + "longer_edge": { + "name": "borde_más_largo", + "tooltip": "Longitud objetivo para el borde más largo." + } + }, + "outputs": { + "0": { + "name": "imágenes", + "tooltip": "Imágenes procesadas" + } + } + }, + "ResizeImagesByShorterEdge": { + "display_name": "Redimensionar imágenes por el borde más corto", + "inputs": { + "images": { + "name": "imágenes", + "tooltip": "Imagen a procesar." + }, + "shorter_edge": { + "name": "borde_más_corto", + "tooltip": "Longitud objetivo para el borde más corto." + } + }, + "outputs": { + "0": { + "name": "imágenes", + "tooltip": "Imágenes procesadas" + } + } + }, + "ResolutionBucket": { + "display_name": "Agrupación por resolución", + "inputs": { + "conditioning": { + "name": "condicionamiento", + "tooltip": "Lista de listas de condicionamiento (debe coincidir con la longitud de latentes)." + }, + "latents": { + "name": "latentes", + "tooltip": "Lista de diccionarios de latent para agrupar por resolución." + } + }, + "outputs": { + "0": { + "name": "latentes", + "tooltip": "Lista de diccionarios de latent agrupados, uno por cada grupo de resolución." + }, + "1": { + "name": "condicionamiento", + "tooltip": "Lista de listas de condición, una por cada grupo de resolución." + } } }, "Rodin3D_Detail": { @@ -9833,6 +11618,11 @@ "steps": { "name": "pasos" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SD_4XUpscale_Conditioning": { @@ -9986,14 +11776,14 @@ "name": "sigmas" } }, - "outputs": { - "0": { - "name": "salida" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "salida_denoisada" + { + "tooltip": null } - } + ] }, "SamplerCustomAdvanced": { "display_name": "SamplerCustomAdvanced", @@ -10014,14 +11804,14 @@ "name": "sigmas" } }, - "outputs": { - "0": { - "name": "salida" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "salida_denoised" + { + "tooltip": null } - } + ] }, "SamplerDPMAdaptative": { "display_name": "SamplerDPMAdaptative", @@ -10056,6 +11846,11 @@ "s_noise": { "name": "s_ruido" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_2M_SDE": { @@ -10073,6 +11868,11 @@ "solver_type": { "name": "tipo_resolvedor" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_2S_Ancestral": { @@ -10084,6 +11884,11 @@ "s_noise": { "name": "s_ruido" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_3M_SDE": { @@ -10098,6 +11903,11 @@ "s_noise": { "name": "s_ruido" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_SDE": { @@ -10115,6 +11925,11 @@ "s_noise": { "name": "s_ruido" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerER_SDE": { @@ -10133,6 +11948,11 @@ "solver_type": { "name": "tipo_solucionador" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerAncestral": { @@ -10144,6 +11964,11 @@ "s_noise": { "name": "s_ruido" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerAncestralCFGPP": { @@ -10155,6 +11980,11 @@ "s_noise": { "name": "s_ruido" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerCFGpp": { @@ -10195,6 +12025,11 @@ "order": { "name": "orden" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerSASolver": { @@ -10227,6 +12062,37 @@ "use_pece": { "name": "usar_pece" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerSEEDS2": { + "description": "Este nodo de muestreador puede representar múltiples muestreadores:\n\nseeds_2\n- configuración predeterminada\n\nexp_heun_2_x0\n- solver_type=phi_2, r=1.0, eta=0.0\n\nexp_heun_2_x0_sde\n- solver_type=phi_2, r=1.0, eta=1.0, s_noise=1.0", + "display_name": "SamplerSEEDS2", + "inputs": { + "eta": { + "name": "eta", + "tooltip": "Intensidad estocástica" + }, + "r": { + "name": "r", + "tooltip": "Tamaño de paso relativo para la etapa intermedia (nodo c2)" + }, + "s_noise": { + "name": "s_noise", + "tooltip": "Multiplicador de ruido SDE" + }, + "solver_type": { + "name": "solver_type" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplingPercentToSigma": { @@ -10243,11 +12109,11 @@ "name": "porcentaje_muestreo" } }, - "outputs": { - "0": { - "name": "valor_sigma" + "outputs": [ + { + "tooltip": null } - } + ] }, "SaveAnimatedPNG": { "display_name": "GuardarPNGAnimado", @@ -10365,6 +12231,44 @@ } } }, + "SaveImageDataSetToFolder": { + "display_name": "Guardar conjunto de imágenes en carpeta", + "inputs": { + "filename_prefix": { + "name": "filename_prefix", + "tooltip": "Prefijo para los nombres de archivo de las imágenes guardadas." + }, + "folder_name": { + "name": "folder_name", + "tooltip": "Nombre de la carpeta donde guardar las imágenes (dentro del directorio de salida)." + }, + "images": { + "name": "images", + "tooltip": "Lista de imágenes para guardar." + } + } + }, + "SaveImageTextDataSetToFolder": { + "display_name": "Guardar conjunto de imágenes y textos en carpeta", + "inputs": { + "filename_prefix": { + "name": "filename_prefix", + "tooltip": "Prefijo para los nombres de archivo de las imágenes guardadas." + }, + "folder_name": { + "name": "folder_name", + "tooltip": "Nombre de la carpeta donde guardar las imágenes (dentro del directorio de salida)." + }, + "images": { + "name": "images", + "tooltip": "Lista de imágenes para guardar." + }, + "texts": { + "name": "texts", + "tooltip": "Lista de textos para guardar." + } + } + }, "SaveImageWebsocket": { "display_name": "GuardarImagenWebsocket", "inputs": { @@ -10384,20 +12288,20 @@ } } }, - "SaveLoRANode": { - "display_name": "Guardar pesos LoRA", + "SaveLoRA": { + "display_name": "Guardar pesos de LoRA", "inputs": { "lora": { "name": "lora", - "tooltip": "El modelo LoRA a guardar. No usar el modelo con capas LoRA." + "tooltip": "El modelo LoRA para guardar. No utilices el modelo con capas LoRA." }, "prefix": { - "name": "prefijo", + "name": "prefix", "tooltip": "El prefijo a usar para el archivo LoRA guardado." }, "steps": { - "name": "pasos", - "tooltip": "Opcional: El número de pasos para los que LoRA ha sido entrenado, usado para nombrar el archivo guardado." + "name": "steps", + "tooltip": "Opcional: Número de pasos para los que se ha entrenado LoRA, usado para nombrar el archivo guardado." } } }, @@ -10414,6 +12318,27 @@ } } }, + "SaveTrainingDataset": { + "display_name": "Guardar conjunto de datos de entrenamiento", + "inputs": { + "conditioning": { + "name": "conditioning", + "tooltip": "Lista de listas de conditioning de MakeTrainingDataset." + }, + "folder_name": { + "name": "folder_name", + "tooltip": "Nombre de la carpeta donde guardar el conjunto de datos (dentro del directorio de salida)." + }, + "latents": { + "name": "latents", + "tooltip": "Lista de diccionarios de latent de MakeTrainingDataset." + }, + "shard_size": { + "name": "shard_size", + "tooltip": "Número de muestras por archivo fragmentado." + } + } + }, "SaveVideo": { "description": "Guarda las imágenes de entrada en tu directorio de salida de ComfyUI.", "display_name": "Guardar video", @@ -10534,6 +12459,11 @@ "sigmas": { "name": "sigmas" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SetHookKeyframes": { @@ -10574,6 +12504,58 @@ } } }, + "ShuffleDataset": { + "display_name": "Barajar conjunto de imágenes", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images", + "tooltip": "Lista de imágenes a procesar." + }, + "seed": { + "name": "seed", + "tooltip": "Semilla aleatoria." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Imágenes procesadas" + } + } + }, + "ShuffleImageTextDataset": { + "display_name": "Barajar conjunto de imágenes y textos", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images", + "tooltip": "Lista de imágenes a barajar." + }, + "seed": { + "name": "seed", + "tooltip": "Semilla aleatoria." + }, + "texts": { + "name": "texts", + "tooltip": "Lista de textos a barajar." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Imágenes barajadas" + }, + "1": { + "name": "texts", + "tooltip": "Textos barajados" + } + } + }, "SkipLayerGuidanceDiT": { "description": "Versión genérica del nodo de Orientación de Capa de Salto que se puede usar en cada modelo DiT.", "display_name": "Orientación de Capa de Salto DiT", @@ -10670,6 +12652,11 @@ "width": { "name": "ancho" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SplitAudioChannels": { @@ -10680,14 +12667,14 @@ "name": "audio" } }, - "outputs": { - "0": { - "name": "izquierdo" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "derecho" + { + "tooltip": null } - } + ] }, "SplitImageWithAlpha": { "display_name": "Dividir Imagen con Alfa", @@ -10715,14 +12702,14 @@ "name": "paso" } }, - "outputs": { - "0": { - "name": "sigmas_altas" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "sigmas_bajas" + { + "tooltip": null } - } + ] }, "SplitSigmasDenoise": { "display_name": "SplitSigmasDenoise", @@ -10734,14 +12721,14 @@ "name": "sigmas" } }, - "outputs": { - "0": { - "name": "high_sigmas" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "low_sigmas" + { + "tooltip": null } - } + ] }, "StabilityAudioInpaint": { "description": "Transforma parte de una muestra de audio existente usando instrucciones de texto.", @@ -11343,6 +13330,21 @@ } } }, + "StripWhitespace": { + "display_name": "Eliminar espacios en blanco", + "inputs": { + "texts": { + "name": "textos", + "tooltip": "Texto a procesar." + } + }, + "outputs": { + "0": { + "name": "textos", + "tooltip": "Textos procesados" + } + } + }, "StyleModelApply": { "display_name": "Aplicar Modelo de Estilo", "inputs": { @@ -11428,6 +13430,84 @@ } } }, + "TencentImageToModelNode": { + "display_name": "Hunyuan3D: Imagen(es) a Modelo (Pro)", + "inputs": { + "control_after_generate": { + "name": "controlar_tras_generar" + }, + "face_count": { + "name": "número_de_caras" + }, + "generate_type": { + "name": "tipo_de_generación" + }, + "generate_type_pbr": { + "name": "pbr" + }, + "image": { + "name": "imagen" + }, + "image_back": { + "name": "imagen_trasera" + }, + "image_left": { + "name": "imagen_izquierda" + }, + "image_right": { + "name": "imagen_derecha" + }, + "model": { + "name": "modelo", + "tooltip": "La opción LowPoly no está disponible para el modelo `3.1`." + }, + "seed": { + "name": "semilla", + "tooltip": "La semilla controla si el nodo debe volver a ejecutarse; los resultados son no deterministas independientemente de la semilla." + } + }, + "outputs": { + "0": { + "name": "archivo_modelo", + "tooltip": null + } + } + }, + "TencentTextToModelNode": { + "display_name": "Hunyuan3D: Texto a Modelo (Pro)", + "inputs": { + "control_after_generate": { + "name": "controlar_tras_generar" + }, + "face_count": { + "name": "número_de_caras" + }, + "generate_type": { + "name": "tipo_de_generación" + }, + "generate_type_pbr": { + "name": "pbr" + }, + "model": { + "name": "modelo", + "tooltip": "La opción LowPoly no está disponible para el modelo `3.1`." + }, + "prompt": { + "name": "prompt", + "tooltip": "Admite hasta 1024 caracteres." + }, + "seed": { + "name": "semilla", + "tooltip": "La semilla controla si el nodo debe volver a ejecutarse; los resultados son no deterministas independientemente de la semilla." + } + }, + "outputs": { + "0": { + "name": "archivo_modelo", + "tooltip": null + } + } + }, "TextEncodeAceStepAudio": { "display_name": "TextEncodeAceStepAudio", "inputs": { @@ -11523,6 +13603,70 @@ } } }, + "TextEncodeZImageOmni": { + "display_name": "TextEncodeZImageOmni", + "inputs": { + "auto_resize_images": { + "name": "auto_redimensionar_imágenes" + }, + "clip": { + "name": "clip" + }, + "image1": { + "name": "imagen1" + }, + "image2": { + "name": "imagen2" + }, + "image3": { + "name": "imagen3" + }, + "image_encoder": { + "name": "codificador_de_imagen" + }, + "prompt": { + "name": "instrucción" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TextToLowercase": { + "display_name": "Convertir texto a minúsculas", + "inputs": { + "texts": { + "name": "textos", + "tooltip": "Texto a procesar." + } + }, + "outputs": { + "0": { + "name": "textos", + "tooltip": "Textos procesados" + } + } + }, + "TextToUppercase": { + "display_name": "Convertir texto a mayúsculas", + "inputs": { + "texts": { + "name": "textos", + "tooltip": "Texto a procesar." + } + }, + "outputs": { + "0": { + "name": "textos", + "tooltip": "Textos procesados" + } + } + }, "ThresholdMask": { "display_name": "Máscara de Umbral", "inputs": { @@ -11532,6 +13676,11 @@ "value": { "name": "valor" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "TomePatchModel": { @@ -11550,6 +13699,118 @@ } } }, + "TopazImageEnhance": { + "description": "Mejora y ampliación de imágenes de nivel industrial.", + "display_name": "Mejorar imagen con Topaz", + "inputs": { + "color_preservation": { + "name": "preservación_de_color", + "tooltip": "Preservar los colores originales." + }, + "creativity": { + "name": "creatividad" + }, + "crop_to_fill": { + "name": "recortar_para_ajustar", + "tooltip": "Por defecto, la imagen se muestra con bandas negras si la relación de aspecto de salida es diferente. Activa para recortar la imagen y llenar las dimensiones de salida." + }, + "face_enhancement": { + "name": "mejora_de_rostros", + "tooltip": "Mejorar rostros (si están presentes) durante el procesamiento." + }, + "face_enhancement_creativity": { + "name": "creatividad_mejora_rostros", + "tooltip": "Establece el nivel de creatividad para la mejora de rostros." + }, + "face_enhancement_strength": { + "name": "intensidad_mejora_rostros", + "tooltip": "Controla cuán nítidos son los rostros mejorados en relación con el fondo." + }, + "face_preservation": { + "name": "preservación_de_rostros", + "tooltip": "Preservar la identidad facial de los sujetos." + }, + "image": { + "name": "imagen" + }, + "model": { + "name": "modelo" + }, + "output_height": { + "name": "alto_de_salida", + "tooltip": "Un valor de cero significa que se usará la misma altura que la original o el ancho de salida." + }, + "output_width": { + "name": "ancho_de_salida", + "tooltip": "Un valor de cero significa calcular automáticamente (normalmente será el tamaño original o la altura de salida si se especifica)." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto opcional para orientación creativa en la ampliación." + }, + "subject_detection": { + "name": "detección_de_sujetos" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TopazVideoEnhance": { + "description": "Da nueva vida a tus videos con potentes tecnologías de escalado y recuperación.", + "display_name": "Topaz Video Enhance", + "inputs": { + "dynamic_compression_level": { + "name": "dynamic_compression_level", + "tooltip": "Nivel CQP." + }, + "interpolation_duplicate": { + "name": "interpolation_duplicate", + "tooltip": "Analiza la entrada para detectar fotogramas duplicados y los elimina." + }, + "interpolation_duplicate_threshold": { + "name": "interpolation_duplicate_threshold", + "tooltip": "Sensibilidad de detección para fotogramas duplicados." + }, + "interpolation_enabled": { + "name": "interpolation_enabled" + }, + "interpolation_frame_rate": { + "name": "interpolation_frame_rate", + "tooltip": "Tasa de fotogramas de salida." + }, + "interpolation_model": { + "name": "interpolation_model" + }, + "interpolation_slowmo": { + "name": "interpolation_slowmo", + "tooltip": "Factor de cámara lenta aplicado al video de entrada. Por ejemplo, 2 hace que la salida sea el doble de lenta y duplica la duración." + }, + "upscaler_creativity": { + "name": "upscaler_creativity", + "tooltip": "Nivel de creatividad (solo se aplica a Starlight (Astra) Creative)." + }, + "upscaler_enabled": { + "name": "upscaler_enabled" + }, + "upscaler_model": { + "name": "upscaler_model" + }, + "upscaler_resolution": { + "name": "upscaler_resolution" + }, + "video": { + "name": "video" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "TorchCompileModel": { "display_name": "Modelo de Compilación Torch", "inputs": { @@ -11577,6 +13838,10 @@ "name": "tamaño_lote", "tooltip": "El tamaño de lote a utilizar para el entrenamiento." }, + "bucket_mode": { + "name": "bucket_mode", + "tooltip": "Habilita el modo de resolución por grupos. Cuando está habilitado, espera latentes preagrupados del nodo ResolutionBucket." + }, "control_after_generate": { "name": "controlar después de generar" }, @@ -11637,20 +13902,20 @@ "tooltip": "El tipo de datos a usar para el entrenamiento." } }, - "outputs": { - "0": { - "name": "modelo_con_lora" + "outputs": [ + { + "tooltip": "Modelo con LoRA aplicado" }, - "1": { - "name": "lora" + { + "tooltip": "Pesos de LoRA" }, - "2": { - "name": "pérdida" + { + "tooltip": "Historial de pérdida" }, - "3": { - "name": "pasos" + { + "tooltip": "Total de pasos de entrenamiento" } - } + ] }, "TrimAudioDuration": { "description": "Recortar tensor de audio al rango de tiempo elegido.", @@ -11667,6 +13932,11 @@ "name": "índice_inicio", "tooltip": "Tiempo de inicio en segundos, puede ser negativo para contar desde el final (admite subsegundos)." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "TrimVideoLatent": { @@ -11708,23 +13978,62 @@ "TripoConversionNode": { "display_name": "Tripo: Convertir modelo", "inputs": { + "animate_in_place": { + "name": "animate_in_place" + }, + "bake": { + "name": "bake" + }, + "export_orientation": { + "name": "export_orientation" + }, + "export_vertex_colors": { + "name": "export_vertex_colors" + }, "face_limit": { "name": "límite_caras" }, + "fbx_preset": { + "name": "fbx_preset" + }, + "flatten_bottom": { + "name": "flatten_bottom" + }, + "flatten_bottom_threshold": { + "name": "flatten_bottom_threshold" + }, + "force_symmetry": { + "name": "force_symmetry" + }, "format": { "name": "formato" }, "original_model_task_id": { "name": "id_tarea_modelo_original" }, + "pack_uv": { + "name": "pack_uv" + }, + "part_names": { + "name": "part_names" + }, + "pivot_to_center_bottom": { + "name": "pivot_to_center_bottom" + }, "quad": { "name": "cuadrangular" }, + "scale_factor": { + "name": "scale_factor" + }, "texture_format": { "name": "formato_textura" }, "texture_size": { "name": "tamaño_textura" + }, + "with_animation": { + "name": "with_animation" } } }, @@ -11734,6 +14043,9 @@ "face_limit": { "name": "límite_de_caras" }, + "geometry_quality": { + "name": "geometry_quality" + }, "image": { "name": "imagen" }, @@ -11786,6 +14098,9 @@ "face_limit": { "name": "límite_de_caras" }, + "geometry_quality": { + "name": "geometry_quality" + }, "image": { "name": "imagen" }, @@ -11903,6 +14218,9 @@ "face_limit": { "name": "límite_de_caras" }, + "geometry_quality": { + "name": "geometry_quality" + }, "image_seed": { "name": "semilla_de_imagen" }, @@ -11981,6 +14299,25 @@ } } }, + "TruncateText": { + "display_name": "Truncar texto", + "inputs": { + "max_length": { + "name": "max_length", + "tooltip": "Longitud máxima del texto." + }, + "texts": { + "name": "texts", + "tooltip": "Texto a procesar." + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "Textos procesados" + } + } + }, "UNETLoader": { "display_name": "Cargar Modelo de Difusión", "inputs": { @@ -12122,6 +14459,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEDecodeHunyuan3D": { @@ -12139,6 +14481,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEDecodeTiled": { @@ -12186,6 +14533,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEEncodeForInpaint": { @@ -12264,6 +14616,63 @@ "steps": { "name": "pasos" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Veo3FirstLastFrameNode": { + "description": "Genera un video usando un prompt y los fotogramas inicial y final.", + "display_name": "Google Veo 3 Primer-Último Fotograma a Video", + "inputs": { + "aspect_ratio": { + "name": "relación de aspecto", + "tooltip": "Relación de aspecto del video de salida" + }, + "control_after_generate": { + "name": "controlar después de generar" + }, + "duration": { + "name": "duración", + "tooltip": "Duración del video de salida en segundos" + }, + "first_frame": { + "name": "primer_fotograma", + "tooltip": "Fotograma inicial" + }, + "generate_audio": { + "name": "generar_audio", + "tooltip": "Generar audio para el video." + }, + "last_frame": { + "name": "último_fotograma", + "tooltip": "Fotograma final" + }, + "model": { + "name": "modelo" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Prompt negativo para guiar lo que se debe evitar en el video" + }, + "prompt": { + "name": "prompt", + "tooltip": "Descripción de texto del video" + }, + "resolution": { + "name": "resolución" + }, + "seed": { + "name": "semilla", + "tooltip": "Semilla para la generación del video" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Veo3VideoGenerationNode": { @@ -12392,6 +14801,166 @@ } } }, + "Vidu2ImageToVideoNode": { + "description": "Genera un video a partir de una imagen y un prompt opcional.", + "display_name": "Vidu2 Generación de Imagen a Video", + "inputs": { + "control_after_generate": { + "name": "controlar después de generar" + }, + "duration": { + "name": "duración" + }, + "image": { + "name": "imagen", + "tooltip": "Una imagen que se usará como el primer fotograma del video generado." + }, + "model": { + "name": "modelo" + }, + "movement_amplitude": { + "name": "amplitud_de_movimiento", + "tooltip": "La amplitud de movimiento de los objetos en el fotograma." + }, + "prompt": { + "name": "prompt", + "tooltip": "Un prompt de texto opcional para la generación de video (máx. 2000 caracteres)." + }, + "resolution": { + "name": "resolución" + }, + "seed": { + "name": "semilla" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2ReferenceVideoNode": { + "description": "Genera un video a partir de múltiples imágenes de referencia y un prompt.", + "display_name": "Vidu2 Generación de Video con Referencias", + "inputs": { + "aspect_ratio": { + "name": "relación_de_aspecto" + }, + "audio": { + "name": "audio", + "tooltip": "Si está habilitado, el video contendrá voz generada y música de fondo basada en el prompt." + }, + "control_after_generate": { + "name": "controlar después de generar" + }, + "duration": { + "name": "duración" + }, + "model": { + "name": "modelo" + }, + "movement_amplitude": { + "name": "amplitud_de_movimiento", + "tooltip": "La amplitud de movimiento de los objetos en el fotograma." + }, + "prompt": { + "name": "prompt", + "tooltip": "Si está habilitado, el video incluirá voz generada y música de fondo basada en el prompt." + }, + "resolution": { + "name": "resolución" + }, + "seed": { + "name": "semilla" + }, + "subjects": { + "name": "sujetos", + "tooltip": "Para cada sujeto, proporciona hasta 3 imágenes de referencia (7 imágenes en total entre todos los sujetos). Haz referencia a ellos en los prompts usando @subject{subject_id}." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2StartEndToVideoNode": { + "description": "Genera un video a partir de un fotograma inicial, un fotograma final y un prompt.", + "display_name": "Generación de video de Vidu2 desde fotogramas inicial/final", + "inputs": { + "control_after_generate": { + "name": "controlar después de generar" + }, + "duration": { + "name": "duración" + }, + "end_frame": { + "name": "fotograma_final" + }, + "first_frame": { + "name": "fotograma_inicial" + }, + "model": { + "name": "modelo" + }, + "movement_amplitude": { + "name": "amplitud_de_movimiento", + "tooltip": "La amplitud de movimiento de los objetos en el fotograma." + }, + "prompt": { + "name": "prompt", + "tooltip": "Descripción del prompt (máximo 2000 caracteres)." + }, + "resolution": { + "name": "resolución" + }, + "seed": { + "name": "semilla" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2TextToVideoNode": { + "description": "Genera un video a partir de un prompt de texto", + "display_name": "Generación de video de Vidu2 desde texto", + "inputs": { + "aspect_ratio": { + "name": "relación_de_aspecto" + }, + "background_music": { + "name": "música_de_fondo", + "tooltip": "Indica si se añade música de fondo al video generado." + }, + "control_after_generate": { + "name": "controlar después de generar" + }, + "duration": { + "name": "duración" + }, + "model": { + "name": "modelo" + }, + "prompt": { + "name": "prompt", + "tooltip": "Una descripción textual para la generación de video, con una longitud máxima de 2000 caracteres." + }, + "resolution": { + "name": "resolución" + }, + "seed": { + "name": "semilla" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "ViduImageToVideoNode": { "description": "Generar video a partir de imagen y texto opcional", "display_name": "Generación de Video a partir de Imagen Vidu", @@ -12580,6 +15149,11 @@ "voxel": { "name": "voxel" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VoxelToMeshBasic": { @@ -12591,6 +15165,11 @@ "voxel": { "name": "voxel" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Wan22FunControlToVideo": { @@ -12870,6 +15449,10 @@ "name": "context_stride", "tooltip": "El paso de la ventana de contexto; solo aplicable a programaciones uniformes." }, + "freenoise": { + "name": "freenoise", + "tooltip": "Si se aplica el barajado de ruido FreeNoise, mejora la mezcla de ventanas." + }, "fuse_method": { "name": "fuse_method", "tooltip": "El método a utilizar para fusionar las ventanas de contexto." @@ -13210,6 +15793,10 @@ "name": "semilla", "tooltip": "Semilla a utilizar para la generación." }, + "shot_type": { + "name": "tipo_de_toma", + "tooltip": "Especifica el tipo de toma para el video generado, es decir, si el video es una sola toma continua o varias tomas con cortes. Este parámetro solo tiene efecto cuando prompt_extend es True." + }, "watermark": { "name": "marca_agua", "tooltip": "Si se debe agregar una marca de agua \"Generado por IA\" al resultado." @@ -13221,6 +15808,196 @@ } } }, + "WanInfiniteTalkToVideo": { + "display_name": "WanInfiniteTalkToVideo", + "inputs": { + "audio_encoder_output_1": { + "name": "salida codificador de audio 1" + }, + "audio_scale": { + "name": "escala de audio" + }, + "clip_vision_output": { + "name": "salida de clip visión" + }, + "height": { + "name": "alto" + }, + "length": { + "name": "longitud" + }, + "mode": { + "name": "modo" + }, + "model": { + "name": "modelo" + }, + "model_patch": { + "name": "parche de modelo" + }, + "motion_frame_count": { + "name": "número de fotogramas de movimiento", + "tooltip": "Número de fotogramas anteriores a usar como contexto de movimiento." + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "previous_frames": { + "name": "fotogramas anteriores" + }, + "start_image": { + "name": "imagen inicial" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "ancho" + } + }, + "outputs": { + "0": { + "name": "modelo", + "tooltip": null + }, + "1": { + "name": "positivo", + "tooltip": null + }, + "2": { + "name": "negativo", + "tooltip": null + }, + "3": { + "name": "latente", + "tooltip": null + }, + "4": { + "name": "imagen recortada", + "tooltip": null + } + } + }, + "WanMoveConcatTrack": { + "display_name": "WanMoveConcatTrack", + "inputs": { + "tracks_1": { + "name": "pistas_1" + }, + "tracks_2": { + "name": "pistas_2" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanMoveTrackToVideo": { + "display_name": "WanMoveTrackToVideo", + "inputs": { + "batch_size": { + "name": "tamaño_lote" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "height": { + "name": "alto" + }, + "length": { + "name": "longitud" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "start_image": { + "name": "imagen_inicial" + }, + "strength": { + "name": "fuerza", + "tooltip": "Fuerza del acondicionamiento de la pista." + }, + "tracks": { + "name": "pistas" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "ancho" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latente", + "tooltip": null + } + } + }, + "WanMoveTracksFromCoords": { + "display_name": "WanMoveTracksFromCoords", + "inputs": { + "track_coords": { + "name": "coordenadas_de_pista" + }, + "track_mask": { + "name": "máscara_de_pista" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "longitud_de_pista", + "tooltip": null + } + } + }, + "WanMoveVisualizeTracks": { + "display_name": "WanMoveVisualizeTracks", + "inputs": { + "circle_size": { + "name": "tamaño_círculo" + }, + "images": { + "name": "imágenes" + }, + "line_resolution": { + "name": "resolución_de_línea" + }, + "line_width": { + "name": "ancho_de_línea" + }, + "opacity": { + "name": "opacidad" + }, + "tracks": { + "name": "pistas" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WanPhantomSubjectToVideo": { "display_name": "WanPhantomSubjectToVideo", "inputs": { @@ -13268,6 +16045,51 @@ } } }, + "WanReferenceVideoApi": { + "description": "Utiliza el personaje y la voz de los videos de entrada, combinados con un prompt, para generar un nuevo video que mantenga la coherencia del personaje.", + "display_name": "Wan Reference to Video", + "inputs": { + "control_after_generate": { + "name": "controlar después de generar" + }, + "duration": { + "name": "duración" + }, + "model": { + "name": "modelo" + }, + "negative_prompt": { + "name": "prompt_negativo", + "tooltip": "Prompt negativo que describe lo que se debe evitar." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt que describe los elementos y características visuales. Soporta inglés y chino. Usa identificadores como `character1` y `character2` para referirse a los personajes de referencia." + }, + "reference_videos": { + "name": "videos_de_referencia" + }, + "seed": { + "name": "semilla" + }, + "shot_type": { + "name": "tipo_de_toma", + "tooltip": "Especifica el tipo de toma para el video generado, es decir, si el video es una sola toma continua o varias tomas con cortes." + }, + "size": { + "name": "tamaño" + }, + "watermark": { + "name": "marca_de_agua", + "tooltip": "Indica si se debe añadir una marca de agua generada por IA al resultado." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WanSoundImageToVideo": { "display_name": "WanSoundImageToVideo", "inputs": { @@ -13446,6 +16268,10 @@ "name": "semilla", "tooltip": "Semilla a utilizar para la generación." }, + "shot_type": { + "name": "tipo_de_toma", + "tooltip": "Especifica el tipo de toma para el video generado, es decir, si el video es una sola toma continua o varias tomas con cortes. Este parámetro solo tiene efecto cuando prompt_extend es True." + }, "size": { "name": "tamaño" }, @@ -13571,6 +16397,43 @@ } } }, + "WavespeedFlashVSRNode": { + "description": "Escalador de video rápido y de alta calidad que aumenta la resolución y restaura la claridad de videos de baja resolución o borrosos.", + "display_name": "FlashVSR Escalado de Video", + "inputs": { + "target_resolution": { + "name": "resolución_objetivo" + }, + "video": { + "name": "video" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WavespeedImageUpscaleNode": { + "description": "Aumenta la resolución y calidad de la imagen, escalando fotos a 4K u 8K para obtener resultados nítidos y detallados.", + "display_name": "WaveSpeed Escalado de Imagen", + "inputs": { + "image": { + "name": "imagen" + }, + "model": { + "name": "modelo" + }, + "target_resolution": { + "name": "resolución_objetivo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WebcamCapture": { "display_name": "Captura de Webcam", "inputs": { @@ -13590,6 +16453,32 @@ } } }, + "ZImageFunControlnet": { + "display_name": "ZImageFunControlnet", + "inputs": { + "image": { + "name": "imagen" + }, + "inpaint_image": { + "name": "imagen_relleno" + }, + "mask": { + "name": "máscara" + }, + "model": { + "name": "modelo" + }, + "model_patch": { + "name": "parche_de_modelo" + }, + "strength": { + "name": "fuerza" + }, + "vae": { + "name": "vae" + } + } + }, "unCLIPCheckpointLoader": { "display_name": "Cargador de Puntos de Control unCLIP", "inputs": { @@ -13614,5 +16503,19 @@ "name": "fuerza" } } + }, + "wanBlockSwap": { + "description": "NOP", + "display_name": "wanBlockSwap", + "inputs": { + "model": { + "name": "modelo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } } } diff --git a/src/locales/es/settings.json b/src/locales/es/settings.json index 3dfe1f847..2e477c938 100644 --- a/src/locales/es/settings.json +++ b/src/locales/es/settings.json @@ -29,12 +29,26 @@ "name": "Imagen de fondo del lienzo", "tooltip": "URL de la imagen para el fondo del lienzo. Puedes hacer clic derecho en una imagen del panel de resultados y seleccionar \"Establecer como fondo\" para usarla." }, + "Comfy_Canvas_LeftMouseClickBehavior": { + "name": "Comportamiento del clic izquierdo del ratón", + "options": { + "Panning": "Desplazamiento", + "Select": "Seleccionar" + } + }, + "Comfy_Canvas_MouseWheelScroll": { + "name": "Desplazamiento de la rueda del ratón", + "options": { + "Panning": "Desplazamiento", + "Zoom in/out": "Acercar/alejar" + } + }, "Comfy_Canvas_NavigationMode": { "name": "Modo de navegación del lienzo", "options": { + "Custom": "Personalizado", "Drag Navigation": "Navegación por arrastre", - "Standard (New)": "Estándar (Nuevo)", - "Custom": "Personalizado" + "Standard (New)": "Estándar (Nuevo)" } }, "Comfy_Canvas_SelectionToolbox": { @@ -65,6 +79,17 @@ "Comfy_EnableWorkflowViewRestore": { "name": "Guardar y restaurar la posición del lienzo y el nivel de zoom en los flujos de trabajo" }, + "Comfy_Execution_PreviewMethod": { + "name": "Método de vista previa en vivo", + "options": { + "auto": "auto", + "default": "default", + "latent2rgb": "latent2rgb", + "none": "ninguno", + "taesd": "taesd" + }, + "tooltip": "Método de vista previa en vivo durante la generación de imágenes. \"default\" utiliza la configuración CLI del servidor." + }, "Comfy_FloatRoundingPrecision": { "name": "Decimales de redondeo del widget flotante [0 = automático].", "tooltip": "(requiere recargar la página)" @@ -86,6 +111,10 @@ "None": "Ninguno" } }, + "Comfy_Graph_LiveSelection": { + "name": "Selección en vivo", + "tooltip": "Cuando está activado, los nodos se seleccionan/deseleccionan en tiempo real mientras arrastras el rectángulo de selección, similar a otras herramientas de diseño." + }, "Comfy_Graph_ZoomSpeed": { "name": "Velocidad de zoom del lienzo" }, @@ -152,6 +181,15 @@ "name": "Intensidad de luz mínima", "tooltip": "Establece el valor mínimo permitido de intensidad de luz para escenas 3D. Esto define el límite inferior de brillo que se puede ajustar al modificar la iluminación en cualquier widget 3D." }, + "Comfy_Load3D_PLYEngine": { + "name": "Motor PLY", + "options": { + "fastply": "fastply", + "sparkjs": "sparkjs", + "threejs": "threejs" + }, + "tooltip": "Selecciona el motor para cargar archivos PLY. \"threejs\" utiliza el cargador nativo Three.js PLYLoader (mejor para archivos PLY de malla). \"fastply\" utiliza un cargador optimizado para archivos PLY de nube de puntos ASCII. \"sparkjs\" utiliza Spark.js para archivos PLY de Gaussian Splatting 3D." + }, "Comfy_Load3D_ShowGrid": { "name": "Mostrar Cuadrícula", "tooltip": "Cambiar para mostrar cuadrícula por defecto" @@ -167,10 +205,6 @@ "name": "Bloquear ajuste del pincel al eje dominante", "tooltip": "Cuando está habilitado, los ajustes del pincel solo afectarán el tamaño O la dureza según la dirección en la que te muevas más" }, - "Comfy_MaskEditor_UseNewEditor": { - "name": "Usar nuevo editor de máscara", - "tooltip": "Cambiar a la nueva interfaz del editor de máscaras" - }, "Comfy_ModelLibrary_AutoLoadAll": { "name": "Cargar automáticamente todas las carpetas de modelos", "tooltip": "Si es verdadero, todas las carpetas se cargarán tan pronto como abras la biblioteca de modelos (esto puede causar retrasos mientras se carga). Si es falso, las carpetas de modelos de nivel raíz solo se cargarán una vez que hagas clic en ellas." @@ -238,6 +272,10 @@ "Comfy_Node_AllowImageSizeDraw": { "name": "Mostrar ancho × altura debajo de la vista previa de la imagen" }, + "Comfy_Node_AlwaysShowAdvancedWidgets": { + "name": "Mostrar siempre los widgets avanzados en todos los nodos", + "tooltip": "Cuando está activado, los widgets avanzados siempre son visibles en todos los nodos sin necesidad de expandirlos individualmente." + }, "Comfy_Node_AutoSnapLinkToSlot": { "name": "Enlace de ajuste automático a la ranura del nodo", "tooltip": "Al arrastrar un enlace sobre un nodo, el enlace se ajustará automáticamente a una ranura de entrada viable en el nodo" @@ -298,6 +336,10 @@ "name": "Tamaño del historial de la cola", "tooltip": "El número máximo de tareas que se muestran en el historial de la cola." }, + "Comfy_Queue_QPOV2": { + "name": "Usar la cola de trabajos unificada en el panel lateral de Activos", + "tooltip": "Reemplaza el panel flotante de la cola de trabajos por una cola de trabajos equivalente integrada en el panel lateral de Activos. Puedes desactivar esto para volver al diseño del panel flotante." + }, "Comfy_Sidebar_Location": { "name": "Ubicación de la barra lateral", "options": { @@ -312,6 +354,13 @@ "small": "pequeña" } }, + "Comfy_Sidebar_Style": { + "name": "Estilo de la barra lateral", + "options": { + "connected": "conectada", + "floating": "flotante" + } + }, "Comfy_Sidebar_UnifiedWidth": { "name": "Ancho unificado de la barra lateral" }, @@ -328,6 +377,14 @@ "Comfy_TreeExplorer_ItemPadding": { "name": "Relleno del elemento del explorador de árboles" }, + "Comfy_UI_TabBarLayout": { + "name": "Diseño de barra de pestañas", + "options": { + "Default": "Predeterminado", + "Integrated": "Integrado" + }, + "tooltip": "Controla el diseño de la barra de pestañas. \"Integrado\" mueve los controles de Ayuda y Usuario al área de la barra de pestañas." + }, "Comfy_UseNewMenu": { "name": "Usar nuevo menú", "options": { @@ -339,6 +396,14 @@ "Comfy_Validation_Workflows": { "name": "Validar flujos de trabajo" }, + "Comfy_VueNodes_AutoScaleLayout": { + "name": "Escalado automático del diseño (nodos Vue)", + "tooltip": "Escala automáticamente las posiciones de los nodos al cambiar a renderizado Vue para evitar superposiciones" + }, + "Comfy_VueNodes_Enabled": { + "name": "Diseño moderno de nodos (nodos Vue)", + "tooltip": "Moderno: Renderizado basado en DOM con interactividad mejorada, funciones nativas del navegador y diseño visual actualizado. Clásico: Renderizado tradicional en lienzo." + }, "Comfy_WidgetControlMode": { "name": "Modo de control del widget", "options": { @@ -376,6 +441,9 @@ "Comfy_Workflow_SortNodeIdOnSave": { "name": "Ordenar IDs de nodos al guardar el flujo de trabajo" }, + "Comfy_Workflow_WarnBlueprintOverwrite": { + "name": "Requerir confirmación para sobrescribir un plano de subgrafo existente" + }, "Comfy_Workflow_WorkflowTabsPosition": { "name": "Posición de los flujos de trabajo abiertos", "options": { @@ -407,37 +475,5 @@ }, "pysssss_SnapToGrid": { "name": "Siempre ajustar a la cuadrícula" - }, - "Comfy_Canvas_LeftMouseClickBehavior": { - "name": "Comportamiento del clic izquierdo del ratón", - "options": { - "Panning": "Desplazamiento", - "Select": "Seleccionar" - } - }, - "Comfy_Canvas_MouseWheelScroll": { - "name": "Desplazamiento de la rueda del ratón", - "options": { - "Panning": "Desplazamiento", - "Zoom in/out": "Acercar/alejar" - } - }, - "Comfy_Sidebar_Style": { - "name": "Estilo de la barra lateral", - "options": { - "floating": "flotante", - "connected": "conectada" - } - }, - "Comfy_VueNodes_AutoScaleLayout": { - "name": "Escalado automático del diseño (nodos Vue)", - "tooltip": "Escala automáticamente las posiciones de los nodos al cambiar a renderizado Vue para evitar superposiciones" - }, - "Comfy_VueNodes_Enabled": { - "name": "Diseño moderno de nodos (nodos Vue)", - "tooltip": "Moderno: Renderizado basado en DOM con interactividad mejorada, funciones nativas del navegador y diseño visual actualizado. Clásico: Renderizado tradicional en lienzo." - }, - "Comfy_Workflow_WarnBlueprintOverwrite": { - "name": "Requerir confirmación para sobrescribir un plano de subgrafo existente" } } diff --git a/src/locales/fa/commands.json b/src/locales/fa/commands.json new file mode 100644 index 000000000..2993e0c22 --- /dev/null +++ b/src/locales/fa/commands.json @@ -0,0 +1,348 @@ +{ + "Comfy-Desktop_CheckForUpdates": { + "label": "بررسی به‌روزرسانی‌ها" + }, + "Comfy-Desktop_Folders_OpenCustomNodesFolder": { + "label": "باز کردن پوشه Custom Nodes" + }, + "Comfy-Desktop_Folders_OpenInputsFolder": { + "label": "باز کردن پوشه Inputs" + }, + "Comfy-Desktop_Folders_OpenLogsFolder": { + "label": "باز کردن پوشه Logs" + }, + "Comfy-Desktop_Folders_OpenModelConfig": { + "label": "باز کردن extra_model_paths.yaml" + }, + "Comfy-Desktop_Folders_OpenModelsFolder": { + "label": "باز کردن پوشه Models" + }, + "Comfy-Desktop_Folders_OpenOutputsFolder": { + "label": "باز کردن پوشه Outputs" + }, + "Comfy-Desktop_OpenDevTools": { + "label": "باز کردن DevTools" + }, + "Comfy-Desktop_OpenUserGuide": { + "label": "راهنمای کاربر دسکتاپ" + }, + "Comfy-Desktop_Quit": { + "label": "خروج" + }, + "Comfy-Desktop_Reinstall": { + "label": "نصب مجدد" + }, + "Comfy-Desktop_Restart": { + "label": "راه‌اندازی مجدد" + }, + "Comfy_3DViewer_Open3DViewer": { + "label": "باز کردن ۳D Viewer (بتا) برای node انتخاب‌شده" + }, + "Comfy_BrowseModelAssets": { + "label": "آزمایشی: مرور Model Assets" + }, + "Comfy_BrowseTemplates": { + "label": "مرور قالب‌ها" + }, + "Comfy_Canvas_DeleteSelectedItems": { + "label": "حذف آیتم‌های انتخاب‌شده" + }, + "Comfy_Canvas_FitView": { + "label": "تنظیم نما بر اساس nodeهای انتخاب‌شده" + }, + "Comfy_Canvas_Lock": { + "label": "قفل کردن بوم" + }, + "Comfy_Canvas_MoveSelectedNodes_Down": { + "label": "انتقال nodeهای انتخاب‌شده به پایین" + }, + "Comfy_Canvas_MoveSelectedNodes_Left": { + "label": "انتقال nodeهای انتخاب‌شده به چپ" + }, + "Comfy_Canvas_MoveSelectedNodes_Right": { + "label": "انتقال nodeهای انتخاب‌شده به راست" + }, + "Comfy_Canvas_MoveSelectedNodes_Up": { + "label": "انتقال nodeهای انتخاب‌شده به بالا" + }, + "Comfy_Canvas_ResetView": { + "label": "بازنشانی نما" + }, + "Comfy_Canvas_Resize": { + "label": "تغییر اندازه nodeهای انتخاب‌شده" + }, + "Comfy_Canvas_ToggleLinkVisibility": { + "label": "نمایش/مخفی‌سازی لینک‌ها در بوم" + }, + "Comfy_Canvas_ToggleLock": { + "label": "قفل/باز کردن قفل بوم" + }, + "Comfy_Canvas_ToggleMinimap": { + "label": "نمایش/مخفی‌سازی نقشه کوچک بوم" + }, + "Comfy_Canvas_ToggleSelectedNodes_Bypass": { + "label": "فعال/غیرفعال کردن bypass برای nodeهای انتخاب‌شده" + }, + "Comfy_Canvas_ToggleSelectedNodes_Collapse": { + "label": "جمع/باز کردن nodeهای انتخاب‌شده" + }, + "Comfy_Canvas_ToggleSelectedNodes_Mute": { + "label": "بی‌صدا/فعال کردن nodeهای انتخاب‌شده" + }, + "Comfy_Canvas_ToggleSelectedNodes_Pin": { + "label": "سنجاق/برداشتن سنجاق nodeهای انتخاب‌شده" + }, + "Comfy_Canvas_ToggleSelected_Pin": { + "label": "سنجاق/برداشتن سنجاق آیتم‌های انتخاب‌شده" + }, + "Comfy_Canvas_Unlock": { + "label": "باز کردن قفل بوم" + }, + "Comfy_Canvas_ZoomIn": { + "label": "بزرگ‌نمایی" + }, + "Comfy_Canvas_ZoomOut": { + "label": "کوچک‌نمایی" + }, + "Comfy_ClearPendingTasks": { + "label": "پاک‌سازی وظایف در انتظار" + }, + "Comfy_ClearWorkflow": { + "label": "پاک‌سازی workflow" + }, + "Comfy_ContactSupport": { + "label": "تماس با پشتیبانی" + }, + "Comfy_Dev_ShowModelSelector": { + "label": "نمایش Model Selector (توسعه‌دهنده)" + }, + "Comfy_DuplicateWorkflow": { + "label": "تکثیر workflow فعلی" + }, + "Comfy_ExportWorkflow": { + "label": "خروجی گرفتن از workflow" + }, + "Comfy_ExportWorkflowAPI": { + "label": "خروجی گرفتن از workflow (فرمت API)" + }, + "Comfy_Graph_ConvertToSubgraph": { + "label": "تبدیل انتخاب به subgraph" + }, + "Comfy_Graph_EditSubgraphWidgets": { + "label": "ویرایش ابزارک‌های subgraph" + }, + "Comfy_Graph_ExitSubgraph": { + "label": "خروج از subgraph" + }, + "Comfy_Graph_FitGroupToContents": { + "label": "تنظیم گروه بر اساس محتوا" + }, + "Comfy_Graph_GroupSelectedNodes": { + "label": "گروه‌بندی نودهای انتخاب‌شده" + }, + "Comfy_Graph_ToggleWidgetPromotion": { + "label": "تغییر وضعیت ارتقاء ویجت در حال اشاره" + }, + "Comfy_Graph_UnpackSubgraph": { + "label": "بازکردن زیرگراف انتخاب‌شده" + }, + "Comfy_GroupNode_ConvertSelectedNodesToGroupNode": { + "label": "تبدیل نودهای انتخاب‌شده به گروه نود" + }, + "Comfy_GroupNode_ManageGroupNodes": { + "label": "مدیریت گروه نودها" + }, + "Comfy_GroupNode_UngroupSelectedGroupNodes": { + "label": "خارج کردن گروه نودهای انتخاب‌شده از گروه" + }, + "Comfy_Help_AboutComfyUI": { + "label": "درباره ComfyUI" + }, + "Comfy_Help_OpenComfyOrgDiscord": { + "label": "بازکردن دیسکورد Comfy-Org" + }, + "Comfy_Help_OpenComfyUIDocs": { + "label": "بازکردن مستندات ComfyUI" + }, + "Comfy_Help_OpenComfyUIForum": { + "label": "بازکردن انجمن ComfyUI" + }, + "Comfy_Help_OpenComfyUIIssues": { + "label": "بازکردن مشکلات ComfyUI" + }, + "Comfy_Interrupt": { + "label": "توقف" + }, + "Comfy_LoadDefaultWorkflow": { + "label": "بارگذاری ورک‌فلو پیش‌فرض" + }, + "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { + "label": "مدیریت نودهای سفارشی" + }, + "Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": { + "label": "نودهای سفارشی (قدیمی)" + }, + "Comfy_Manager_ShowLegacyManagerMenu": { + "label": "منوی مدیریت (قدیمی)" + }, + "Comfy_Manager_ShowMissingPacks": { + "label": "نصب نودهای سفارشی گمشده" + }, + "Comfy_Manager_ShowUpdateAvailablePacks": { + "label": "بررسی به‌روزرسانی نودهای سفارشی" + }, + "Comfy_MaskEditor_BrushSize_Decrease": { + "label": "کاهش اندازه قلم‌مو در MaskEditor" + }, + "Comfy_MaskEditor_BrushSize_Increase": { + "label": "افزایش اندازه قلم‌مو در MaskEditor" + }, + "Comfy_MaskEditor_ColorPicker": { + "label": "بازکردن انتخابگر رنگ در MaskEditor" + }, + "Comfy_MaskEditor_Mirror_Horizontal": { + "label": "آینه افقی در MaskEditor" + }, + "Comfy_MaskEditor_Mirror_Vertical": { + "label": "آینه عمودی در MaskEditor" + }, + "Comfy_MaskEditor_OpenMaskEditor": { + "label": "بازکردن ویرایشگر ماسک برای نود انتخاب‌شده" + }, + "Comfy_MaskEditor_Rotate_Left": { + "label": "چرخش به چپ در MaskEditor" + }, + "Comfy_MaskEditor_Rotate_Right": { + "label": "چرخش به راست در MaskEditor" + }, + "Comfy_Memory_UnloadModels": { + "label": "خارج کردن مدل‌ها از حافظه" + }, + "Comfy_Memory_UnloadModelsAndExecutionCache": { + "label": "خارج کردن مدل‌ها و کش اجرا از حافظه" + }, + "Comfy_NewBlankWorkflow": { + "label": "ورک‌فلو جدید خالی" + }, + "Comfy_OpenClipspace": { + "label": "Clipspace" + }, + "Comfy_OpenManagerDialog": { + "label": "مدیریت" + }, + "Comfy_OpenWorkflow": { + "label": "بازکردن ورک‌فلو" + }, + "Comfy_PublishSubgraph": { + "label": "انتشار زیرگراف" + }, + "Comfy_QueuePrompt": { + "label": "صف‌بندی پرامپت" + }, + "Comfy_QueuePromptFront": { + "label": "صف‌بندی پرامپت (ابتدا)" + }, + "Comfy_QueueSelectedOutputNodes": { + "label": "صف‌بندی نودهای خروجی انتخاب‌شده" + }, + "Comfy_Queue_ToggleOverlay": { + "label": "تغییر وضعیت تاریخچه وظایف" + }, + "Comfy_Redo": { + "label": "انجام مجدد" + }, + "Comfy_RefreshNodeDefinitions": { + "label": "به‌روزرسانی تعاریف نود" + }, + "Comfy_RenameWorkflow": { + "label": "تغییر نام Workflow" + }, + "Comfy_SaveWorkflow": { + "label": "ذخیره ورک‌فلو" + }, + "Comfy_SaveWorkflowAs": { + "label": "ذخیره ورک‌فلو با نام جدید" + }, + "Comfy_ShowSettingsDialog": { + "label": "نمایش پنجره تنظیمات" + }, + "Comfy_ToggleAssetAPI": { + "label": "آزمایشی: فعال‌سازی AssetAPI" + }, + "Comfy_ToggleCanvasInfo": { + "label": "عملکرد بوم" + }, + "Comfy_ToggleHelpCenter": { + "label": "مرکز راهنما" + }, + "Comfy_ToggleLinear": { + "label": "تغییر حالت خطی" + }, + "Comfy_ToggleQPOV2": { + "label": "تغییر پنل صف V2" + }, + "Comfy_ToggleTheme": { + "label": "تغییر تم (تاریک/روشن)" + }, + "Comfy_Undo": { + "label": "واگردانی" + }, + "Comfy_User_OpenSignInDialog": { + "label": "باز کردن پنجره ورود" + }, + "Comfy_User_SignOut": { + "label": "خروج از حساب" + }, + "Experimental_ToggleVueNodes": { + "label": "آزمایشی: فعال‌سازی Nodes 2.0" + }, + "Workspace_CloseWorkflow": { + "label": "بستن گردش‌کار جاری" + }, + "Workspace_NextOpenedWorkflow": { + "label": "گردش‌کار باز بعدی" + }, + "Workspace_PreviousOpenedWorkflow": { + "label": "گردش‌کار باز قبلی" + }, + "Workspace_SearchBox_Toggle": { + "label": "تغییر جعبه جستجو" + }, + "Workspace_ToggleBottomPanel": { + "label": "تغییر پنل پایین" + }, + "Workspace_ToggleBottomPanelTab_command-terminal": { + "label": "تغییر پنل ترمینال پایین" + }, + "Workspace_ToggleBottomPanelTab_logs-terminal": { + "label": "تغییر پنل گزارش‌ها پایین" + }, + "Workspace_ToggleBottomPanelTab_shortcuts-essentials": { + "label": "تغییر پنل ضروریات پایین" + }, + "Workspace_ToggleBottomPanelTab_shortcuts-view-controls": { + "label": "تغییر پنل کنترل‌های نمای پایین" + }, + "Workspace_ToggleBottomPanel_Shortcuts": { + "label": "نمایش پنجره کلیدهای میانبر" + }, + "Workspace_ToggleFocusMode": { + "label": "تغییر حالت تمرکز" + }, + "Workspace_ToggleSidebarTab_assets": { + "label": "تغییر نوار کناری دارایی‌ها", + "tooltip": "دارایی‌ها" + }, + "Workspace_ToggleSidebarTab_model-library": { + "label": "تغییر نوار کناری کتابخانه مدل", + "tooltip": "کتابخانه مدل" + }, + "Workspace_ToggleSidebarTab_node-library": { + "label": "تغییر نوار کناری کتابخانه node", + "tooltip": "کتابخانه node" + }, + "Workspace_ToggleSidebarTab_workflows": { + "label": "تغییر نوار کناری گردش‌کارها", + "tooltip": "گردش‌کارها" + } +} diff --git a/src/locales/fa/main.json b/src/locales/fa/main.json new file mode 100644 index 000000000..3c52c70c5 --- /dev/null +++ b/src/locales/fa/main.json @@ -0,0 +1,2810 @@ +{ + "actionbar": { + "dockToTop": "چسباندن به بالا", + "feedback": "بازخورد", + "feedbackTooltip": "بازخورد" + }, + "apiNodesCostBreakdown": { + "costPerRun": "هزینه هر اجرا", + "title": "API Node(ها)", + "totalCost": "هزینه کل" + }, + "apiNodesSignInDialog": { + "message": "این workflow شامل API Node است که برای اجرا نیاز به ورود به حساب کاربری دارد.", + "title": "ورود برای استفاده از API Nodeها لازم است" + }, + "assetBrowser": { + "allCategory": "همه {category}", + "allModels": "همه مدل‌ها", + "ariaLabel": { + "assetCard": "{name} - دارایی {type}", + "loadingAsset": "در حال بارگذاری دارایی" + }, + "assetCollection": "مجموعه دارایی‌ها", + "assets": "دارایی‌ها", + "baseModels": "مدل‌های پایه", + "browseAssets": "مرور دارایی‌ها", + "byType": "بر اساس نوع", + "checkpoints": "چک‌پوینت‌ها", + "civitaiLinkExample": "{example} {link}", + "civitaiLinkExampleStrong": "مثال:", + "civitaiLinkExampleUrl": "https://civitai.com/models/10706/luisap-z-image-and-qwen-pixel-art-refiner?modelVersionId=2225295", + "civitaiLinkLabel": "لینک {download} مدل Civitai", + "civitaiLinkLabelDownload": "دانلود", + "civitaiLinkPlaceholder": "لینک را اینجا وارد کنید", + "confirmModelDetails": "تأیید جزئیات مدل", + "connectionError": "لطفاً اتصال خود را بررسی کرده و دوباره تلاش کنید", + "deletion": { + "body": "این مدل به طور دائمی از کتابخانه شما حذف خواهد شد.", + "complete": "{assetName} حذف شد.", + "failed": "امکان حذف {assetName} وجود ندارد.", + "header": "حذف این مدل؟", + "inProgress": "در حال حذف {assetName}..." + }, + "download": { + "complete": "دانلود کامل شد", + "failed": "دانلود ناموفق بود", + "inProgress": "در حال دانلود {assetName}..." + }, + "emptyImported": { + "canImport": "هنوز مدلی وارد نشده است. برای افزودن مدل خود، روی «وارد کردن مدل» کلیک کنید.", + "restricted": "مدل‌های شخصی فقط برای سطح Creator و بالاتر در دسترس هستند." + }, + "errorFileTooLarge": "فایل از حداکثر اندازه مجاز بزرگ‌تر است", + "errorFormatNotAllowed": "فقط فرمت SafeTensor مجاز است", + "errorModelTypeNotSupported": "این نوع مدل پشتیبانی نمی‌شود", + "errorUnknown": "خطای غیرمنتظره‌ای رخ داد", + "errorUnsafePickleScan": "CivitAI کد بالقوه ناامن را در این فایل شناسایی کرد", + "errorUnsafeVirusScan": "CivitAI بدافزار یا محتوای مشکوک را در این فایل شناسایی کرد", + "errorUploadFailed": "وارد کردن دارایی ناموفق بود. لطفاً دوباره تلاش کنید.", + "failedToCreateNode": "ایجاد node ناموفق بود. لطفاً دوباره تلاش کنید یا کنسول را بررسی کنید.", + "fileFormats": "فرمت‌های فایل", + "fileName": "نام فایل", + "fileSize": "اندازه فایل", + "filterBy": "فیلتر بر اساس", + "findInLibrary": "در بخش {type} کتابخانه مدل‌ها پیدا کنید.", + "finish": "پایان", + "genericLinkPlaceholder": "لینک را اینجا وارد کنید", + "importAnother": "وارد کردن مورد دیگر", + "imported": "وارد شده", + "jobId": "شناسه کار: {jobId}", + "loadingModels": "در حال بارگذاری {type}...", + "maxFileSize": "حداکثر اندازه فایل: {size}", + "maxFileSizeValue": "۱ گیگابایت", + "media": { + "audioPlaceholder": "صوت", + "threeDModelPlaceholder": "مدل سه‌بعدی" + }, + "modelAssociatedWithLink": "مدل مرتبط با لینکی که وارد کردید:", + "modelInfo": { + "addBaseModel": "افزودن مدل پایه...", + "addTag": "افزودن برچسب...", + "additionalTags": "برچسب‌های اضافی", + "baseModelUnknown": "مدل پایه نامشخص", + "basicInfo": "اطلاعات پایه", + "compatibleBaseModels": "مدل‌های پایه سازگار", + "description": "توضیحات", + "descriptionNotSet": "توضیحی تنظیم نشده است", + "descriptionPlaceholder": "یک توضیح برای این مدل اضافه کنید...", + "displayName": "نام نمایشی", + "editDisplayName": "ویرایش نام نمایشی", + "fileName": "نام فایل", + "modelDescription": "توضیحات مدل", + "modelTagging": "برچسب‌گذاری مدل", + "modelType": "نوع مدل", + "noAdditionalTags": "برچسب اضافی وجود ندارد", + "selectModelPrompt": "یک مدل را برای مشاهده اطلاعات آن انتخاب کنید", + "selectModelType": "انتخاب نوع مدل...", + "source": "منبع", + "title": "اطلاعات مدل", + "triggerPhrases": "عبارات فعال‌ساز", + "viewOnSource": "مشاهده در {source}" + }, + "modelName": "نام مدل", + "modelNamePlaceholder": "یک نام برای این مدل وارد کنید", + "modelTypeSelectorLabel": "نوع مدل چیست؟", + "modelTypeSelectorPlaceholder": "نوع مدل را انتخاب کنید", + "modelUploaded": "مدل با موفقیت وارد شد.", + "noAssetsFound": "هیچ دارایی‌ای یافت نشد", + "noModelsInFolder": "هیچ {type} در این پوشه موجود نیست", + "noValidSourceDetected": "هیچ منبع واردات معتبری شناسایی نشد", + "notSureLeaveAsIs": "مطمئن نیستید؟ همین را باقی بگذارید", + "onlyCivitaiUrlsSupported": "فقط URLهای Civitai پشتیبانی می‌شوند", + "ownership": "مالکیت", + "ownershipAll": "همه", + "ownershipMyModels": "مدل‌های من", + "ownershipPublicModels": "مدل‌های عمومی", + "processingModel": "دانلود آغاز شد", + "processingModelDescription": "می‌توانید این پنجره را ببندید. دانلود در پس‌زمینه ادامه خواهد داشت.", + "providerCivitai": "Civitai", + "providerHuggingFace": "Hugging Face", + "rename": { + "failed": "امکان تغییر نام دارایی وجود ندارد." + }, + "selectFrameworks": "فریم‌ورک‌ها را انتخاب کنید", + "selectModelType": "نوع مدل را انتخاب کنید", + "selectProjects": "پروژه‌ها را انتخاب کنید", + "sortAZ": "الف-ی", + "sortBy": "مرتب‌سازی بر اساس", + "sortPopular": "محبوب", + "sortRecent": "جدیدترین", + "sortZA": "ی-الف", + "sortingType": "نوع مرتب‌سازی", + "tags": "برچسب‌ها", + "tagsHelp": "برچسب‌ها را با ویرگول جدا کنید", + "tagsPlaceholder": "مثلاً: models, checkpoint", + "tryAdjustingFilters": "جستجو یا فیلترهای خود را تغییر دهید", + "unknown": "نامشخص", + "unsupportedUrlSource": "فقط URLهای {sources} پشتیبانی می‌شوند", + "upgradeFeatureDescription": "این قابلیت فقط با پلن Creator یا Pro فعال است.", + "upgradeToUnlockFeature": "برای فعال‌سازی این قابلیت ارتقا دهید", + "upload": "وارد کردن", + "uploadFailed": "وارد کردن ناموفق بود", + "uploadModel": "وارد کردن", + "uploadModelDescription1": "لینک دانلود مدل Civitai را وارد کنید تا به کتابخانه شما افزوده شود.", + "uploadModelDescription1Generic": "لینک دانلود مدل را وارد کنید تا به کتابخانه شما افزوده شود.", + "uploadModelDescription2": "در حال حاضر فقط لینک‌های {link} پشتیبانی می‌شوند", + "uploadModelDescription2Generic": "در حال حاضر فقط URLهای ارائه‌دهندگان زیر پشتیبانی می‌شوند:", + "uploadModelDescription2Link": "https://civitai.com/models", + "uploadModelDescription3": "حداکثر اندازه فایل: {size}", + "uploadModelFailedToRetrieveMetadata": "دریافت متادیتا ناموفق بود. لطفاً لینک را بررسی و دوباره تلاش کنید.", + "uploadModelFromCivitai": "وارد کردن مدل از Civitai", + "uploadModelGeneric": "وارد کردن مدل", + "uploadModelHelpFooterText": "برای یافتن URLها نیاز به راهنما دارید؟ روی یکی از ارائه‌دهندگان زیر کلیک کنید تا ویدئوی آموزشی را ببینید.", + "uploadModelHelpVideo": "ویدئوی راهنمای وارد کردن مدل", + "uploadModelHowDoIFindThis": "چطور این را پیدا کنم؟", + "uploadSuccess": "مدل با موفقیت وارد شد!", + "uploadingModel": "در حال وارد کردن مدل..." + }, + "auth": { + "apiKey": { + "cleared": "کلید API پاک شد", + "clearedDetail": "کلید API شما با موفقیت پاک شد", + "description": "برای فعال‌سازی API Nodeها از کلید Comfy API خود استفاده کنید", + "error": "کلید API نامعتبر است", + "generateKey": "از اینجا دریافت کنید", + "helpText": "نیاز به کلید API دارید؟", + "invalid": "کلید API نامعتبر است", + "invalidDetail": "لطفاً یک کلید API معتبر وارد کنید", + "label": "کلید API", + "placeholder": "کلید API خود را وارد کنید", + "storageFailed": "ذخیره‌سازی کلید API ناموفق بود", + "storageFailedDetail": "لطفاً دوباره تلاش کنید.", + "stored": "کلید API ذخیره شد", + "storedDetail": "کلید API شما با موفقیت ذخیره شد", + "title": "کلید API", + "whitelistInfo": "درباره سایت‌های غیرمجاز" + }, + "deleteAccount": { + "cancel": "انصراف", + "confirm": "حذف حساب کاربری", + "confirmMessage": "آیا مطمئن هستید که می‌خواهید حساب کاربری خود را حذف کنید؟ این عملیات غیرقابل بازگشت است و تمام داده‌های شما به طور دائمی حذف خواهد شد.", + "confirmTitle": "حذف حساب کاربری", + "deleteAccount": "حذف حساب کاربری", + "success": "حساب کاربری حذف شد", + "successDetail": "حساب کاربری شما با موفقیت حذف شد." + }, + "errors": { + "auth/cancelled-popup-request": "ورود لغو شد. لطفاً دوباره تلاش کنید.", + "auth/email-already-in-use": "حسابی با این ایمیل قبلاً وجود دارد. لطفاً وارد شوید.", + "auth/invalid-credential": "اطلاعات ورود نامعتبر است. لطفاً ایمیل و رمز عبور خود را بررسی کنید.", + "auth/invalid-email": "لطفاً یک آدرس ایمیل معتبر وارد کنید.", + "auth/network-request-failed": "خطای شبکه. لطفاً اتصال خود را بررسی کرده و دوباره تلاش کنید.", + "auth/operation-not-allowed": "این روش ورود در حال حاضر پشتیبانی نمی‌شود.", + "auth/popup-closed-by-user": "ورود لغو شد. لطفاً دوباره تلاش کنید.", + "auth/too-many-requests": "تعداد دفعات تلاش برای ورود بیش از حد مجاز است. لطفاً کمی صبر کنید و دوباره تلاش کنید.", + "auth/user-disabled": "این حساب غیرفعال شده است. لطفاً با پشتیبانی تماس بگیرید.", + "auth/user-not-found": "حسابی با این ایمیل یافت نشد. مایل به ایجاد حساب جدید هستید؟", + "auth/weak-password": "رمز عبور خیلی ضعیف است. لطفاً از رمز عبور قوی‌تر با حداقل ۶ کاراکتر استفاده کنید.", + "auth/wrong-password": "رمز عبور وارد شده نادرست است. لطفاً دوباره تلاش کنید." + }, + "login": { + "andText": "و", + "backToLogin": "بازگشت به ورود", + "confirmPasswordLabel": "تأیید رمز عبور", + "confirmPasswordPlaceholder": "رمز عبور را مجدداً وارد کنید", + "didntReceiveEmail": "ایمیلی دریافت نکردید؟ با ما تماس بگیرید:", + "emailLabel": "ایمیل", + "emailPlaceholder": "ایمیل خود را وارد کنید", + "failed": "ورود ناموفق بود", + "forgotPassword": "رمز عبور را فراموش کرده‌اید؟", + "forgotPasswordError": "ارسال ایمیل بازیابی رمز عبور ناموفق بود", + "insecureContextWarning": "این اتصال ناامن است (HTTP) - در صورت ادامه ورود، اطلاعات شما ممکن است توسط مهاجمان رهگیری شود.", + "loginButton": "ورود", + "loginWithGithub": "ورود با Github", + "loginWithGoogle": "ورود با Google", + "newUser": "جدید هستید؟", + "noAssociatedUser": "هیچ کاربری از Comfy با کلید API ارائه‌شده مرتبط نیست", + "orContinueWith": "یا ادامه با", + "passwordLabel": "رمز عبور", + "passwordPlaceholder": "رمز عبور خود را وارد کنید", + "passwordResetError": "ارسال ایمیل بازیابی رمز عبور ناموفق بود. لطفاً دوباره تلاش کنید.", + "passwordResetInstructions": "ایمیل خود را وارد کنید تا لینک بازیابی رمز عبور برای شما ارسال شود.", + "passwordResetSent": "ایمیل بازیابی رمز عبور ارسال شد", + "passwordResetSentDetail": "لطفاً ایمیل خود را برای دریافت لینک بازیابی رمز عبور بررسی کنید.", + "privacyLink": "سیاست حفظ حریم خصوصی", + "questionsContactPrefix": "سؤالی دارید؟ با ما تماس بگیرید:", + "sendResetLink": "ارسال لینک بازیابی", + "signInOrSignUp": "ورود / ثبت‌نام", + "signUp": "ثبت‌نام", + "success": "ورود موفقیت‌آمیز بود", + "termsLink": "شرایط استفاده", + "termsText": "با کلیک بر روی «بعدی» یا «ثبت‌نام»، شما با", + "title": "ورود به حساب کاربری", + "useApiKey": "کلید Comfy API", + "userAvatar": "آواتار کاربر" + }, + "loginButton": { + "tooltipHelp": "برای استفاده از «API Nodeها» وارد شوید", + "tooltipLearnMore": "بیشتر بدانید..." + }, + "passwordUpdate": { + "success": "رمز عبور به‌روزرسانی شد", + "successDetail": "رمز عبور شما با موفقیت به‌روزرسانی شد" + }, + "reauthRequired": { + "cancel": "انصراف", + "confirm": "ورود مجدد", + "message": "به دلایل امنیتی، این عملیات نیاز به ورود مجدد دارد. آیا مایل به ادامه هستید؟", + "title": "احراز هویت مجدد لازم است" + }, + "signOut": { + "signOut": "خروج", + "success": "خروج با موفقیت انجام شد", + "successDetail": "شما با موفقیت از حساب کاربری خود خارج شدید." + }, + "signup": { + "alreadyHaveAccount": "قبلاً حساب کاربری دارید؟", + "emailLabel": "ایمیل", + "emailPlaceholder": "ایمیل خود را وارد کنید", + "passwordLabel": "رمز عبور", + "passwordPlaceholder": "رمز عبور جدید را وارد کنید", + "personalDataConsentLabel": "با پردازش داده‌های شخصی خود موافقم.", + "regionRestrictionChina": "مطابق با الزامات قانونی محلی، خدمات ما به طور موقت برای کاربران ساکن چین در دسترس نیست.", + "signIn": "ورود", + "signUpButton": "ثبت‌نام", + "signUpWithGithub": "ثبت‌نام با Github", + "signUpWithGoogle": "ثبت‌نام با Google", + "title": "ایجاد حساب کاربری" + } + }, + "boundingBox": { + "height": "ارتفاع", + "width": "عرض", + "x": "ایکس", + "y": "وای" + }, + "breadcrumbsMenu": { + "clearWorkflow": "پاک‌سازی workflow", + "deleteBlueprint": "حذف blueprint", + "deleteWorkflow": "حذف workflow", + "duplicate": "تکرار", + "enterNewName": "نام جدید را وارد کنید", + "missingNodesWarning": "workflow شامل نودهای پشتیبانی‌نشده است (با رنگ قرمز مشخص شده‌اند)." + }, + "clipboard": { + "errorMessage": "کپی به کلیپ‌بورد ناموفق بود", + "errorNotSupported": "Clipboard API در مرورگر شما پشتیبانی نمی‌شود", + "successMessage": "در کلیپ‌بورد کپی شد" + }, + "cloudFooter_needHelp": "نیاز به کمک دارید؟", + "cloudForgotPassword_backToLogin": "بازگشت به ورود", + "cloudForgotPassword_didntReceiveEmail": "ایمیلی دریافت نکردید؟", + "cloudForgotPassword_emailLabel": "ایمیل", + "cloudForgotPassword_emailPlaceholder": "ایمیل خود را وارد کنید", + "cloudForgotPassword_emailRequired": "وارد کردن ایمیل الزامی است", + "cloudForgotPassword_instructions": "آدرس ایمیل خود را وارد کنید تا لینکی برای بازنشانی رمز عبور برای شما ارسال کنیم.", + "cloudForgotPassword_passwordResetError": "ارسال ایمیل بازنشانی رمز عبور ناموفق بود", + "cloudForgotPassword_passwordResetSent": "لینک بازنشانی رمز عبور ارسال شد", + "cloudForgotPassword_sendResetLink": "ارسال لینک بازنشانی", + "cloudForgotPassword_title": "فراموشی رمز عبور", + "cloudOnboarding": { + "authTimeout": { + "causes": [ + "فایروال یا پراکسی سازمانی که سرویس‌های احراز هویت را مسدود می‌کند", + "محدودیت‌های VPN یا شبکه", + "افزونه‌های مرورگر که در درخواست‌ها اختلال ایجاد می‌کنند", + "محدودیت‌های منطقه‌ای شبکه", + "استفاده از مرورگر یا شبکه دیگر را امتحان کنید" + ], + "helpText": "نیاز به راهنمایی دارید؟ تماس با", + "message": "در اتصال به ComfyUI Cloud با مشکل مواجه شدیم. این مشکل ممکن است به دلیل کندی اتصال یا اختلال موقت سرویس باشد.", + "restart": "خروج و تلاش مجدد", + "supportLink": "پشتیبانی", + "technicalDetails": "جزئیات فنی", + "title": "اتصال بیش از حد طول کشید", + "troubleshooting": "دلایل رایج:" + }, + "checkingStatus": "در حال بررسی وضعیت حساب شما...", + "forgotPassword": { + "backToLogin": "بازگشت به ورود", + "didntReceiveEmail": "ایمیلی دریافت نکردید؟ با ما تماس بگیرید:", + "emailLabel": "ایمیل", + "emailPlaceholder": "ایمیل خود را وارد کنید", + "emailRequired": "وارد کردن ایمیل الزامی است", + "instructions": "آدرس ایمیل خود را وارد کنید تا لینک بازنشانی رمز عبور برای شما ارسال شود.", + "passwordResetError": "ارسال ایمیل بازنشانی رمز عبور ناموفق بود. لطفاً دوباره تلاش کنید.", + "passwordResetSent": "ایمیل بازنشانی رمز عبور ارسال شد", + "sendResetLink": "ارسال لینک بازنشانی", + "title": "فراموشی رمز عبور" + }, + "privateBeta": { + "desc": "برای پیوستن به لیست انتظار وارد شوید. زمانی که نوبت شما شد به شما اطلاع خواهیم داد. قبلاً اطلاع داده شده‌اید؟ وارد شوید و از Cloud استفاده کنید.", + "title": "کلود در حال حاضر در نسخه بتای خصوصی است" + }, + "retry": "تلاش دوباره", + "retrying": "در حال تلاش مجدد...", + "skipToCloudApp": "رفتن به برنامه ابری", + "start": { + "desc": "بدون نیاز به تنظیمات اولیه. روی هر دستگاهی کار می‌کند.", + "download": "دانلود ComfyUI", + "explain": "چندین خروجی را به طور همزمان تولید کنید. گردش‌کارها را به راحتی به اشتراک بگذارید.", + "learnAboutButton": "درباره Cloud بیشتر بدانید", + "title": "در چند ثانیه شروع به خلق کنید", + "wantToRun": "می‌خواهید ComfyUI را به صورت محلی اجرا کنید؟" + }, + "survey": { + "options": { + "familiarity": { + "advanced": "کاربر پیشرفته (جریان‌کارهای سفارشی)", + "basics": "آشنایی با مبانی", + "expert": "کاربر خبره (به دیگران کمک می‌کنم)", + "new": "جدید در ComfyUI (تا کنون استفاده نکرده‌ام)", + "starting": "تازه شروع کرده‌ام (در حال دنبال کردن آموزش‌ها)" + }, + "industry": { + "architecture": "معماری", + "education": "آموزش", + "film_tv_animation": "فیلم، تلویزیون و انیمیشن", + "fine_art": "هنرهای زیبا و تصویرسازی", + "gaming": "بازی‌سازی", + "marketing": "بازاریابی و تبلیغات", + "other": "سایر", + "otherPlaceholder": "لطفاً مشخص کنید", + "product_design": "طراحی محصول و گرافیک", + "software": "نرم‌افزار و فناوری" + }, + "making": { + "3d": "دارایی‌های سه‌بعدی", + "audio": "صدا / موسیقی", + "custom_nodes": "nodeها و workflowهای سفارشی", + "images": "تصاویر", + "video": "ویدیو و انیمیشن" + }, + "purpose": { + "client": "کار برای مشتری (فریلنس)", + "community": "مشارکت در جامعه (nodeها، workflowها و غیره)", + "inhouse": "محل کار خودم (درون‌سازمانی)", + "personal": "پروژه‌های شخصی / سرگرمی", + "research": "پژوهش دانشگاهی" + } + }, + "placeholder": "جای‌نگهدار سوالات نظرسنجی", + "questions": { + "familiarity": "تا چه حد با ComfyUI آشنایی دارید؟", + "industry": "صنعت اصلی شما چیست؟", + "making": "قصد دارید چه چیزی بسازید؟", + "purpose": "هدف اصلی شما از استفاده از ComfyUI چیست؟" + }, + "steps": { + "familiarity": "تا چه حد با ComfyUI آشنایی دارید؟", + "industry": "صنعت اصلی شما چیست؟", + "making": "قصد دارید چه چیزی بسازید؟", + "purpose": "هدف اصلی شما از استفاده از ComfyUI چیست؟" + }, + "title": "نظرسنجی ابری" + } + }, + "cloudPrivateBeta_desc": "برای پیوستن به لیست انتظار وارد شوید. زمانی که نوبت شما شد به شما اطلاع خواهیم داد. قبلاً اطلاع‌رسانی شده‌اید؟ وارد شوید و استفاده از کلود را آغاز کنید.", + "cloudPrivateBeta_title": "کلود در حال حاضر در نسخه بتای خصوصی است", + "cloudSorryContactSupport_title": "متاسفیم، با پشتیبانی تماس بگیرید", + "cloudStart_desc": "بدون نیاز به تنظیمات. روی هر دستگاهی کار می‌کند.", + "cloudStart_download": "دانلود ComfyUI", + "cloudStart_explain": "چندین خروجی را همزمان تولید کنید. workflowها را به راحتی به اشتراک بگذارید.", + "cloudStart_learnAboutButton": "درباره Cloud بیشتر بدانید", + "cloudStart_title": "در چند ثانیه شروع به خلق کنید", + "cloudStart_wantToRun": "مایلید ComfyUI را به صورت محلی اجرا کنید؟", + "cloudSurvey_steps_familiarity": "تا چه اندازه با ComfyUI آشنایی دارید؟", + "cloudSurvey_steps_industry": "صنعت اصلی شما چیست؟", + "cloudSurvey_steps_making": "برنامه دارید چه چیزی بسازید؟", + "cloudSurvey_steps_purpose": "عمدتاً قصد دارید از ComfyUI برای چه کاری استفاده کنید؟", + "cloudWaitlist_contactLink": "اینجا", + "cloudWaitlist_questionsText": "سؤالی دارید؟ با ما تماس بگیرید", + "color": { + "black": "مشکی", + "blue": "آبی", + "brown": "قهوه‌ای", + "custom": "سفارشی", + "cyan": "فیروزه‌ای", + "default": "پیش‌فرض", + "green": "سبز", + "noColor": "بدون رنگ", + "pale_blue": "آبی کم‌رنگ", + "pink": "صورتی", + "purple": "بنفش", + "red": "قرمز", + "yellow": "زرد" + }, + "commands": { + "clear": "پاک‌سازی workflow", + "clipspace": "باز کردن Clipspace", + "dark": "تاریک", + "execute": "اجرا", + "help": "راهنما", + "interrupt": "لغو اجرای فعلی", + "light": "روشن", + "manageExtensions": "مدیریت افزونه‌ها", + "queue": "پنل صف", + "refresh": "به‌روزرسانی تعاریف node", + "resetView": "بازنشانی نمای canvas", + "run": "اجرا", + "runWorkflow": "اجرای workflow", + "runWorkflowFront": "اجرای workflow (صف در ابتدا)", + "settings": "تنظیمات", + "theme": "پوسته", + "toggleBottomPanel": "نمایش/مخفی‌سازی پنل پایین" + }, + "contextMenu": { + "Add Group": "افزودن گروه", + "Add Group For Selected Nodes": "افزودن گروه برای Nodeهای انتخاب‌شده", + "Add Node": "افزودن Node", + "Add Subgraph to Library": "افزودن Subgraph به کتابخانه", + "Adjust Size": "تنظیم اندازه", + "Align Selected To": "تراز کردن انتخاب‌شده با", + "Bottom": "پایین", + "Bypass": "عبور", + "Clone": "شبیه‌سازی", + "Collapse": "جمع کردن", + "Color": "رنگ", + "Colors": "رنگ‌ها", + "Convert to Group Node": "تبدیل به Group Node", + "Convert to Subgraph": "تبدیل به Subgraph", + "Copy": "کپی", + "Copy (Clipspace)": "کپی (Clipspace)", + "Copy Image": "کپی تصویر", + "Delete": "حذف", + "Distribute Nodes": "توزیع Nodeها", + "Duplicate": "تکرار", + "Edit Subgraph Widgets": "ویرایش ویجت‌های Subgraph", + "Expand": "باز کردن", + "Expand Node": "بزرگ‌نمایی Node", + "Extensions": "افزونه‌ها", + "FavoriteWidget": "افزودن به علاقه‌مندی‌ها", + "Horizontal": "افقی", + "Inputs": "ورودی‌ها", + "Left": "چپ", + "Manage": "مدیریت", + "Manage Group Nodes": "مدیریت Group Nodeها", + "Minimize Node": "کوچک‌سازی Node", + "Mode": "حالت", + "Node Info": "اطلاعات Node", + "Node Templates": "قالب‌های Node", + "Open Image": "باز کردن تصویر", + "Open in Mask Editor": "باز کردن در Mask Editor", + "Outputs": "خروجی‌ها", + "Paste": "چسباندن", + "Pin": "سنجاق کردن", + "Properties": "ویژگی‌ها", + "Properties Panel": "پنل ویژگی‌ها", + "Remove": "حذف", + "Remove Bypass": "حذف عبور", + "Rename": "تغییر نام", + "RenameWidget": "تغییر نام ویجت", + "Resize": "تغییر اندازه", + "Right": "راست", + "Run Branch": "اجرای شاخه", + "Save Image": "ذخیره تصویر", + "Save Selected as Template": "ذخیره انتخاب‌شده به عنوان قالب", + "Search": "جستجو", + "Shape": "شکل", + "Shapes": "اشکال", + "Title": "عنوان", + "Top": "بالا", + "UnfavoriteWidget": "حذف از علاقه‌مندی‌ها", + "Unpack Subgraph": "باز کردن Subgraph", + "Unpin": "برداشتن سنجاق", + "Vertical": "عمودی", + "deprecated": "منسوخ", + "new": "جدید" + }, + "credits": { + "accountInitialized": "حساب کاربری فعال شد", + "activity": "فعالیت", + "added": "افزوده شد", + "additionalInfo": "اطلاعات تکمیلی", + "apiPricing": "قیمت‌گذاری API", + "credits": "اعتبار", + "creditsAvailable": "اعتبار موجود", + "details": "جزئیات", + "eventType": "نوع رویداد", + "faqs": "سؤالات متداول", + "invoiceHistory": "تاریخچه فاکتورها", + "lastUpdated": "آخرین به‌روزرسانی", + "messageSupport": "پشتیبانی پیامکی", + "model": "مدل", + "purchaseCredits": "خرید اعتبار", + "refreshes": "به‌روزرسانی {date}", + "time": "زمان", + "topUp": { + "addMoreCredits": "افزودن اعتبار بیشتر", + "addMoreCreditsToRun": "برای اجرا اعتبار بیشتری اضافه کنید", + "amountToPayLabel": "مبلغ پرداختی به دلار", + "buy": "خرید", + "buyCredits": "ادامه به پرداخت", + "buyNow": "هم‌اکنون خرید کنید", + "contactUs": "با ما تماس بگیرید", + "creditsDescription": "اعتبارها برای اجرای workflow یا nodeهای شریک استفاده می‌شوند.", + "creditsPerDollar": "اعتبار به ازای هر دلار", + "creditsToReceiveLabel": "اعتبار دریافتی", + "howManyCredits": "چه تعداد اعتبار می‌خواهید اضافه کنید؟", + "insufficientMessage": "شما اعتبار کافی برای اجرای این workflow ندارید.", + "insufficientTitle": "اعتبار کافی نیست", + "insufficientWorkflowMessage": "شما اعتبار کافی برای اجرای این workflow ندارید.", + "maxAllowed": "حداکثر {credits} اعتبار.", + "maxAmount": "(حداکثر ۱٬۰۰۰ دلار آمریکا)", + "maximumAmount": "حداکثر {amount}", + "minRequired": "حداقل {credits} اعتبار", + "minimumPurchase": "حداقل {amount} ({credits} اعتبار)", + "needMore": "بیشتر نیاز دارید؟", + "purchaseError": "خرید ناموفق بود", + "purchaseErrorDetail": "خرید اعتبار ناموفق بود: {error}", + "quickPurchase": "خرید سریع", + "seeDetails": "مشاهده جزئیات", + "selectAmount": "انتخاب مبلغ", + "templateNote": "*تولید شده با قالب Wan Fun Control", + "topUp": "افزایش اعتبار", + "unknownError": "خطای ناشناخته رخ داد", + "usdAmount": "{amount} دلار", + "videosEstimate": "~{count} ویدیو*", + "viewPricing": "مشاهده جزئیات قیمت‌گذاری", + "youGet": "اعتبار", + "youPay": "مبلغ (دلار آمریکا)" + }, + "unified": { + "message": "اعتبارها یکپارچه شدند", + "tooltip": "پرداخت‌ها در Comfy یکپارچه شده‌اند. اکنون همه چیز با اعتبار Comfy انجام می‌شود:\n- nodeهای شریک (قبلاً nodeهای API)\n- workflowهای ابری\n\nموجودی nodeهای شریک شما به اعتبار تبدیل شده است." + }, + "yourCreditBalance": "موجودی اعتبار شما" + }, + "dataTypes": { + "*": "*", + "AUDIO": "صوت", + "AUDIO_ENCODER": "رمزگذار صوت", + "AUDIO_ENCODER_OUTPUT": "خروجی رمزگذار صوت", + "AUDIO_RECORD": "ضبط صوت", + "BOOLEAN": "بولی", + "CAMERA_CONTROL": "کنترل دوربین", + "CLIP": "clip", + "CLIP_VISION": "بینایی clip", + "CLIP_VISION_OUTPUT": "خروجی بینایی clip", + "COMBO": "ترکیبی", + "COMFY_AUTOGROW_V3": "Comfy AutoGrow V3", + "COMFY_DYNAMICCOMBO_V3": "Comfy DynamicCombo V3", + "COMFY_MATCHTYPE_V3": "Comfy MatchType V3", + "CONDITIONING": "شرط‌گذاری", + "CONTROL_NET": "controlnet", + "FLOAT": "عدد اعشاری", + "FLOATS": "اعداد اعشاری", + "GEMINI_INPUT_FILES": "فایل‌های ورودی Gemini", + "GLIGEN": "GLIGEN", + "GUIDER": "راهنما", + "HOOKS": "hookها", + "HOOK_KEYFRAMES": "کلیدفریم‌های hook", + "IMAGE": "تصویر", + "IMAGECOMPARE": "مقایسه تصویر", + "INT": "عدد صحیح", + "LATENT": "latent", + "LATENT_OPERATION": "عملیات latent", + "LATENT_UPSCALE_MODEL": "مدل بزرگ‌نمایی latent", + "LOAD3D_CAMERA": "دوربین بارگذاری سه‌بعدی", + "LOAD_3D": "بارگذاری سه‌بعدی", + "LORA_MODEL": "مدل lora", + "LOSS_MAP": "نقشه خطا", + "LUMA_CONCEPTS": "مفاهیم Luma", + "LUMA_REF": "مرجع Luma", + "MASK": "ماسک", + "MESH": "مش", + "MESHY_RIGGED_TASK_ID": "MESHY_RIGGED_TASK_ID", + "MESHY_TASK_ID": "MESHY_TASK_ID", + "MODEL": "مدل", + "MODEL_PATCH": "وصله مدل", + "MODEL_TASK_ID": "شناسه وظیفه مدل", + "NOISE": "نویز", + "OPENAI_CHAT_CONFIG": "پیکربندی گفتگوی OpenAI", + "OPENAI_INPUT_FILES": "فایل‌های ورودی OpenAI", + "PHOTOMAKER": "photomaker", + "PIXVERSE_TEMPLATE": "قالب Pixverse", + "RECRAFT_COLOR": "رنگ Recraft", + "RECRAFT_CONTROLS": "کنترل‌های Recraft", + "RECRAFT_V3_STYLE": "سبک Recraft V3", + "RETARGET_TASK_ID": "شناسه وظیفه Retarget", + "RIG_TASK_ID": "شناسه وظیفه Rig", + "SAMPLER": "نمونه‌گیر", + "SIGMAS": "سیگماها", + "STRING": "رشته", + "STYLE_MODEL": "مدل سبک", + "SVG": "SVG", + "TIMESTEPS_RANGE": "بازه گام‌های زمانی", + "TRACKS": "ترک‌ها", + "UPSCALE_MODEL": "مدل بزرگ‌نمایی", + "VAE": "vae", + "VIDEO": "ویدیو", + "VOXEL": "وُکسل", + "WAN_CAMERA_EMBEDDING": "جاسازی دوربین WAN", + "WEBCAM": "وب‌کم" + }, + "desktopDialogs": { + "": { + "buttons": { + "Close": "بستن" + }, + "message": "شناسه گفت‌وگوی نامعتبری ارائه شده است.", + "title": "گفت‌وگوی نامعتبر" + } + }, + "desktopMenu": { + "confirmQuit": "جریان‌های کاری ذخیره‌نشده باز هستند؛ هرگونه تغییر ذخیره‌نشده از بین خواهد رفت. آیا می‌خواهید بدون توجه به این موضوع خارج شوید؟", + "confirmReinstall": "این کار فایل extra_models_config.yaml شما را پاک می‌کند\nو نصب را دوباره آغاز می‌کند.\n\nآیا مطمئن هستید؟", + "quit": "خروج", + "reinstall": "نصب مجدد" + }, + "desktopStart": { + "initialising": "در حال راه‌اندازی..." + }, + "desktopUpdate": { + "description": "ComfyUI Desktop در حال نصب وابستگی‌های جدید است. این فرایند ممکن است چند دقیقه طول بکشد.", + "errorCheckingUpdate": "خطا در بررسی به‌روزرسانی‌ها", + "errorInstallingUpdate": "خطا در نصب به‌روزرسانی", + "noUpdateFound": "هیچ به‌روزرسانی یافت نشد", + "terminalDefaultMessage": "هر خروجی کنسول از به‌روزرسانی در اینجا نمایش داده می‌شود.", + "title": "در حال به‌روزرسانی ComfyUI Desktop", + "updateAvailableMessage": "یک به‌روزرسانی موجود است. آیا می‌خواهید اکنون راه‌اندازی مجدد و به‌روزرسانی کنید؟", + "updateFoundTitle": "به‌روزرسانی یافت شد (نسخه {version})" + }, + "downloadGit": { + "gitWebsite": "دانلود git", + "instructions": "لطفاً آخرین نسخه مناسب با سیستم‌عامل خود را دانلود و نصب کنید. دکمه دانلود git در زیر، صفحه دانلود سایت git-scm.com را باز می‌کند.", + "message": "امکان یافتن git وجود ندارد. یک نسخه فعال از git برای عملکرد عادی مورد نیاز است.", + "skip": "رد کردن", + "title": "دانلود git", + "warning": "اگر مطمئن هستید که به نصب git نیازی ندارید یا اشتباهی رخ داده است، می‌توانید روی رد کردن کلیک کنید تا این بررسی نادیده گرفته شود. اجرای ComfyUI بدون نسخه فعال git در حال حاضر پشتیبانی نمی‌شود." + }, + "electronFileDownload": { + "cancel": "لغو دانلود", + "cancelled": "لغو شد", + "inProgress": "در حال انجام", + "pause": "توقف دانلود", + "paused": "متوقف شده", + "resume": "ادامه دانلود" + }, + "errorDialog": { + "defaultTitle": "خطایی رخ داد", + "extensionFileHint": "این ممکن است به دلیل اسکریپت زیر باشد", + "loadWorkflowTitle": "بارگذاری به دلیل خطا در بارگذاری مجدد داده‌های workflow متوقف شد", + "noStackTrace": "هیچ stacktraceی موجود نیست", + "promptExecutionError": "اجرای prompt با شکست مواجه شد" + }, + "g": { + "1x": "۱x", + "2x": "۲x", + "about": "درباره", + "add": "افزودن", + "addNodeFilterCondition": "افزودن شرط فیلتر node", + "all": "همه", + "amount": "مقدار", + "apply": "اعمال", + "architecture": "معماری", + "asset": "{count} دارایی | {count} دارایی | {count} دارایی", + "audioFailedToLoad": "بارگذاری صوت ناموفق بود", + "audioProgress": "پیشرفت صوت", + "author": "نویسنده", + "back": "بازگشت", + "batchRename": "تغییر نام گروهی", + "beta": "آزمایشی", + "bookmark": "ذخیره در کتابخانه", + "calculatingDimensions": "در حال محاسبه ابعاد", + "cancel": "لغو", + "cancelled": "لغو شده", + "capture": "گرفتن", + "category": "دسته‌بندی", + "chart": "نمودار", + "chartLowercase": "نمودار", + "choose_file_to_upload": "انتخاب فایل برای بارگذاری", + "clear": "پاک‌سازی", + "clearAll": "پاک‌سازی همه", + "clearFilters": "پاک‌سازی فیلترها", + "close": "بستن", + "closeDialog": "بستن پنجره", + "color": "رنگ", + "comfy": "Comfy", + "comfyOrgLogoAlt": "لوگوی ComfyOrg", + "comingSoon": "به‌زودی", + "command": "دستور", + "commandProhibited": "دستور {command} مجاز نیست. برای اطلاعات بیشتر با مدیر تماس بگیرید.", + "community": "انجمن", + "completed": "تکمیل شده", + "confirm": "تأیید", + "confirmed": "تأیید شد", + "content": "محتوا", + "continue": "ادامه", + "control_after_generate": "کنترل پس از تولید", + "control_before_generate": "کنترل پیش از تولید", + "copied": "کپی شد", + "copy": "کپی", + "copyAll": "کپی همه", + "copyJobId": "کپی شناسه وظیفه", + "copyToClipboard": "کپی در کلیپ‌بورد", + "copyURL": "کپی آدرس", + "core": "هسته", + "currentUser": "کاربر فعلی", + "custom": "سفارشی", + "customBackground": "پس‌زمینه سفارشی", + "customize": "سفارشی‌سازی", + "customizeFolder": "سفارشی‌سازی پوشه", + "decrement": "کاهش", + "defaultBanner": "بنر پیش‌فرض", + "delete": "حذف", + "deleteAudioFile": "حذف فایل صوتی", + "deleteImage": "حذف تصویر", + "deprecated": "منسوخ", + "description": "توضیحات", + "devices": "دستگاه‌ها", + "disableAll": "غیرفعال‌سازی همه", + "disableSelected": "غیرفعال‌سازی انتخاب‌شده‌ها", + "disableThirdParty": "غیرفعال‌سازی شخص ثالث", + "disabling": "در حال غیرفعال‌سازی {id}", + "dismiss": "رد کردن", + "download": "دانلود", + "downloadImage": "دانلود تصویر", + "downloadVideo": "دانلود ویدیو", + "downloading": "در حال دانلود", + "dropYourFileOr": "فایل خود را رها کنید یا", + "duplicate": "تکراری", + "edit": "ویرایش", + "editImage": "ویرایش تصویر", + "editOrMaskImage": "ویرایش یا mask تصویر", + "emDash": "—", + "empty": "خالی", + "enableAll": "فعال‌سازی همه", + "enableOrDisablePack": "فعال یا غیرفعال کردن بسته", + "enableSelected": "فعال‌سازی انتخاب‌شده‌ها", + "enabled": "فعال", + "enabling": "در حال فعال‌سازی {id}", + "enterBaseName": "نام پایه را وارد کنید", + "enterNewName": "نام جدید را وارد کنید", + "error": "خطا", + "errorLoadingImage": "خطا در بارگذاری تصویر", + "errorLoadingVideo": "خطا در بارگذاری ویدیو", + "experimental": "آزمایشی", + "export": "خروجی گرفتن", + "extensionName": "نام افزونه", + "failed": "ناموفق", + "failedToCopyJobId": "کپی شناسه وظیفه ناموفق بود", + "failedToDownloadImage": "دانلود تصویر ناموفق بود", + "failedToDownloadVideo": "دانلود ویدیو ناموفق بود", + "feedback": "بازخورد", + "file": "فایل", + "filter": "فیلتر", + "findIssues": "یافتن مشکلات", + "frameNodes": "قاب‌بندی nodeها", + "frontendNewer": "نسخه فرانت‌اند {frontendVersion} ممکن است با نسخه بک‌اند {backendVersion} ناسازگار باشد.", + "frontendOutdated": "نسخه فرانت‌اند {frontendVersion} قدیمی است. بک‌اند به نسخه {requiredVersion} یا بالاتر نیاز دارد.", + "galleryImage": "تصویر گالری", + "galleryThumbnail": "تصویر بندانگشتی گالری", + "goToNode": "رفتن به node", + "graphNavigation": "ناوبری گراف", + "halfSpeed": "۰.۵x", + "hideLeftPanel": "پنهان کردن پنل چپ", + "hideRightPanel": "پنهان کردن پنل راست", + "icon": "آیکون", + "imageFailedToLoad": "بارگذاری تصویر ناموفق بود", + "imagePreview": "پیش‌نمایش تصویر - برای جابجایی بین تصاویر از کلیدهای جهت‌دار استفاده کنید", + "imageUrl": "آدرس تصویر", + "import": "وارد کردن", + "inProgress": "در حال انجام", + "increment": "افزایش", + "info": "اطلاعات node", + "insert": "درج", + "install": "نصب", + "installed": "نصب شده", + "installing": "در حال نصب", + "interrupted": "متوقف شده", + "itemSelected": "{selectedCount} مورد انتخاب شد", + "itemsCopiedToClipboard": "موارد در کلیپ‌بورد کپی شدند", + "itemsSelected": "{selectedCount} مورد انتخاب شدند", + "job": "وظیفه", + "jobIdCopied": "شناسه وظیفه در کلیپ‌بورد کپی شد", + "keybinding": "کلید میانبر", + "keybindingAlreadyExists": "کلید میانبر قبلاً وجود دارد در", + "learnMore": "اطلاعات بیشتر", + "listening": "در حال گوش دادن...", + "liveSamplingPreview": "پیش‌نمایش زنده نمونه‌گیری", + "loadAllFolders": "بارگذاری همه پوشه‌ها", + "loadWorkflow": "بارگذاری workflow", + "loading": "در حال بارگذاری", + "loadingPanel": "در حال بارگذاری پنل {panel}...", + "login": "ورود", + "logoAlt": "لوگوی ComfyUI", + "logs": "گزارش‌ها", + "markdown": "markdown", + "micPermissionDenied": "دسترسی میکروفون رد شد", + "migrate": "مهاجرت", + "missing": "ناقص", + "more": "بیشتر", + "moreOptions": "گزینه‌های بیشتر", + "moreWorkflows": "workflowهای بیشتر", + "multiSelectDropdown": "لیست کشویی چندانتخابی", + "name": "نام", + "newFolder": "پوشه جدید", + "next": "بعدی", + "nightly": "نسخه شبانه", + "no": "خیر", + "noAudioRecorded": "هیچ صدایی ضبط نشد", + "noItems": "هیچ موردی وجود ندارد", + "noResults": "بدون نتیجه", + "noResultsFound": "نتیجه‌ای یافت نشد", + "noTasksFound": "هیچ وظیفه‌ای یافت نشد", + "noTasksFoundMessage": "هیچ وظیفه‌ای در صف وجود ندارد.", + "noWorkflowsFound": "هیچ workflowی یافت نشد.", + "nodeContentError": "خطا در محتوای node", + "nodeHeaderError": "خطا در سربرگ node", + "nodeRenderError": "خطا در رندر node", + "nodeSlotsError": "خطا در slotهای node", + "nodeWidgetsError": "خطا در ابزارک‌های node", + "nodes": "nodeها", + "nodesCount": "{count} نود | {count} نود | {count} نود", + "nodesRunning": "nodeها در حال اجرا هستند", + "none": "هیچ‌کدام", + "nothingToCopy": "موردی برای کپی وجود ندارد", + "nothingToDelete": "موردی برای حذف وجود ندارد", + "nothingToDuplicate": "موردی برای تکرار وجود ندارد", + "nothingToRename": "موردی برای تغییر نام وجود ندارد", + "ok": "تأیید", + "openManager": "باز کردن مدیریت", + "openNewIssue": "ایجاد گزارش جدید", + "or": "یا", + "overwrite": "جایگزینی", + "playPause": "پخش/توقف", + "playRecording": "پخش ضبط", + "playbackSpeed": "سرعت پخش", + "playing": "در حال پخش", + "pressKeysForNewBinding": "کلیدهای میانبر جدید را فشار دهید", + "preview": "پیش‌نمایش", + "profile": "پروفایل", + "progressCountOf": "از", + "queued": "در صف", + "ready": "آماده", + "reconnected": "اتصال مجدد برقرار شد", + "reconnecting": "در حال اتصال مجدد", + "refresh": "بازنشانی", + "refreshNode": "بازنشانی node", + "relativeTime": { + "daysAgo": "{count} روز پیش", + "hoursAgo": "{count} ساعت پیش", + "minutesAgo": "{count} دقیقه پیش", + "monthsAgo": "{count} ماه پیش", + "now": "اکنون", + "weeksAgo": "{count} هفته پیش", + "yearsAgo": "{count} سال پیش" + }, + "releaseTitle": "انتشار {package} نسخه {version}", + "reloadToApplyChanges": "برای اعمال تغییرات بارگذاری مجدد کنید", + "removeImage": "حذف تصویر", + "removeTag": "حذف برچسب", + "removeVideo": "حذف ویدیو", + "rename": "تغییر نام", + "reportIssue": "ارسال گزارش", + "reportIssueTooltip": "گزارش خطا را به Comfy Org ارسال کنید", + "reportSent": "گزارش ارسال شد", + "reset": "بازنشانی", + "resetAll": "بازنشانی همه", + "resetAllKeybindingsTooltip": "بازنشانی همه کلیدهای میانبر به حالت پیش‌فرض", + "resizeFromBottomLeft": "تغییر اندازه از گوشه پایین-چپ", + "resizeFromBottomRight": "تغییر اندازه از گوشه پایین-راست", + "resizeFromTopLeft": "تغییر اندازه از گوشه بالا-چپ", + "resizeFromTopRight": "تغییر اندازه از گوشه بالا-راست", + "restart": "راه‌اندازی مجدد", + "resultsCount": "{count} نتیجه یافت شد", + "running": "در حال اجرا", + "save": "ذخیره", + "saving": "در حال ذخیره", + "scrollLeft": "اسکرول به چپ", + "scrollRight": "اسکرول به راست", + "search": "جستجو", + "searchExtensions": "جستجوی افزونه‌ها", + "searchFailedMessage": "تنظیماتی مطابق با جستجوی شما یافت نشد. لطفاً عبارت جستجو را تغییر دهید.", + "searchKeybindings": "جستجوی کلیدهای میانبر", + "searchModels": "جستجوی مدل‌ها", + "searchNodes": "جستجوی nodeها", + "searchPlaceholder": "جستجو...", + "searchSettings": "جستجوی تنظیمات", + "searchWorkflows": "جستجوی workflowها", + "seeTutorial": "مشاهده آموزش", + "selectItemsToCopy": "مواردی برای کپی انتخاب کنید", + "selectItemsToDelete": "مواردی برای حذف انتخاب کنید", + "selectItemsToDuplicate": "مواردی برای تکرار انتخاب کنید", + "selectItemsToRename": "مواردی برای تغییر نام انتخاب کنید", + "selectedFile": "فایل انتخاب‌شده", + "setAsBackground": "تنظیم به عنوان پس‌زمینه", + "settings": "تنظیمات", + "showLeftPanel": "نمایش پنل چپ", + "showReport": "نمایش گزارش", + "showRightPanel": "نمایش پنل راست", + "singleSelectDropdown": "لیست کشویی تک‌انتخابی", + "sort": "مرتب‌سازی", + "source": "منبع", + "startRecording": "شروع ضبط", + "status": "وضعیت", + "stopPlayback": "توقف پخش", + "stopRecording": "پایان ضبط", + "submit": "ارسال", + "success": "موفقیت‌آمیز", + "systemInfo": "اطلاعات سیستم", + "terminal": "ترمینال", + "title": "عنوان", + "triggerPhrase": "عبارت trigger", + "unknownError": "خطای ناشناخته", + "untitled": "بدون عنوان", + "update": "به‌روزرسانی", + "updateAvailable": "به‌روزرسانی موجود است", + "updateFrontend": "به‌روزرسانی فرانت‌اند", + "updated": "به‌روزرسانی شد", + "updating": "در حال به‌روزرسانی {id}", + "upload": "بارگذاری", + "usageHint": "راهنمای استفاده", + "use": "استفاده", + "user": "کاربر", + "versionMismatchWarning": "هشدار ناسازگاری نسخه", + "versionMismatchWarningMessage": "{warning}: {detail} برای راهنمای به‌روزرسانی به https://docs.comfy.org/installation/update_comfyui#common-update-issues مراجعه کنید.", + "videoFailedToLoad": "بارگذاری ویدیو ناموفق بود", + "videoPreview": "پیش‌نمایش ویدیو - برای جابجایی بین ویدیوها از کلیدهای جهت‌دار استفاده کنید", + "viewImageOfTotal": "مشاهده تصویر {index} از {total}", + "viewVideoOfTotal": "مشاهده ویدیو {index} از {total}", + "volume": "حجم صدا", + "warning": "هشدار", + "workflow": "workflow", + "you": "شما" + }, + "graphCanvasMenu": { + "fitView": "تطبیق با نما", + "focusMode": "حالت تمرکز", + "hand": "دست", + "hideLinks": "مخفی‌سازی پیوندها", + "panMode": "حالت جابجایی", + "resetView": "بازنشانی نما", + "select": "انتخاب", + "selectMode": "حالت انتخاب", + "showLinks": "نمایش پیوندها", + "toggleLinkVisibility": "تغییر نمایش پیوندها", + "toggleMinimap": "نمایش/مخفی‌سازی نقشه کوچک", + "zoomIn": "بزرگ‌نمایی", + "zoomOptions": "گزینه‌های بزرگ‌نمایی", + "zoomOut": "کوچک‌نمایی" + }, + "groupNode": { + "create": "ایجاد گره گروهی", + "enterName": "نام را وارد کنید" + }, + "help": { + "helpCenterMenu": "منوی مرکز راهنما", + "recentReleases": "انتشارهای اخیر" + }, + "helpCenter": { + "clickToLearnMore": "برای اطلاعات بیشتر کلیک کنید →", + "desktopUserGuide": "راهنمای کاربر دسکتاپ", + "docs": "مستندات", + "feedback": "ارسال بازخورد", + "github": "گیت‌هاب", + "help": "راهنما و پشتیبانی", + "loadingReleases": "در حال بارگذاری نسخه‌ها...", + "managerExtension": "افزونه مدیریت", + "more": "بیشتر...", + "noRecentReleases": "نسخه جدیدی وجود ندارد", + "openDevTools": "باز کردن ابزار توسعه‌دهنده", + "recentReleases": "نسخه‌های اخیر", + "reinstall": "نصب مجدد", + "updateAvailable": "به‌روزرسانی", + "updateComfyUI": "به‌روزرسانی ComfyUI", + "updateComfyUIFailed": "به‌روزرسانی ComfyUI ناموفق بود. لطفاً دوباره تلاش کنید.", + "updateComfyUIStarted": "به‌روزرسانی آغاز شد", + "updateComfyUIStartedDetail": "به‌روزرسانی ComfyUI در صف قرار گرفت. لطفاً منتظر بمانید...", + "updateComfyUISuccess": "به‌روزرسانی کامل شد", + "updateComfyUISuccessDetail": "ComfyUI به‌روزرسانی شد. در حال راه‌اندازی مجدد...", + "whatsNew": "چه خبر جدیدی است؟" + }, + "icon": { + "bookmark": "نشانک", + "box": "جعبه", + "briefcase": "کیف اداری", + "exclamation-triangle": "هشدار", + "file": "فایل", + "folder": "پوشه", + "heart": "قلب", + "inbox": "صندوق ورودی", + "star": "ستاره" + }, + "imageCompare": { + "noImages": "تصویری برای مقایسه وجود ندارد" + }, + "imageCrop": { + "cropPreviewAlt": "پیش‌نمایش برش", + "loading": "در حال بارگذاری...", + "noInputImage": "هیچ تصویر ورودی متصل نیست" + }, + "importFailed": { + "copyError": "خطا در کپی", + "title": "وارد کردن ناموفق بود" + }, + "install": { + "appDataLocationTooltip": "پوشه داده‌های برنامه ComfyUI. شامل:\n- لاگ‌ها\n- تنظیمات سرور", + "appPathLocationTooltip": "پوشه دارایی‌های برنامه ComfyUI. شامل کد و دارایی‌های ComfyUI", + "cannotWrite": "امکان نوشتن در مسیر انتخاب‌شده وجود ندارد", + "chooseInstallationLocation": "انتخاب محل نصب", + "customNodes": "nodeهای سفارشی", + "customNodesDescription": "نصب مجدد nodeهای سفارشی از نصب‌های قبلی ComfyUI.", + "desktopAppSettings": "تنظیمات برنامه دسکتاپ", + "desktopAppSettingsDescription": "نحوه عملکرد ComfyUI روی دسکتاپ خود را پیکربندی کنید. می‌توانید این تنظیمات را بعداً تغییر دهید.", + "desktopSettings": "تنظیمات دسکتاپ", + "failedToSelectDirectory": "انتخاب پوشه ناموفق بود", + "gpu": "GPU", + "gpuPicker": { + "amdDescription": "از GPU ای‌ام‌دی خود با شتاب ROCm™ برای بهترین عملکرد استفاده کنید.", + "amdSubtitle": "AMD ROCm™", + "appleMetalDescription": "از GPU مک شما برای سرعت بیشتر و تجربه بهتر استفاده می‌کند.", + "cpuDescription": "حالت CPU برای سازگاری زمانی که شتاب GPU در دسترس نیست.", + "cpuSubtitle": "حالت CPU", + "manualDescription": "پیکربندی ComfyUI به صورت دستی برای تنظیمات پیشرفته یا سخت‌افزارهای پشتیبانی‌نشده", + "manualSubtitle": "پیکربندی دستی", + "nvidiaDescription": "از GPU انویدیا خود با شتاب CUDA برای بهترین عملکرد استفاده کنید.", + "nvidiaSubtitle": "NVIDIA CUDA", + "recommended": "توصیه‌شده", + "title": "انتخاب پیکربندی سخت‌افزاری" + }, + "gpuSelection": { + "cpuMode": "حالت CPU", + "cpuModeDescription": "حالت CPU فقط برای توسعه‌دهندگان و موارد خاص نادر است.", + "cpuModeDescription2": "اگر کاملاً مطمئن نیستید که به این حالت نیاز دارید، این گزینه را نادیده بگیرید و GPU خود را انتخاب کنید.", + "customComfyNeedsPython": "ComfyUI تا زمانی که پایتون نصب نشود کار نخواهد کرد", + "customInstallRequirements": "نصب تمام پیش‌نیازها و وابستگی‌ها (مثلاً torch سفارشی)", + "customManualVenv": "پیکربندی دستی venv پایتون", + "customMayNotWork": "این حالت کاملاً پشتیبانی‌نشده است و ممکن است به‌درستی کار نکند", + "customSkipsPython": "این گزینه نصب معمول پایتون را رد می‌کند.", + "enableCpuMode": "فعال‌سازی حالت CPU", + "mpsDescription": "Apple Metal Performance Shaders با نسخه nightly از pytorch پشتیبانی می‌شود.", + "nvidiaDescription": "دستگاه‌های NVIDIA به طور مستقیم با نسخه‌های CUDA از pytorch پشتیبانی می‌شوند.", + "selectGpu": "انتخاب GPU", + "selectGpuDescription": "نوع GPU خود را انتخاب کنید" + }, + "helpImprove": "لطفاً به بهبود ComfyUI کمک کنید", + "insideAppInstallDir": "این پوشه داخل بسته برنامه دسکتاپ ComfyUI قرار دارد و هنگام به‌روزرسانی حذف خواهد شد. پوشه‌ای خارج از پوشه نصب مانند Documents/ComfyUI انتخاب کنید.", + "insideUpdaterCache": "این پوشه داخل کش به‌روزرسانی‌کننده ComfyUI است که در هر به‌روزرسانی پاک می‌شود. محل دیگری را برای داده‌های خود انتخاب کنید.", + "installLocation": "محل نصب", + "installLocationDescription": "پوشه‌ای را برای داده‌های کاربری ComfyUI انتخاب کنید. محیط پایتون در محل انتخاب‌شده نصب خواهد شد.", + "installLocationTooltip": "پوشه داده‌های کاربری ComfyUI. شامل:\n- محیط پایتون\n- مدل‌ها\n- nodeهای سفارشی\n", + "insufficientFreeSpace": "فضای کافی وجود ندارد - حداقل فضای آزاد", + "isOneDrive": "OneDrive پشتیبانی نمی‌شود. لطفاً ComfyUI را در محل دیگری نصب کنید.", + "locationPicker": { + "chooseDownloadServers": "انتخاب دستی سرورهای دانلود", + "downloadServersDescription": "سرورهای آینه‌ای خاصی را برای دانلود Python، بسته‌های PyPI و PyTorch بر اساس موقعیت مکانی خود انتخاب کنید.", + "migrateDescription": "مدل‌ها، nodeهای سفارشی و تنظیمات خود را از نصب قبلی ComfyUI کپی یا لینک کنید.", + "migrateFromExisting": "انتقال از نصب موجود", + "migrationPathPlaceholder": "انتخاب نصب موجود ComfyUI (اختیاری)", + "pathPlaceholder": "/Users/username/Documents/ComfyUI", + "subtitle": "یک پوشه برای فایل‌های ComfyUI انتخاب کنید. پایتون نیز به طور خودکار در آنجا نصب خواهد شد.", + "title": "انتخاب محل نصب ComfyUI" + }, + "manualConfiguration": { + "createVenv": "باید یک محیط مجازی (virtual environment) در مسیر زیر ایجاد کنید", + "requirements": "پیش‌نیازها", + "restartWhenFinished": "پس از اتمام پیکربندی محیط مجازی، لطفاً ComfyUI را مجدداً راه‌اندازی کنید.", + "title": "پیکربندی دستی", + "virtualEnvironmentPath": "مسیر محیط مجازی" + }, + "metricsDisabled": "جمع‌آوری داده غیرفعال است", + "metricsEnabled": "جمع‌آوری داده فعال است", + "migrateFromExistingInstallation": "انتقال از نصب موجود", + "migration": "انتقال", + "migrationOptional": "انتقال اختیاری است. اگر نصب قبلی ندارید، می‌توانید این مرحله را رد کنید.", + "migrationSourcePathDescription": "اگر نصب قبلی ComfyUI دارید، می‌توانیم فایل‌های کاربری و مدل‌های شما را به نصب جدید کپی یا لینک کنیم. نصب قبلی شما تحت تأثیر قرار نخواهد گرفت.", + "moreInfo": "برای اطلاعات بیشتر، لطفاً", + "nonDefaultDrive": "لطفاً ComfyUI را روی درایو سیستم خود نصب کنید (مثلاً C:\\). درایوهایی با فایل‌سیستم متفاوت ممکن است مشکلات غیرقابل پیش‌بینی ایجاد کنند. مدل‌ها و سایر فایل‌ها را می‌توانید پس از نصب در درایوهای دیگر ذخیره کنید.", + "parentMissing": "مسیر وجود ندارد - ابتدا پوشه والد را ایجاد کنید", + "pathExists": "پوشه از قبل وجود دارد - لطفاً مطمئن شوید که از تمام داده‌ها نسخه پشتیبان تهیه کرده‌اید", + "pathValidationFailed": "اعتبارسنجی مسیر ناموفق بود", + "privacyPolicy": "سیاست حفظ حریم خصوصی", + "selectItemsToMigrate": "انتخاب موارد برای انتقال", + "settings": { + "allowMetrics": "جمع‌آوری داده‌های استفاده", + "allowMetricsDescription": "با ارسال داده‌های ناشناس استفاده، به بهبود ComfyUI کمک کنید. هیچ اطلاعات شخصی یا محتوای workflow جمع‌آوری نمی‌شود.", + "autoUpdate": "به‌روزرسانی خودکار", + "autoUpdateDescription": "به‌روزرسانی‌ها را به‌صورت خودکار دانلود کنید. قبل از نصب به‌روزرسانی‌ها به شما اطلاع داده خواهد شد.", + "checkingMirrors": "در حال بررسی دسترسی شبکه به آینه‌های پایتون...", + "dataCollectionDialog": { + "collect": { + "errorReports": "پیام خطا و stack trace", + "systemInfo": "سخت‌افزار، نوع سیستم‌عامل و نسخه برنامه", + "userJourneyEvents": "رویدادهای مسیر کاربر" + }, + "doNotCollect": { + "customNodeConfigurations": "تنظیمات nodeهای سفارشی", + "fileSystemInformation": "اطلاعات فایل‌سیستم", + "personalInformation": "اطلاعات شخصی", + "workflowContents": "محتوای workflow" + }, + "title": "درباره جمع‌آوری داده‌ها", + "viewFullPolicy": "مشاهده سیاست کامل", + "whatWeCollect": "چه چیزی جمع‌آوری می‌کنیم:", + "whatWeDoNotCollect": "چه چیزی جمع‌آوری نمی‌کنیم:" + }, + "errorUpdatingConsent": "خطا در به‌روزرسانی رضایت", + "errorUpdatingConsentDetail": "به‌روزرسانی تنظیمات رضایت جمع‌آوری داده‌ها ناموفق بود", + "learnMoreAboutData": "اطلاعات بیشتر درباره جمع‌آوری داده‌ها", + "mirrorSettings": "تنظیمات آینه", + "mirrorsReachable": "دسترسی شبکه به آینه‌های پایتون مناسب است", + "mirrorsUnreachable": "دسترسی شبکه به برخی آینه‌های پایتون مناسب نیست", + "pypiMirrorPlaceholder": "آدرس آینه PyPI را وارد کنید", + "pythonMirrorPlaceholder": "آدرس آینه Python را وارد کنید" + }, + "systemLocations": "محل‌های سیستمی", + "unhandledError": "خطای ناشناخته", + "updateConsent": "قبلاً با گزارش خطاها موافقت کرده‌اید. اکنون رویدادهای مبتنی بر رخداد برای شناسایی باگ‌ها و بهبود برنامه جمع‌آوری می‌شود. هیچ اطلاعات شناسایی شخصی جمع‌آوری نمی‌شود." + }, + "issueReport": { + "helpFix": "کمک به رفع این مشکل" + }, + "linearMode": { + "beta": "بتا - ارسال بازخورد", + "downloadAll": "دانلود همه", + "dragAndDropImage": "تصویر را بکشید و رها کنید", + "graphMode": "حالت گراف", + "linearMode": "حالت ساده", + "rerun": "اجرای مجدد", + "reuseParameters": "استفاده مجدد از پارامترها", + "runCount": "تعداد اجرا: " + }, + "load3d": { + "applyingTexture": "در حال اعمال تکسچر...", + "backgroundColor": "رنگ پس‌زمینه", + "camera": "دوربین", + "cameraType": { + "orthographic": "اورتوگرافیک", + "perspective": "پرسپکتیو" + }, + "clearRecording": "پاک کردن ضبط", + "dropToLoad": "مدل سه‌بعدی را برای بارگذاری رها کنید", + "edgeThreshold": "آستانه لبه", + "export": "خروجی گرفتن", + "exportModel": "خروجی گرفتن مدل", + "exportRecording": "خروجی گرفتن ضبط", + "exportingModel": "در حال خروجی گرفتن مدل...", + "fov": "زاویه دید (FOV)", + "light": "نور", + "lightIntensity": "شدت نور", + "loadingBackgroundImage": "در حال بارگذاری تصویر پس‌زمینه", + "loadingModel": "در حال بارگذاری مدل سه‌بعدی...", + "materialMode": "حالت متریال", + "materialModes": { + "depth": "عمق", + "lineart": "خطی", + "normal": "عادی", + "original": "اصلی", + "pointCloud": "ابر نقاط", + "wireframe": "سیمی" + }, + "model": "مدل", + "openIn3DViewer": "باز کردن در نمایشگر سه‌بعدی", + "panoramaMode": "حالت پانوراما", + "previewOutput": "پیش‌نمایش خروجی", + "reloadingModel": "در حال بارگذاری مجدد مدل...", + "removeBackgroundImage": "حذف تصویر پس‌زمینه", + "resizeNodeMatchOutput": "تغییر اندازه node مطابق خروجی", + "scene": "صحنه", + "showGrid": "نمایش شبکه", + "showSkeleton": "نمایش اسکلت", + "startRecording": "شروع ضبط", + "stopRecording": "توقف ضبط", + "switchCamera": "تغییر دوربین", + "switchingMaterialMode": "در حال تغییر حالت متریال...", + "tiledMode": "حالت کاشی‌کاری", + "unsupportedFileType": "نوع فایل پشتیبانی نمی‌شود (فقط .gltf، .glb، .obj، .fbx، .stl پشتیبانی می‌شود)", + "upDirection": "جهت بالا", + "upDirections": { + "original": "اصلی" + }, + "uploadBackgroundImage": "بارگذاری تصویر پس‌زمینه", + "uploadTexture": "بارگذاری تکسچر", + "uploadingModel": "در حال بارگذاری مدل سه‌بعدی...", + "viewer": { + "apply": "اعمال", + "cameraSettings": "تنظیمات دوربین", + "cameraType": "نوع دوربین", + "cancel": "لغو", + "exportSettings": "تنظیمات خروجی", + "lightSettings": "تنظیمات نور", + "modelSettings": "تنظیمات مدل", + "sceneSettings": "تنظیمات صحنه", + "title": "نمایشگر سه‌بعدی (بتا)" + } + }, + "loadWorkflowWarning": { + "coreNodesFromVersion": "nodeهای اصلی از نسخه {version}:", + "outdatedVersion": "این workflow با نسخه جدیدتری از ComfyUI ({version}) ایجاد شده است. برخی nodeها ممکن است به درستی کار نکنند.", + "outdatedVersionGeneric": "این workflow با نسخه جدیدتری از ComfyUI ایجاد شده است. برخی nodeها ممکن است به درستی کار نکنند." + }, + "maintenance": { + "None": "هیچ‌کدام", + "OK": "مناسب", + "Skipped": "رد شده", + "allOk": "هیچ مشکلی شناسایی نشد.", + "confirmTitle": "آیا مطمئن هستید؟", + "consoleLogs": "لاگ‌های کنسول", + "detected": "شناسایی شد", + "error": { + "cannotContinue": "امکان ادامه وجود ندارد - هنوز خطاهایی باقی مانده است", + "defaultDescription": "هنگام اجرای وظیفه نگهداری خطایی رخ داد.", + "taskFailed": "اجرای وظیفه با شکست مواجه شد.", + "toastTitle": "خطا در وظیفه" + }, + "refreshing": "در حال به‌روزرسانی", + "showManual": "نمایش وظایف نگهداری", + "status": "وضعیت", + "terminalDefaultMessage": "هنگام اجرای دستور عیب‌یابی، هر خروجی در اینجا نمایش داده می‌شود.", + "title": "نگهداری", + "unsafeMigration": { + "action": "از وظیفه نگهداری «Base path» در زیر برای انتقال ComfyUI به محل امن استفاده کنید.", + "appInstallDir": "مسیر پایه شما داخل بسته نرم‌افزاری برنامه ComfyUI Desktop قرار دارد. این پوشه ممکن است هنگام به‌روزرسانی حذف یا بازنویسی شود. پوشه‌ای خارج از محل نصب مانند Documents/ComfyUI را انتخاب کنید.", + "generic": "مسیر فعلی ComfyUI شما در مکانی قرار دارد که ممکن است هنگام به‌روزرسانی حذف یا تغییر کند. برای جلوگیری از از دست رفتن داده‌ها، آن را به پوشه‌ای امن منتقل کنید.", + "oneDrive": "مسیر پایه شما روی OneDrive قرار دارد که می‌تواند باعث مشکلات همگام‌سازی و از دست رفتن تصادفی داده‌ها شود. پوشه‌ای محلی که توسط OneDrive مدیریت نمی‌شود انتخاب کنید.", + "title": "محل نصب ناامن شناسایی شد", + "updaterCache": "مسیر پایه شما داخل کش به‌روزرسانی‌کننده ComfyUI قرار دارد که در هر به‌روزرسانی پاک می‌شود. محل دیگری را برای داده‌های خود انتخاب کنید." + } + }, + "manager": { + "allMissingNodesInstalled": "همه نودهای مفقود با موفقیت نصب شدند", + "applyChanges": "اعمال تغییرات", + "changingVersion": "تغییر نسخه از {from} به {to}", + "clickToFinishSetup": "کلیک کنید", + "conflicts": { + "conflictInfoTitle": "چرا این اتفاق می‌افتد؟", + "conflictMessages": { + "accelerator": "GPU/شتاب‌دهنده پشتیبانی نمی‌شود (موجود: {current}، مورد نیاز: {required})", + "banned": "این بسته به دلایل امنیتی مسدود شده است", + "comfyui_version": "عدم تطابق نسخه ComfyUI (فعلی: {current}، مورد نیاز: {required})", + "frontend_version": "عدم تطابق نسخه فرانت‌اند (فعلی: {current}، مورد نیاز: {required})", + "generic": "مشکل سازگاری (فعلی: {current}، مورد نیاز: {required})", + "import_failed": "وارد کردن ناموفق بود", + "os": "سیستم عامل پشتیبانی نمی‌شود (فعلی: {current}، مورد نیاز: {required})", + "pending": "بررسی امنیتی در انتظار است - امکان تأیید سازگاری وجود ندارد" + }, + "conflicts": "تضادها", + "description": "تضادهایی بین برخی از افزونه‌های شما و نسخه جدید ComfyUI شناسایی شده است. با به‌روزرسانی، ممکن است workflowهایی که به این افزونه‌ها وابسته‌اند دچار مشکل شوند.", + "enableAnyway": "فعال‌سازی به هر حال", + "extensionAtRisk": "افزونه در معرض خطر", + "importFailedExtensions": "افزونه‌های وارد نشده", + "info": "در صورت ادامه به‌روزرسانی، افزونه‌های دارای تضاد به طور خودکار غیرفعال خواهند شد. شما می‌توانید هر زمان آن‌ها را در مدیریت ComfyUI بررسی و مدیریت کنید.", + "installAnyway": "نصب به هر حال", + "title": "مشکلات بسته نود شناسایی شد!", + "understood": "متوجه شدم", + "warningBanner": { + "button": "اطلاعات بیشتر...", + "message": "این افزونه‌ها به نسخه‌هایی از بسته‌های سیستمی نیاز دارند که با تنظیمات فعلی شما متفاوت است. نصب آن‌ها ممکن است وابستگی‌های اصلی را بازنویسی کرده و بر سایر افزونه‌ها یا workflowها تأثیر بگذارد.", + "title": "برخی افزونه‌ها به دلیل ناسازگاری با تنظیمات فعلی شما غیرفعال شده‌اند" + }, + "warningTooltip": "این بسته ممکن است با محیط فعلی شما ناسازگار باشد" + }, + "createdBy": "سازنده", + "dependencies": "وابستگی‌ها", + "disabledNodesWontUpdate": "نودهای غیرفعال به‌روزرسانی نخواهند شد", + "discoverCommunityContent": "بسته‌های نود، افزونه‌ها و محتوای ساخته‌شده توسط جامعه را کشف کنید...", + "downloads": "دانلودها", + "enablePackToChangeVersion": "برای تغییر نسخه، این بسته را فعال کنید", + "errorConnecting": "خطا در اتصال به رجیستری نود Comfy.", + "extensionsSuccessfullyInstalled": "افزونه(ها) با موفقیت نصب شدند و آماده استفاده هستند!", + "failed": "ناموفق", + "failedToInstall": "نصب ناموفق بود", + "filter": { + "disabled": "غیرفعال", + "enabled": "فعال", + "nodePack": "بسته نود" + }, + "gettingInfo": "در حال دریافت اطلاعات...", + "importFailedGenericError": "وارد کردن بسته ناموفق بود. برای جزئیات بیشتر کنسول را بررسی کنید.", + "inWorkflow": "در workflow", + "infoPanelEmpty": "برای مشاهده اطلاعات، یک مورد را انتخاب کنید", + "installAllMissingNodes": "نصب همه", + "installError": "خطا در نصب", + "installSelected": "نصب انتخاب‌شده‌ها", + "installationQueue": "صف نصب", + "installingDependencies": "در حال نصب وابستگی‌ها...", + "lastUpdated": "آخرین به‌روزرسانی", + "latestVersion": "آخرین نسخه", + "legacyManagerUI": "استفاده از رابط کاربری قدیمی", + "legacyManagerUIDescription": "برای استفاده از رابط کاربری قدیمی مدیریت، ComfyUI را با --enable-manager-legacy-ui اجرا کنید.", + "legacyMenuNotAvailable": "منوی مدیریت قدیمی در دسترس نیست، به منوی جدید مدیریت منتقل می‌شود.", + "license": "مجوز", + "loadingVersions": "در حال بارگذاری نسخه‌ها...", + "mixedSelectionMessage": "امکان انجام عملیات گروهی روی انتخاب ترکیبی وجود ندارد", + "nightlyVersion": "نسخه nightly", + "noDescription": "توضیحی موجود نیست", + "noNodesFound": "نودی یافت نشد", + "noNodesFoundDescription": "نودهای این بسته قابل تجزیه نبودند یا این بسته فقط یک افزونه فرانت‌اند است و نودی ندارد.", + "noResultsFound": "نتیجه‌ای مطابق با جستجوی شما یافت نشد.", + "nodePack": "بسته نود", + "notAvailable": "در دسترس نیست", + "packsSelected": "بسته انتخاب شد", + "repository": "مخزن", + "restartToApplyChanges": "برای اعمال تغییرات، لطفاً ComfyUI را مجدداً راه‌اندازی کنید", + "restartingBackend": "در حال راه‌اندازی مجدد backend برای اعمال تغییرات...", + "searchPlaceholder": "جستجو", + "selectVersion": "انتخاب نسخه", + "sort": { + "created": "جدیدترین", + "downloads": "محبوب‌ترین", + "publisher": "ناشر", + "updated": "به‌روزرسانی اخیر" + }, + "status": { + "active": "فعال", + "banned": "مسدودشده", + "conflicting": "دارای تضاد", + "deleted": "حذف‌شده", + "flagged": "علامت‌گذاری‌شده", + "importFailed": "خطا در نصب", + "pending": "در انتظار", + "unknown": "نامشخص" + }, + "title": "مدیریت نودهای سفارشی", + "toFinishSetup": "برای تکمیل راه‌اندازی", + "totalNodes": "تعداد کل نودها", + "tryAgainLater": "لطفاً بعداً دوباره تلاش کنید.", + "tryDifferentSearch": "لطفاً عبارت جستجوی دیگری را امتحان کنید.", + "tryUpdate": "تلاش برای به‌روزرسانی", + "tryUpdateTooltip": "آخرین تغییرات را از مخزن دریافت کنید. نسخه‌های nightly ممکن است به‌روزرسانی‌هایی داشته باشند که به طور خودکار شناسایی نمی‌شوند.", + "uninstall": "حذف نصب", + "uninstallSelected": "حذف نصب انتخاب‌شده‌ها", + "uninstalling": "در حال حذف نصب {id}", + "update": "به‌روزرسانی", + "updateAll": "به‌روزرسانی همه", + "updateSelected": "به‌روزرسانی انتخاب‌شده‌ها", + "updatingAllPacks": "در حال به‌روزرسانی همه بسته‌ها", + "version": "نسخه" + }, + "maskEditor": { + "activateLayer": "فعال‌سازی لایه", + "applyToWholeImage": "اعمال به کل تصویر", + "baseImageLayer": "لایه تصویر پایه", + "baseLayerPreview": "پیش‌نمایش لایه پایه", + "black": "سیاه", + "brushSettings": "تنظیمات قلم‌مو", + "brushShape": "شکل قلم‌مو", + "clear": "پاک‌سازی", + "clickToResetZoom": "برای بازنشانی بزرگنمایی کلیک کنید", + "colorSelectSettings": "تنظیمات انتخاب رنگ", + "colorSelector": "انتخاب رنگ", + "fillOpacity": "شفافیت پرکردن", + "hardness": "سختی", + "imageLayer": "لایه تصویر", + "invert": "معکوس", + "layers": "لایه‌ها", + "livePreview": "پیش‌نمایش زنده", + "maskBlendingOptions": "گزینه‌های ترکیب ماسک", + "maskLayer": "لایه ماسک", + "maskOpacity": "شفافیت ماسک", + "maskTolerance": "تلورانس ماسک", + "method": "روش", + "mirrorHorizontal": "آینه افقی", + "mirrorVertical": "آینه عمودی", + "negative": "نگاتیو", + "opacity": "شفافیت", + "paintBucketSettings": "تنظیمات سطل رنگ", + "paintLayer": "لایه نقاشی", + "redo": "بازانجام", + "resetToDefault": "بازنشانی به پیش‌فرض", + "rotateLeft": "چرخش به چپ", + "rotateRight": "چرخش به راست", + "selectionOpacity": "شفافیت انتخاب", + "smoothingPrecision": "دقت هموارسازی", + "stepSize": "اندازه گام", + "stopAtMask": "توقف در ماسک", + "thickness": "ضخامت", + "title": "ویرایشگر ماسک", + "tolerance": "تلورانس", + "undo": "واگرد", + "white": "سفید" + }, + "mediaAsset": { + "actions": { + "copyJobId": "کپی شناسه job", + "delete": "حذف", + "download": "دانلود", + "exportWorkflow": "خروجی گرفتن از workflow", + "insertAsNodeInWorkflow": "افزودن به عنوان node در workflow", + "inspect": "بررسی دارایی", + "more": "گزینه‌های بیشتر", + "moreOptions": "گزینه‌های بیشتر", + "openWorkflow": "باز کردن به عنوان workflow در تب جدید", + "seeMoreOutputs": "مشاهده خروجی‌های بیشتر", + "zoom": "بزرگ‌نمایی" + }, + "assetDeletedSuccessfully": "دارایی با موفقیت حذف شد", + "deleteAssetDescription": "این دارایی به طور دائمی حذف خواهد شد.", + "deleteAssetTitle": "حذف این دارایی؟", + "deleteSelectedDescription": "{count} دارایی به طور دائمی حذف خواهد شد.", + "deleteSelectedTitle": "حذف دارایی‌های انتخاب‌شده؟", + "deletingImportedFilesCloudOnly": "حذف فایل‌های واردشده فقط در نسخه ابری پشتیبانی می‌شود", + "failedToCreateNode": "ایجاد node ناموفق بود", + "failedToDeleteAsset": "حذف دارایی ناموفق بود", + "failedToExportWorkflow": "خروجی گرفتن از workflow ناموفق بود", + "jobIdToast": { + "copied": "کپی شد", + "error": "خطا", + "jobIdCopied": "شناسه job با موفقیت کپی شد", + "jobIdCopyFailed": "کپی شناسه job ناموفق بود" + }, + "noJobIdFound": "شناسه job برای این دارایی یافت نشد", + "noWorkflowDataFound": "داده‌ای از workflow در این دارایی یافت نشد", + "nodeAddedToWorkflow": "node از نوع {nodeType} به workflow افزوده شد", + "nodeTypeNotFound": "نوع node {nodeType} یافت نشد", + "selection": { + "assetsDeletedSuccessfully": "{count} دارایی با موفقیت حذف شد", + "deleteSelected": "حذف", + "deleteSelectedAll": "حذف همه", + "deselectAll": "لغو انتخاب همه", + "downloadSelected": "دانلود", + "downloadSelectedAll": "دانلود همه", + "downloadStarted": "در حال دانلود {count} فایل...", + "downloadsStarted": "دانلود {count} فایل آغاز شد", + "exportWorkflowAll": "خروجی گرفتن از همه workflowها", + "failedToAddNodes": "افزودن nodeها به workflow ناموفق بود", + "failedToDeleteAssets": "حذف دارایی‌های انتخاب‌شده ناموفق بود", + "insertAllAssetsAsNodes": "افزودن همه دارایی‌ها به عنوان node", + "multipleSelectedAssets": "چندین دارایی انتخاب شده‌اند", + "noWorkflowsFound": "هیچ داده‌ای از workflow در دارایی‌های انتخاب‌شده یافت نشد", + "noWorkflowsToExport": "هیچ داده‌ای برای خروجی گرفتن از workflow یافت نشد", + "nodesAddedToWorkflow": "{count} node به workflow افزوده شد", + "openWorkflowAll": "باز کردن همه workflowها", + "partialAddNodesSuccess": "{succeeded} با موفقیت افزوده شد، {failed} ناموفق", + "partialDeleteSuccess": "{succeeded} با موفقیت حذف شد، {failed} ناموفق بود", + "partialWorkflowsExported": "{succeeded} با موفقیت خروجی گرفته شد، {failed} ناموفق", + "partialWorkflowsOpened": "{succeeded} workflow باز شد، {failed} ناموفق", + "selectedCount": "دارایی‌های انتخاب‌شده: {count}", + "workflowsExported": "{count} workflow با موفقیت خروجی گرفته شد", + "workflowsOpened": "{count} workflow در تب جدید باز شد" + }, + "unsupportedFileType": "نوع فایل پشتیبانی نمی‌شود برای node بارگذار", + "workflowExportedSuccessfully": "workflow با موفقیت خروجی گرفته شد", + "workflowOpenedInNewTab": "workflow در تب جدید باز شد" + }, + "menu": { + "autoQueue": "صف خودکار", + "batchCount": "تعداد دسته", + "batchCountTooltip": "تعداد دفعاتی که تولید workflow باید در صف قرار گیرد", + "clear": "پاک‌سازی workflow", + "clipspace": "باز کردن Clipspace", + "customNodesManager": "مدیریت nodeهای سفارشی", + "dark": "تاریک", + "disabled": "غیرفعال", + "disabledTooltip": "workflow به صورت خودکار در صف قرار نمی‌گیرد", + "execute": "اجرا", + "help": "راهنما", + "helpAndFeedback": "راهنما و بازخورد", + "hideMenu": "مخفی کردن منو", + "instant": "فوری", + "instantTooltip": "workflow بلافاصله پس از پایان تولید در صف قرار می‌گیرد", + "interrupt": "لغو اجرای فعلی", + "light": "روشن", + "manageExtensions": "مدیریت افزونه‌ها", + "onChange": "در صورت تغییر", + "onChangeTooltip": "workflow پس از هر تغییر در صف قرار می‌گیرد", + "queue": "پنل صف", + "refresh": "به‌روزرسانی تعاریف node", + "resetView": "بازنشانی نمای بوم", + "run": "اجرا", + "runWorkflow": "اجرای workflow (Shift برای صف در ابتدا)", + "runWorkflowDisabled": "workflow شامل nodeهای پشتیبانی‌نشده است (با رنگ قرمز مشخص شده‌اند). برای اجرا، این موارد را حذف کنید.", + "runWorkflowFront": "اجرای workflow (صف در ابتدا)", + "settings": "تنظیمات", + "showMenu": "نمایش منو", + "theme": "پوسته", + "toggleBottomPanel": "نمایش/مخفی‌سازی پنل پایین" + }, + "menuLabels": { + "About ComfyUI": "درباره ComfyUI", + "Assets": "دارایی‌ها", + "Bottom Panel": "پنل پایین", + "Browse Templates": "مرور قالب‌ها", + "Bypass/Unbypass Selected Nodes": "فعال/غیرفعال کردن عبور Nodeهای انتخاب‌شده", + "Canvas Performance": "عملکرد بوم", + "Canvas Toggle Lock": "تغییر وضعیت قفل بوم", + "Check for Custom Node Updates": "بررسی به‌روزرسانی Custom Nodeها", + "Check for Updates": "بررسی به‌روزرسانی‌ها", + "Clear Pending Tasks": "پاک‌سازی وظایف در انتظار", + "Clear Workflow": "پاک‌سازی Workflow", + "Clipspace": "Clipspace", + "Close Current Workflow": "بستن Workflow فعلی", + "Collapse/Expand Selected Nodes": "جمع/باز کردن Nodeهای انتخاب‌شده", + "Comfy-Org Discord": "Comfy-Org Discord", + "ComfyUI Docs": "مستندات ComfyUI", + "ComfyUI Forum": "انجمن ComfyUI", + "ComfyUI Issues": "مشکلات ComfyUI", + "Contact Support": "تماس با پشتیبانی", + "Convert Selection to Subgraph": "تبدیل انتخاب به Subgraph", + "Convert selected nodes to group node": "تبدیل Nodeهای انتخاب‌شده به group node", + "Custom Nodes (Legacy)": "Custom Nodes (قدیمی)", + "Custom Nodes Manager": "مدیر Custom Nodes", + "Decrease Brush Size in MaskEditor": "کاهش اندازه قلم‌مو در MaskEditor", + "Delete Selected Items": "حذف موارد انتخاب‌شده", + "Desktop User Guide": "راهنمای کاربر دسکتاپ", + "Duplicate Current Workflow": "تکثیر Workflow فعلی", + "Edit": "ویرایش", + "Edit Subgraph Widgets": "ویرایش ابزارک‌های Subgraph", + "Exit Subgraph": "خروج از Subgraph", + "Experimental: Browse Model Assets": "آزمایشی: مرور دارایی‌های مدل", + "Experimental: Enable AssetAPI": "آزمایشی: فعال‌سازی AssetAPI", + "Experimental: Enable Nodes 2_0": "آزمایشی: فعال‌سازی Nodes 2.0", + "Export": "خروجی گرفتن", + "Export (API)": "خروجی گرفتن (API)", + "File": "فایل", + "Fit Group To Contents": "تطبیق گروه با محتوا", + "Focus Mode": "حالت تمرکز", + "Group Selected Nodes": "گروه‌بندی Nodeهای انتخاب‌شده", + "Help": "راهنما", + "Help Center": "مرکز راهنما", + "Increase Brush Size in MaskEditor": "افزایش اندازه قلم‌مو در MaskEditor", + "Install Missing Custom Nodes": "نصب Custom Nodeهای گمشده", + "Interrupt": "توقف", + "Job History": "تاریخچه وظایف", + "Load Default Workflow": "بارگذاری Workflow پیش‌فرض", + "Lock Canvas": "قفل کردن بوم", + "Manage group nodes": "مدیریت group nodeها", + "Manager": "مدیر", + "Manager Menu (Legacy)": "منوی مدیر (قدیمی)", + "Minimap": "نقشه کوچک", + "Mirror Horizontal in MaskEditor": "آینه افقی در MaskEditor", + "Mirror Vertical in MaskEditor": "آینه عمودی در MaskEditor", + "Model Library": "کتابخانه مدل", + "Move Selected Nodes Down": "انتقال Nodeهای انتخاب‌شده به پایین", + "Move Selected Nodes Left": "انتقال Nodeهای انتخاب‌شده به چپ", + "Move Selected Nodes Right": "انتقال Nodeهای انتخاب‌شده به راست", + "Move Selected Nodes Up": "انتقال Nodeهای انتخاب‌شده به بالا", + "Mute/Unmute Selected Nodes": "بی‌صدا/فعال کردن صدای Nodeهای انتخاب‌شده", + "New": "جدید", + "Next Opened Workflow": "Workflow بعدی بازشده", + "Node Library": "کتابخانه Node", + "Node Links": "پیوندهای Node", + "Open": "باز کردن", + "Open 3D Viewer (Beta) for Selected Node": "باز کردن نمایشگر سه‌بعدی (بتا) برای Node انتخاب‌شده", + "Open Color Picker in MaskEditor": "باز کردن انتخاب‌گر رنگ در MaskEditor", + "Open Custom Nodes Folder": "باز کردن پوشه Custom Nodes", + "Open DevTools": "باز کردن DevTools", + "Open Inputs Folder": "باز کردن پوشه Inputs", + "Open Logs Folder": "باز کردن پوشه Logs", + "Open Mask Editor for Selected Node": "باز کردن Mask Editor برای Node انتخاب‌شده", + "Open Models Folder": "باز کردن پوشه Models", + "Open Outputs Folder": "باز کردن پوشه Outputs", + "Open Sign In Dialog": "باز کردن پنجره ورود", + "Open extra_model_paths_yaml": "باز کردن extra_model_paths.yaml", + "Pin/Unpin Selected Items": "سنجاق/برداشتن سنجاق موارد انتخاب‌شده", + "Pin/Unpin Selected Nodes": "سنجاق/برداشتن سنجاق Nodeهای انتخاب‌شده", + "Previous Opened Workflow": "Workflow قبلی بازشده", + "Publish": "انتشار", + "Queue Prompt": "افزودن به صف", + "Queue Prompt (Front)": "افزودن به ابتدای صف", + "Queue Selected Output Nodes": "افزودن Nodeهای خروجی انتخاب‌شده به صف", + "Quit": "خروج", + "Redo": "انجام مجدد", + "Refresh Node Definitions": "به‌روزرسانی تعاریف Node", + "Reinstall": "نصب مجدد", + "Rename": "تغییر نام", + "Reset View": "بازنشانی نما", + "Resize Selected Nodes": "تغییر اندازه Nodeهای انتخاب‌شده", + "Restart": "راه‌اندازی مجدد", + "Rotate Left in MaskEditor": "چرخش به چپ در MaskEditor", + "Rotate Right in MaskEditor": "چرخش به راست در MaskEditor", + "Save": "ذخیره", + "Save As": "ذخیره به عنوان", + "Show Keybindings Dialog": "نمایش پنجره کلیدهای میانبر", + "Show Model Selector (Dev)": "نمایش انتخاب‌گر مدل (توسعه‌دهنده)", + "Show Settings Dialog": "نمایش پنجره تنظیمات", + "Sign Out": "خروج از حساب", + "Toggle Essential Bottom Panel": "تغییر وضعیت پنل ضروری پایین", + "Toggle Logs Bottom Panel": "تغییر وضعیت پنل لاگ پایین", + "Toggle Queue Panel V2": "تغییر وضعیت پنل صف V2", + "Toggle Search Box": "تغییر وضعیت جعبه جستجو", + "Toggle Simple Mode": "تغییر حالت ساده", + "Toggle Terminal Bottom Panel": "تغییر وضعیت پنل ترمینال پایین", + "Toggle Theme (Dark/Light)": "تغییر وضعیت تم (تاریک/روشن)", + "Toggle View Controls Bottom Panel": "تغییر وضعیت کنترل‌های نمای پایین", + "Toggle promotion of hovered widget": "تغییر وضعیت ارتقاء ابزارک زیر نشانگر", + "Undo": "واگرد", + "Ungroup selected group nodes": "خارج کردن group nodeهای انتخاب‌شده از گروه", + "Unload Models": "خارج کردن مدل‌ها", + "Unload Models and Execution Cache": "خارج کردن مدل‌ها و حافظه اجرای موقت", + "Unlock Canvas": "باز کردن قفل بوم", + "Unpack the selected Subgraph": "باز کردن Subgraph انتخاب‌شده", + "View": "نمایش", + "Workflows": "Workflowها", + "Zoom In": "بزرگ‌نمایی", + "Zoom Out": "کوچک‌نمایی", + "Zoom to fit": "بزرگ‌نمایی برای تطبیق" + }, + "minimap": { + "nodeColors": "رنگ‌های نود", + "renderBypassState": "نمایش وضعیت عبور", + "renderErrorState": "نمایش وضعیت خطا", + "showGroups": "نمایش قاب‌ها/گروه‌ها", + "showLinks": "نمایش پیوندها" + }, + "missingModelsDialog": { + "doNotAskAgain": "دیگر نمایش داده نشود", + "missingModels": "مدل‌های مفقود", + "missingModelsMessage": "هنگام بارگذاری گراف، مدل‌های زیر یافت نشدند" + }, + "missingNodes": { + "cloud": { + "description": "این workflow از nodeهای سفارشی استفاده می‌کند که هنوز در نسخه ابری پشتیبانی نمی‌شوند.", + "gotIt": "متوجه شدم", + "learnMore": "اطلاعات بیشتر", + "priorityMessage": "ما این nodeها را به طور خودکار علامت‌گذاری کردیم تا اولویت اضافه شدن آن‌ها را افزایش دهیم.", + "replacementInstruction": "در این مدت، در صورت امکان این nodeها (که روی canvas با رنگ قرمز مشخص شده‌اند) را با nodeهای پشتیبانی‌شده جایگزین کنید یا workflow دیگری را امتحان کنید.", + "title": "این nodeها هنوز در Comfy Cloud در دسترس نیستند" + }, + "oss": { + "description": "این workflow از nodeهای سفارشی استفاده می‌کند که هنوز نصب نکرده‌اید.", + "replacementInstruction": "برای اجرای این workflow این nodeها را نصب کنید یا آن‌ها را با گزینه‌های نصب‌شده جایگزین نمایید. nodeهای مفقود روی canvas با رنگ قرمز مشخص شده‌اند.", + "title": "این workflow دارای nodeهای مفقود است" + } + }, + "nightly": { + "badge": { + "label": "نسخه پیش‌نمایش", + "tooltip": "شما در حال استفاده از نسخه شبانه ComfyUI هستید. لطفاً با استفاده از دکمه بازخورد، نظرات خود را درباره این قابلیت‌ها به اشتراک بگذارید." + } + }, + "nodeCategories": { + "": "", + "3d": "سه‌بعدی", + "3d_models": "مدل‌های سه‌بعدی", + "BFL": "BFL", + "Bria": "Bria", + "ByteDance": "ByteDance", + "Gemini": "Gemini", + "Ideogram": "Ideogram", + "Kling": "Kling", + "LTXV": "LTXV", + "Luma": "Luma", + "Meshy": "Meshy", + "MiniMax": "MiniMax", + "Moonvalley Marey": "Moonvalley Marey", + "OpenAI": "OpenAI", + "PixVerse": "PixVerse", + "Recraft": "Recraft", + "Rodin": "Rodin", + "Runway": "Runway", + "Sora": "Sora", + "Stability AI": "Stability AI", + "Tencent": "Tencent", + "Topaz": "Topaz", + "Tripo": "Tripo", + "Veo": "Veo", + "Vidu": "Vidu", + "Wan": "Wan", + "WaveSpeed": "WaveSpeed", + "_for_testing": "_for_testing", + "advanced": "پیشرفته", + "animation": "انیمیشن", + "api": "API", + "api node": "گره API", + "attention_experiments": "آزمایش‌های توجه", + "audio": "صدا", + "batch": "دسته‌ای", + "camera": "دوربین", + "chroma_radiance": "درخشندگی رنگی", + "clip": "clip", + "combine": "ترکیب", + "compositing": "ترکیب‌بندی", + "cond pair": "جفت شرط", + "cond single": "شرط تکی", + "conditioning": "شرط‌گذاری", + "context": "زمینه", + "controlnet": "controlnet", + "create": "ایجاد", + "custom_sampling": "نمونه‌گیری سفارشی", + "dataset": "داده‌نما", + "debug": "اشکال‌زدایی", + "deprecated": "منسوخ", + "edit_models": "ویرایش مدل‌ها", + "flux": "flux", + "gligen": "gligen", + "guidance": "راهنمایی", + "guiders": "راهنماها", + "hooks": "هوک‌ها", + "image": "تصویر", + "inpaint": "بازسازی", + "instructpix2pix": "instructpix2pix", + "kandinsky5": "kandinsky5", + "latent": "لاتنت", + "loaders": "بارگذارها", + "logic": "منطق", + "lotus": "lotus", + "ltxv": "ltxv", + "mask": "ماسک", + "model": "مدل", + "model_merging": "ادغام مدل", + "model_patches": "وصله‌های مدل", + "model_specific": "ویژه مدل", + "noise": "نویز", + "operations": "عملیات", + "photomaker": "photomaker", + "postprocessing": "پس‌پردازش", + "preprocessors": "پیش‌پردازنده‌ها", + "primitive": "ابتدایی", + "qwen": "qwen", + "samplers": "نمونه‌گیرها", + "sampling": "نمونه‌گیری", + "save": "ذخیره", + "schedulers": "زمان‌بندی‌ها", + "scheduling": "زمان‌بندی", + "sd": "sd", + "sd3": "sd3", + "sigmas": "سیگماها", + "stable_cascade": "stable cascade", + "string": "رشته", + "style_model": "مدل سبک", + "text": "متن", + "training": "آموزش", + "transform": "تبدیل", + "unet": "unet", + "upscale_diffusion": "پراکندگی افزایش وضوح", + "upscaling": "افزایش وضوح", + "utils": "ابزارها", + "video": "ویدیو", + "video_models": "مدل‌های ویدیو", + "zimage": "zimage" + }, + "nodeErrors": { + "content": "خطای محتوای node", + "header": "خطای هدر node", + "render": "خطای رندر node", + "slots": "خطای slotهای node", + "widgets": "خطای widgetهای node" + }, + "nodeHelpPage": { + "documentationPage": "صفحه مستندات", + "inputs": "ورودی‌ها", + "loadError": "بارگذاری راهنما ناموفق بود: {error}", + "moreHelp": "برای راهنمایی بیشتر، مراجعه کنید به", + "outputs": "خروجی‌ها", + "type": "نوع" + }, + "nodeTemplates": { + "enterName": "نام را وارد کنید", + "saveAsTemplate": "ذخیره به عنوان قالب" + }, + "notSupported": { + "continue": "ادامه", + "continueTooltip": "مطمئنم دستگاه من پشتیبانی می‌شود", + "illustrationAlt": "تصویر دختر ناراحت", + "learnMore": "اطلاعات بیشتر", + "message": "فقط دستگاه‌های زیر پشتیبانی می‌شوند:", + "reportIssue": "گزارش مشکل", + "supportedDevices": { + "macos": "MacOS (‏M۱ یا جدیدتر)", + "windows": "Windows (کارت گرافیک Nvidia با پشتیبانی CUDA)" + }, + "title": "دستگاه شما پشتیبانی نمی‌شود" + }, + "progressToast": { + "allDownloadsCompleted": "همه دانلودها تکمیل شدند", + "downloadingModel": "در حال دانلود مدل...", + "downloadsFailed": "{count} دانلود ناموفق بود | {count} دانلود ناموفق بود | {count} دانلود ناموفق بودند", + "failed": "ناموفق", + "filter": { + "all": "همه", + "completed": "تکمیل شده", + "failed": "ناموفق" + }, + "finished": "پایان یافته", + "importingModels": "در حال وارد کردن مدل‌ها", + "noImportsInQueue": "هیچ {filter} در صف نیست", + "pending": "در انتظار", + "progressCount": "{completed} از {total}" + }, + "queue": { + "completedIn": "پایان یافته در {duration}", + "inQueue": "در صف...", + "initializingAlmostReady": "در حال راه‌اندازی - تقریباً آماده", + "jobAddedToQueue": "کار به صف اضافه شد", + "jobDetails": { + "computeHoursUsed": "ساعت محاسباتی مصرف‌شده", + "errorMessage": "پیام خطا", + "estimatedFinishIn": "برآورد پایان در", + "estimatedStartIn": "برآورد شروع در", + "eta": { + "minutes": "~{count} دقیقه | ~{count} دقیقه", + "minutesRange": "~{lo}-{hi} دقیقه", + "seconds": "~{count} ثانیه | ~{count} ثانیه", + "secondsRange": "~{lo}-{hi} ثانیه" + }, + "failedAfter": "ناموفق پس از", + "generatedOn": "تولید شده در", + "header": "جزئیات کار", + "jobId": "شناسه کار", + "queuePosition": "موقعیت در صف", + "queuePositionValue": "~{count} کار جلوتر از شما | ~{count} کار جلوتر از شما", + "queuedAt": "در صف قرار گرفته در", + "report": "گزارش", + "timeElapsed": "زمان سپری‌شده", + "totalGenerationTime": "مجموع زمان تولید", + "workflow": "workflow" + }, + "jobHistory": "تاریخچه کارها", + "jobList": { + "sortComputeHoursUsed": "ساعت محاسباتی مصرف‌شده (بیشترین ابتدا)", + "sortMostRecent": "جدیدترین", + "sortTotalGenerationTime": "مجموع زمان تولید (بیشترین ابتدا)", + "undated": "بدون تاریخ" + }, + "jobMenu": { + "addToCurrentWorkflow": "افزودن به workflow فعلی", + "cancelJob": "لغو کار", + "copyErrorMessage": "کپی پیام خطا", + "copyJobId": "کپی شناسه کار", + "delete": "حذف", + "deleteAsset": "حذف دارایی", + "download": "دانلود", + "exportWorkflow": "خروجی گرفتن از workflow", + "inspectAsset": "بررسی دارایی", + "openAsWorkflowNewTab": "باز کردن به عنوان workflow در تب جدید", + "openWorkflowNewTab": "باز کردن workflow در تب جدید", + "removeJob": "حذف کار", + "reportError": "گزارش خطا" + }, + "toggleJobHistory": "نمایش/مخفی‌سازی تاریخچه کارها" + }, + "releaseToast": { + "description": "آخرین بهبودها و قابلیت‌های این به‌روزرسانی را مشاهده کنید.", + "newVersionAvailable": "به‌روزرسانی جدید منتشر شد!", + "skip": "رد کردن", + "update": "به‌روزرسانی", + "whatsNew": "مشاهده تغییرات جدید" + }, + "rightSidePanel": { + "addFavorite": "افزودن به علاقه‌مندی‌ها", + "advancedInputs": "ورودی‌های پیشرفته", + "bypass": "عبور", + "color": "رنگ نود", + "fallbackGroupTitle": "گروه", + "fallbackNodeTitle": "node", + "favorites": "ورودی‌های علاقه‌مندی", + "favoritesNone": "هیچ ورودی علاقه‌مندی وجود ندارد", + "favoritesNoneDesc": "ورودی‌هایی که به علاقه‌مندی‌ها اضافه کنید اینجا نمایش داده می‌شوند", + "favoritesNoneTooltip": "برای دسترسی سریع، ویجت‌ها را ستاره‌دار کنید تا بدون انتخاب nodeها به آن‌ها دسترسی داشته باشید", + "globalSettings": { + "canvas": "canvas", + "connectionLinks": "اتصالات", + "gridSpacing": "فاصله شبکه", + "linkShape": "شکل اتصال", + "nodes": "nodeها", + "nodes2": "nodeها ۲.۰", + "searchPlaceholder": "جستجوی تنظیمات سریع...", + "showAdvanced": "نمایش پارامترهای پیشرفته", + "showAdvancedTooltip": "این یک تنظیم مهم است که در صورت فعال بودن، همه پارامترهای پیشرفته nodeها را نمایش می‌دهد", + "showConnectedLinks": "نمایش اتصالات متصل", + "showInfoBadges": "نمایش نشان‌های اطلاعاتی", + "showToolbox": "نمایش جعبه ابزار هنگام انتخاب", + "snapNodesToGrid": "چسباندن nodeها به شبکه", + "title": "تنظیمات کلی", + "viewAllSettings": "مشاهده همه تنظیمات" + }, + "groupSettings": "تنظیمات گروه", + "groups": "گروه‌ها", + "hideAdvancedInputsButton": "مخفی کردن ورودی‌های پیشرفته", + "hideInput": "مخفی کردن ورودی", + "info": "اطلاعات", + "inputs": "ورودی‌ها", + "inputsNone": "بدون ورودی", + "inputsNoneTooltip": "این نود ورودی ندارد", + "locateNode": "یافتن node در canvas", + "mute": "بی‌صدا", + "noSelection": "یک نود را انتخاب کنید تا ویژگی‌ها و اطلاعات آن نمایش داده شود.", + "nodeState": "وضعیت نود", + "nodes": "nodeها", + "nodesNoneDesc": "هیچ nodeی وجود ندارد", + "noneSearchDesc": "هیچ موردی با جستجوی شما مطابقت ندارد", + "normal": "عادی", + "parameters": "پارامترها", + "pinned": "سنجاق شده", + "properties": "ویژگی‌ها", + "removeFavorite": "حذف از علاقه‌مندی‌ها", + "settings": "تنظیمات", + "showAdvancedInputsButton": "نمایش ورودی‌های پیشرفته", + "showInput": "نمایش ورودی", + "title": "هیچ نودی انتخاب نشده است | ۱ نود انتخاب شده است | {count} نود انتخاب شده‌اند", + "togglePanel": "نمایش/پنهان کردن پنل ویژگی‌ها", + "workflowOverview": "نمای کلی workflow" + }, + "selectionToolbox": { + "Bypass Group Nodes": "عبور از نودهای گروه", + "Set Group Nodes to Always": "تنظیم نودهای گروه روی همیشه", + "Set Group Nodes to Never": "تنظیم نودهای گروه روی هرگز", + "executeButton": { + "disabledTooltip": "هیچ نود خروجی انتخاب نشده است", + "tooltip": "اجرا برای نودهای خروجی انتخاب‌شده (با کادر نارنجی مشخص شده‌اند)" + } + }, + "serverConfig": { + "modifiedConfigs": "شما تنظیمات سرور زیر را تغییر داده‌اید. برای اعمال تغییرات، برنامه را مجدداً راه‌اندازی کنید.", + "restart": "راه‌اندازی مجدد", + "restartRequiredToastDetail": "برای اعمال تغییرات تنظیمات سرور، برنامه را مجدداً راه‌اندازی کنید.", + "restartRequiredToastSummary": "نیاز به راه‌اندازی مجدد", + "revertChanges": "بازگرداندن تغییرات" + }, + "serverConfigCategories": { + "Attention": "Attention", + "CUDA": "CUDA", + "Cache": "کش", + "Directories": "پوشه‌ها", + "General": "عمومی", + "Inference": "استنتاج", + "Memory": "حافظه", + "Network": "شبکه", + "Preview": "پیش‌نمایش" + }, + "serverConfigItems": { + "cache-classic": { + "name": "استفاده از سیستم کش کلاسیک" + }, + "cache-lru": { + "name": "استفاده از کش LRU با حداکثر N نتیجه node کش شده.", + "tooltip": "ممکن است از RAM/VRAM بیشتری استفاده شود." + }, + "cpu-vae": { + "name": "اجرای VAE روی CPU" + }, + "cross-attention-method": { + "name": "روش cross attention" + }, + "cuda-device": { + "name": "اندیس دستگاه CUDA برای استفاده" + }, + "cuda-malloc": { + "name": "استفاده از CUDA malloc برای تخصیص حافظه" + }, + "default-hashing-function": { + "name": "تابع هش پیش‌فرض برای فایل‌های مدل" + }, + "deterministic": { + "name": "استفاده از الگوریتم‌های قطعی کندتر در pytorch در صورت امکان.", + "tooltip": "توجه داشته باشید که این ممکن است در همه موارد تصاویر را قطعی نکند." + }, + "directml": { + "name": "اندیس دستگاه DirectML" + }, + "disable-all-custom-nodes": { + "name": "غیرفعال‌سازی بارگذاری همه nodeهای سفارشی." + }, + "disable-ipex-optimize": { + "name": "غیرفعال‌سازی بهینه‌سازی IPEX" + }, + "disable-metadata": { + "name": "غیرفعال‌سازی ذخیره متادیتای prompt در فایل‌ها." + }, + "disable-smart-memory": { + "name": "غیرفعال‌سازی مدیریت هوشمند حافظه", + "tooltip": "ComfyUI را مجبور می‌کند به طور تهاجمی به RAM معمولی منتقل کند به جای نگه داشتن مدل‌ها در VRAM در صورت امکان." + }, + "disable-xformers": { + "name": "غیرفعال‌سازی بهینه‌سازی xFormers" + }, + "dont-print-server": { + "name": "عدم چاپ خروجی سرور در کنسول." + }, + "dont-upcast-attention": { + "name": "جلوگیری از upcast در attention" + }, + "enable-cors-header": { + "name": "فعال‌سازی هدر CORS: برای همه مبداها از \"*\" استفاده کنید یا دامنه را مشخص نمایید" + }, + "enable-manager-legacy-ui": { + "name": "استفاده از رابط کاربری قدیمی Manager", + "tooltip": "از رابط کاربری قدیمی ComfyUI-Manager به جای رابط کاربری جدید استفاده می‌کند." + }, + "fast": { + "name": "فعال‌سازی برخی بهینه‌سازی‌های آزمایش‌نشده و احتمالاً کاهش‌دهنده کیفیت." + }, + "force-channels-last": { + "name": "اجبار فرمت حافظه channels-last" + }, + "force-upcast-attention": { + "name": "اجبار upcast در attention" + }, + "global-precision": { + "name": "دقت ممیز شناور سراسری", + "tooltip": "دقت ممیز شناور سراسری" + }, + "input-directory": { + "name": "پوشه ورودی" + }, + "listen": { + "name": "میزبان: آدرس IP برای گوش دادن" + }, + "log-level": { + "name": "سطح جزئیات لاگ" + }, + "max-upload-size": { + "name": "حداکثر اندازه بارگذاری (مگابایت)" + }, + "output-directory": { + "name": "پوشه خروجی" + }, + "port": { + "name": "پورت: شماره پورت برای گوش دادن" + }, + "preview-method": { + "name": "روش استفاده شده برای پیش‌نمایش latent" + }, + "preview-size": { + "name": "اندازه تصاویر پیش‌نمایش" + }, + "reserve-vram": { + "name": "VRAM رزرو شده (گیگابایت)", + "tooltip": "مقدار VRAM به گیگابایت که می‌خواهید برای سیستم‌عامل/نرم‌افزارهای دیگر رزرو کنید را تعیین نمایید. به طور پیش‌فرض مقداری بسته به سیستم‌عامل رزرو می‌شود." + }, + "text-encoder-precision": { + "name": "دقت Text Encoder", + "tooltip": "دقت Text Encoder" + }, + "tls-certfile": { + "name": "فایل گواهی TLS: مسیر فایل گواهی TLS برای HTTPS" + }, + "tls-keyfile": { + "name": "فایل کلید TLS: مسیر فایل کلید TLS برای HTTPS" + }, + "unet-precision": { + "name": "دقت UNET", + "tooltip": "دقت UNET" + }, + "vae-precision": { + "name": "دقت VAE", + "tooltip": "دقت VAE" + }, + "vram-management": { + "name": "حالت مدیریت VRAM" + } + }, + "serverStart": { + "copyAllTooltip": "کپی همه", + "copySelectionTooltip": "کپی انتخاب", + "errorMessage": "امکان راه‌اندازی ComfyUI Desktop وجود ندارد", + "installation": { + "title": "در حال نصب ComfyUI" + }, + "openLogs": "باز کردن گزارش‌ها", + "process": { + "error": "امکان راه‌اندازی ComfyUI Desktop وجود ندارد", + "initial-state": "در حال بارگذاری...", + "python-setup": "در حال آماده‌سازی محیط Python...", + "ready": "در حال بارگذاری رابط کاربری", + "starting-server": "در حال راه‌اندازی سرور ComfyUI..." + }, + "reportIssue": "گزارش مشکل", + "showTerminal": "نمایش ترمینال", + "title": "در حال شروع ComfyUI", + "troubleshoot": "عیب‌یابی" + }, + "settingsCategories": { + "3D": "سه‌بعدی", + "3DViewer": "نمایشگر سه‌بعدی", + "API Nodes": "Nodeهای API", + "About": "درباره", + "Appearance": "ظاهر", + "BrushAdjustment": "تنظیم قلم‌مو", + "Camera": "دوربین", + "Canvas": "بوم", + "Canvas Navigation": "ناوبری بوم", + "ColorPalette": "پالت رنگ", + "Comfy": "Comfy", + "Comfy-Desktop": "Comfy-Desktop", + "ContextMenu": "منوی زمینه", + "Credits": "اعتبارات", + "CustomColorPalettes": "پالت‌های رنگ سفارشی", + "DevMode": "حالت توسعه‌دهنده", + "EditTokenWeight": "ویرایش وزن توکن", + "Execution": "اجرا", + "Extension": "افزونه", + "General": "عمومی", + "Graph": "گراف", + "Group": "گروه", + "Keybinding": "کلیدهای میانبر", + "Light": "نور", + "Link": "پیوند", + "LinkRelease": "رهاسازی پیوند", + "LiteGraph": "Lite Graph", + "Load 3D": "بارگذاری ۳بعدی و انیمیشن", + "Locale": "زبان", + "Mask Editor": "ویرایشگر Mask", + "Menu": "منو", + "ModelLibrary": "کتابخانه مدل", + "Node": "Node", + "Node Search Box": "جعبه جستجوی Node", + "Node Widget": "ویجت Node", + "NodeLibrary": "کتابخانه Node", + "Nodes 2_0": "Nodes 2.0", + "Notification Preferences": "تنظیمات اعلان", + "Other": "سایر", + "PLY": "PLY", + "PlanCredits": "طرح و اعتبارات", + "Pointer": "اشاره‌گر", + "Queue": "صف", + "QueueButton": "دکمه صف", + "Reroute": "مسیر مجدد", + "RerouteBeta": "مسیر مجدد (بتا)", + "Scene": "صحنه", + "Server": "سرور", + "Server-Config": "پیکربندی سرور", + "Settings": "تنظیمات", + "Sidebar": "نوار کناری", + "Tree Explorer": "کاوشگر درختی", + "UV": "UV", + "User": "کاربر", + "Validation": "اعتبارسنجی", + "Vue Nodes": "Nodes 2.0", + "VueNodes": "Nodes 2.0", + "Window": "پنجره", + "Workflow": "Workflow", + "Workspace": "محیط کاری" + }, + "shape": { + "CARD": "کارت", + "arrow": "پیکان", + "box": "جعبه", + "circle": "دایره", + "default": "پیش‌فرض", + "round": "گرد" + }, + "shortcuts": { + "essentials": "ضروری", + "keyboardShortcuts": "میانبرهای صفحه‌کلید", + "manageShortcuts": "مدیریت میانبرها", + "noKeybinding": "بدون کلید میانبر", + "shortcuts": "میانبرها", + "subcategories": { + "node": "node", + "panelControls": "کنترل‌های پنل", + "queue": "صف", + "view": "نمایش", + "workflow": "workflow" + }, + "viewControls": "کنترل‌های نمایش" + }, + "sideToolbar": { + "activeJobStatus": "وضعیت کار فعال: {status}", + "assets": "دارایی‌ها", + "backToAssets": "بازگشت به همه دارایی‌ها", + "browseTemplates": "مرور قالب‌های نمونه", + "downloads": "دانلودها", + "generatedAssetsHeader": "دارایی‌های تولیدشده", + "helpCenter": "مرکز راهنما", + "importedAssetsHeader": "دارایی‌های واردشده", + "labels": { + "assets": "دارایی‌ها", + "console": "کنسول", + "generated": "تولیدشده", + "imported": "واردشده", + "menu": "منو", + "models": "مدل‌ها", + "nodes": "Nodeها", + "queue": "صف", + "templates": "قالب‌ها", + "workflows": "Workflowها" + }, + "logout": "خروج", + "mediaAssets": { + "filter3D": "سه‌بعدی", + "filterAudio": "صوت", + "filterImage": "تصویر", + "filterText": "متن", + "filterVideo": "ویدیو", + "sortFastestFirst": "زمان تولید (کمترین ابتدا)", + "sortLongestFirst": "زمان تولید (بیشترین ابتدا)", + "sortNewestFirst": "جدیدترین ابتدا", + "sortOldestFirst": "قدیمی‌ترین ابتدا", + "title": "دارایی‌های رسانه‌ای" + }, + "modelLibrary": "کتابخانه مدل", + "newBlankWorkflow": "ایجاد Workflow جدید خالی", + "noFilesFound": "فایلی یافت نشد", + "noFilesFoundMessage": "برای مشاهده فایل‌ها، فایل بارگذاری یا محتوا تولید کنید", + "noGeneratedFiles": "فایل تولیدشده‌ای یافت نشد", + "noImportedFiles": "فایل واردشده‌ای یافت نشد", + "nodeLibrary": "کتابخانه Node", + "nodeLibraryTab": { + "groupBy": "گروه‌بندی بر اساس", + "groupStrategies": { + "category": "دسته‌بندی", + "categoryDesc": "گروه‌بندی بر اساس دسته Node", + "module": "ماژول", + "moduleDesc": "گروه‌بندی بر اساس منبع ماژول", + "source": "منبع", + "sourceDesc": "گروه‌بندی بر اساس نوع منبع (Core، Custom، API)" + }, + "resetView": "بازنشانی نمای پیش‌فرض", + "sortBy": { + "alphabetical": "حروف الفبا", + "alphabeticalDesc": "مرتب‌سازی الفبایی درون گروه‌ها", + "original": "اصلی", + "originalDesc": "حفظ ترتیب اصلی" + }, + "sortMode": "حالت مرتب‌سازی" + }, + "openWorkflow": "باز کردن Workflow در سیستم فایل محلی", + "queue": "صف", + "queueProgressOverlay": { + "activeJobs": "{count} کار فعال", + "activeJobsShort": "{count} فعال | {count} فعال", + "activeJobsSuffix": "کار فعال", + "cancelJobTooltip": "لغو کار", + "clearHistory": "پاک‌سازی تاریخچه صف کار", + "clearHistoryDialogAssetsNote": "دارایی‌های تولیدشده توسط این کارها حذف نمی‌شوند و همیشه از پنل دارایی‌ها قابل مشاهده هستند.", + "clearHistoryDialogDescription": "همه کارهای تمام‌شده یا ناموفق زیر از این پنل صف کار حذف خواهند شد.", + "clearHistoryDialogTitle": "تاریخچه صف کار را پاک کنید؟", + "clearQueueTooltip": "پاک‌سازی صف", + "clearQueued": "پاک‌سازی صف", + "colonPercent": ": {percent}", + "currentNode": "Node فعلی:", + "expandCollapsedQueue": "گسترش صف کار", + "filterAllWorkflows": "همه Workflowها", + "filterBy": "فیلتر بر اساس", + "filterCurrentWorkflow": "Workflow فعلی", + "filterJobs": "فیلتر کارها", + "interruptAll": "توقف همه کارهای در حال اجرا", + "jobQueue": "صف کار", + "jobsCompleted": "{count} کار تکمیل شد", + "jobsFailed": "{count} کار ناموفق بود", + "moreOptions": "گزینه‌های بیشتر", + "noActiveJobs": "کار فعالی وجود ندارد", + "preview": "پیش‌نمایش", + "queuedSuffix": "در صف", + "running": "در حال اجرا", + "showAssets": "نمایش دارایی‌ها", + "showAssetsPanel": "نمایش پنل دارایی‌ها", + "sortBy": "مرتب‌سازی بر اساس", + "sortJobs": "مرتب‌سازی کارها", + "stubClipTextEncode": "CLIP Text Encode:", + "title": "پیشرفت صف", + "total": "مجموع: {percent}", + "viewAllJobs": "مشاهده همه کارها", + "viewGrid": "نمای جدولی", + "viewJobHistory": "مشاهده تاریخچه کار", + "viewList": "نمای لیستی" + }, + "searchAssets": "جستجوی دارایی‌ها", + "sidebar": "نوار کناری", + "templates": "قالب‌ها", + "themeToggle": "تغییر تم", + "workflowTab": { + "confirmDelete": "آیا مطمئن هستید که می‌خواهید این Workflow را حذف کنید؟", + "confirmDeleteTitle": "حذف Workflow؟", + "confirmOverwrite": "فایل زیر از قبل وجود دارد. آیا مایل به جایگزینی آن هستید؟", + "confirmOverwriteTitle": "جایگزینی فایل موجود؟", + "deleteFailed": "تلاش برای حذف Workflow ناموفق بود.", + "deleteFailedTitle": "حذف ناموفق بود", + "deleted": "Workflow حذف شد", + "dirtyClose": "فایل‌های زیر تغییر کرده‌اند. آیا مایل به ذخیره آن‌ها قبل از بستن هستید؟", + "dirtyCloseHint": "برای بستن بدون پیام، Shift را نگه دارید", + "dirtyCloseTitle": "ذخیره تغییرات؟", + "workflowTreeType": { + "bookmarks": "نشانک‌ها", + "browse": "مرور", + "open": "باز" + } + }, + "workflows": "Workflowها" + }, + "subgraphStore": { + "blueprintName": "نام زیرگراف", + "confirmDelete": "این عمل باعث حذف دائمی بلوپرینت از کتابخانه شما می‌شود", + "confirmDeleteTitle": "حذف بلوپرینت؟", + "hidden": "پارامترهای مخفی / تو در تو", + "hideAll": "مخفی‌سازی همه", + "loadFailure": "بارگذاری بلوپرینت‌های زیرگراف ناموفق بود", + "overwriteBlueprint": "ذخیره باعث جایگزینی بلوپرینت فعلی با تغییرات شما می‌شود", + "overwriteBlueprintTitle": "جایگزینی بلوپرینت موجود؟", + "promoteOutsideSubgraph": "امکان ارتقاء ویجت خارج از زیرگراف وجود ندارد", + "publish": "انتشار زیرگراف", + "publishSuccess": "در کتابخانه گره‌ها ذخیره شد", + "publishSuccessMessage": "می‌توانید بلوپرینت زیرگراف خود را در کتابخانه گره‌ها در بخش \"بلوپرینت‌های زیرگراف\" پیدا کنید", + "saveBlueprint": "ذخیره زیرگراف در کتابخانه", + "showAll": "نمایش همه", + "showRecommended": "نمایش ویجت‌های پیشنهادی", + "shown": "نمایش روی گره" + }, + "subscription": { + "addApiCredits": "افزودن اعتبار API", + "addCredits": "افزودن اعتبار", + "addCreditsLabel": "هر زمان اعتبار بیشتری اضافه کنید", + "benefits": { + "benefit1": "۱۰ دلار اعتبار ماهانه برای Partner Nodes — در صورت نیاز شارژ کنید", + "benefit2": "تا ۳۰ دقیقه زمان اجرا برای هر کار" + }, + "beta": "بتا", + "billedMonthly": "صورتحساب ماهانه", + "billedYearly": "{total} صورتحساب سالانه", + "billingComingSoon": { + "message": "صورت‌حساب تیمی به‌زودی ارائه می‌شود. به‌زودی می‌توانید برای فضای کاری خود با قیمت هر نفر اشتراک تهیه کنید. برای به‌روزرسانی‌ها همراه ما باشید.", + "title": "به‌زودی" + }, + "cancelSubscription": "لغو اشتراک", + "changeTo": "تغییر به {plan}", + "comfyCloud": "Comfy Cloud", + "comfyCloudLogo": "لوگوی Comfy Cloud", + "contactOwnerToSubscribe": "برای فعال‌سازی اشتراک با مالک محیط کاری تماس بگیرید", + "contactUs": "تماس با ما", + "creditsRemainingThisMonth": "شامل شده (شارژ مجدد {date})", + "creditsRemainingThisYear": "شامل شده (شارژ مجدد {date})", + "creditsYouveAdded": "اضافه شده", + "currentPlan": "طرح فعلی", + "customLoRAsLabel": "LoRAهای خود را وارد کنید", + "description": "بهترین طرح را برای خود انتخاب کنید", + "expiresDate": "انقضا در {date}", + "gpuLabel": "RTX 6000 Pro (۹۶ گیگابایت VRAM)", + "haveQuestions": "سوالی دارید یا به دنبال راهکار سازمانی هستید؟", + "invoiceHistory": "تاریخچه فاکتورها", + "learnMore": "اطلاعات بیشتر", + "managePayment": "مدیریت پرداخت", + "managePlan": "مدیریت طرح", + "manageSubscription": "مدیریت اشتراک", + "maxDuration": { + "creator": "۳۰ دقیقه", + "founder": "۳۰ دقیقه", + "pro": "۱ ساعت", + "standard": "۳۰ دقیقه" + }, + "maxDurationLabel": "حداکثر مدت اجرا", + "messageSupport": "پیام به پشتیبانی", + "monthly": "ماهانه", + "monthlyBonusDescription": "پاداش ماهانه اعتبار", + "monthlyCreditsInfo": "این اعتبارها هر ماه شارژ می‌شوند و منتقل نمی‌شوند", + "monthlyCreditsLabel": "اعتبار ماهانه", + "monthlyCreditsRollover": "این اعتبارها به ماه بعد منتقل می‌شوند", + "mostPopular": "محبوب‌ترین", + "nextBillingCycle": "چرخه صورتحساب بعدی", + "partnerNodesBalance": "اعتبار «Partner Nodes»", + "partnerNodesCredits": "قیمت‌گذاری Partner Nodes", + "partnerNodesDescription": "برای اجرای مدل‌های تجاری/اختصاصی", + "perMonth": "/ ماه", + "plansAndPricing": "طرح‌ها و قیمت‌ها", + "prepaidCreditsInfo": "اعتبارهای پیش‌پرداخت تا یک سال پس از تاریخ خرید منقضی می‌شوند.", + "prepaidDescription": "اعتبارهای پیش‌پرداخت", + "renewsDate": "تمدید در {date}", + "required": { + "subscribe": "اشتراک", + "title": "اشتراک در", + "waitingForSubscription": "اشتراک خود را در تب جدید تکمیل کنید. ما به صورت خودکار اتمام را تشخیص می‌دهیم!" + }, + "subscribeNow": "هم‌اکنون اشتراک بگیرید", + "subscribeTo": "اشتراک در {plan}", + "subscribeToComfyCloud": "اشتراک در Comfy Cloud", + "subscribeToRun": "اشتراک", + "subscribeToRunFull": "اشتراک برای اجرا", + "subscriptionRequiredMessage": "برای اجرای workflowها در Cloud، اشتراک لازم است.", + "tierNameYearly": "{name} سالانه", + "tiers": { + "creator": { + "name": "خالق" + }, + "founder": { + "name": "نسخه بنیان‌گذاران" + }, + "pro": { + "name": "حرفه‌ای" + }, + "standard": { + "name": "استاندارد" + } + }, + "title": "اشتراک", + "titleUnsubscribed": "اشتراک در Comfy Cloud", + "totalCredits": "کل اعتبارها", + "upgrade": "ارتقا", + "upgradePlan": "ارتقا طرح", + "upgradeTo": "ارتقا به {plan}", + "usdPerMonth": "دلار آمریکا / ماه", + "videoEstimateExplanation": "این تخمین‌ها بر اساس قالب Wan 2.2 Image-to-Video با تنظیمات پیش‌فرض (۵ ثانیه، ۶۴۰×۶۴۰، ۱۶ فریم بر ثانیه، ۴ مرحله نمونه‌گیری) است.", + "videoEstimateHelp": "جزئیات بیشتر درباره این قالب", + "videoEstimateLabel": "تخمین تعداد ویدیوهای ۵ ثانیه‌ای تولید شده با قالب Wan 2.2 Image-to-Video", + "videoEstimateTryTemplate": "امتحان این قالب", + "videoTemplateBasedCredits": "ویدیوهای تولید شده با Wan 2.2 Image to Video", + "viewEnterprise": "مشاهده راهکار سازمانی", + "viewMoreDetails": "مشاهده جزئیات بیشتر", + "viewMoreDetailsPlans": "مشاهده جزئیات بیشتر درباره طرح‌ها و قیمت‌ها", + "viewUsageHistory": "مشاهده تاریخچه استفاده", + "workspaceNotSubscribed": "این محیط کاری اشتراک فعال ندارد", + "yearly": "سالانه", + "yearlyCreditsLabel": "کل اعتبار سالانه", + "yearlyDiscount": "٪۲۰ تخفیف", + "yourPlanIncludes": "طرح شما شامل:" + }, + "tabMenu": { + "addToBookmarks": "افزودن به نشانک‌ها", + "closeOtherTabs": "بستن سایر تب‌ها", + "closeTab": "بستن تب", + "closeTabsToLeft": "بستن تب‌های سمت چپ", + "closeTabsToRight": "بستن تب‌های سمت راست", + "duplicateTab": "ایجاد تب مشابه", + "removeFromBookmarks": "حذف از نشانک‌ها" + }, + "templateWidgets": { + "sort": { + "searchPlaceholder": "جستجو..." + } + }, + "templateWorkflows": { + "activeFilters": "فیلترها:", + "allTemplates": "همه قالب‌ها", + "categories": "دسته‌بندی‌ها", + "category": { + "3D": "سه‌بعدی", + "All": "همه قالب‌ها", + "Area Composition": "ترکیب ناحیه", + "Audio": "صوت", + "Basics": "مبانی", + "ComfyUI Examples": "نمونه‌های ComfyUI", + "ControlNet": "ControlNet", + "Custom Nodes": "گره‌های سفارشی", + "Extensions": "افزونه‌ها", + "Flux": "Flux", + "Generation Type": "نوع تولید", + "GettingStarted": "شروع کار", + "Image": "تصویر", + "Image API": "رابط برنامه‌نویسی تصویر", + "LLM API": "رابط برنامه‌نویسی LLM", + "LLMs": "LLMs", + "Partner Nodes": "گره‌های همکار", + "Upscaling": "افزایش کیفیت", + "Video": "ویدیو", + "Video API": "رابط برنامه‌نویسی ویدیو" + }, + "error": { + "templateNotFound": "قالب \"{templateName}\" یافت نشد" + }, + "licenseFilter": "مجوز", + "loading": "در حال بارگذاری قالب‌ها...", + "loadingMore": "در حال بارگذاری قالب‌های بیشتر...", + "modelFilter": "فیلتر مدل", + "modelsSelected": "{count} مدل", + "noResults": "قالبی یافت نشد", + "noResultsHint": "لطفاً جستجو یا فیلترهای خود را تغییر دهید", + "resetFilters": "پاک‌کردن فیلترها", + "resultsCount": "نمایش {count} از {total} قالب", + "runsOnFilter": "اجرا روی", + "runsOnSelected": "{count} اجرا روی", + "searchPlaceholder": "جستجوی قالب‌ها...", + "sort": { + "alphabetical": "الف → ی", + "default": "پیش‌فرض", + "modelSizeLowToHigh": "اندازه مدل (کوچک به بزرگ)", + "newest": "جدیدترین", + "popular": "محبوب", + "recommended": "پیشنهادی", + "searchPlaceholder": "جستجو...", + "vramLowToHigh": "مصرف VRAM (کم به زیاد)" + }, + "sorting": "مرتب‌سازی بر اساس", + "title": "شروع با یک قالب", + "useCaseFilter": "وظایف", + "useCasesSelected": "{count} وظیفه" + }, + "toastMessages": { + "cannotCreateSubgraph": "امکان ایجاد subgraph وجود ندارد", + "couldNotDetermineFileType": "نوع فایل قابل شناسایی نیست", + "dropFileError": "امکان پردازش آیتم انداخته‌شده وجود ندارد: {error}", + "emptyCanvas": "بوم خالی است", + "errorCopyImage": "خطا در کپی تصویر: {error}", + "errorLoadingModel": "خطا در بارگذاری مدل", + "errorSaveSetting": "خطا در ذخیره تنظیمات {id}: {err}", + "exportSuccess": "مدل با موفقیت با فرمت {format} صادر شد", + "failedExecutionPathResolution": "مسیر اجرای nodeهای انتخاب‌شده قابل شناسایی نیست", + "failedToAccessBillingPortal": "دسترسی به پورتال صورتحساب انجام نشد: {error}", + "failedToApplyTexture": "اعمال بافت با خطا مواجه شد", + "failedToConvertToSubgraph": "تبدیل آیتم‌ها به subgraph انجام نشد", + "failedToCreateCustomer": "ایجاد مشتری انجام نشد: {error}", + "failedToDownloadFile": "دانلود فایل انجام نشد", + "failedToExportModel": "صادرات مدل با فرمت {format} انجام نشد", + "failedToFetchBalance": "دریافت موجودی انجام نشد: {error}", + "failedToFetchLogs": "دریافت گزارش‌های سرور انجام نشد", + "failedToFetchSubscription": "دریافت وضعیت اشتراک انجام نشد: {error}", + "failedToInitializeLoad3dViewer": "راه‌اندازی نمایشگر سه‌بعدی انجام نشد", + "failedToInitiateCreditPurchase": "آغاز خرید اعتبار انجام نشد: {error}", + "failedToInitiateSubscription": "آغاز اشتراک انجام نشد: {error}", + "failedToLoadBackgroundImage": "بارگذاری تصویر پس‌زمینه انجام نشد", + "failedToLoadModel": "بارگذاری مدل سه‌بعدی انجام نشد", + "failedToPurchaseCredits": "خرید اعتبار انجام نشد: {error}", + "failedToQueue": "صف‌بندی انجام نشد", + "failedToToggleCamera": "تغییر وضعیت دوربین انجام نشد", + "failedToToggleGrid": "تغییر وضعیت شبکه انجام نشد", + "failedToUpdateBackgroundColor": "به‌روزرسانی رنگ پس‌زمینه انجام نشد", + "failedToUpdateBackgroundImage": "به‌روزرسانی تصویر پس‌زمینه انجام نشد", + "failedToUpdateBackgroundRenderMode": "به‌روزرسانی حالت رندر پس‌زمینه به {mode} انجام نشد", + "failedToUpdateEdgeThreshold": "به‌روزرسانی آستانه لبه انجام نشد", + "failedToUpdateFOV": "به‌روزرسانی زاویه دید انجام نشد", + "failedToUpdateLightIntensity": "به‌روزرسانی شدت نور انجام نشد", + "failedToUpdateMaterialMode": "به‌روزرسانی حالت متریال انجام نشد", + "failedToUpdateUpDirection": "به‌روزرسانی جهت بالا انجام نشد", + "failedToUploadBackgroundImage": "بارگذاری تصویر پس‌زمینه انجام نشد", + "fileLoadError": "جریان کاری در {fileName} یافت نشد", + "fileTooLarge": "فایل بیش از حد بزرگ است ({size} مگابایت). حداکثر اندازه مجاز {maxSize} مگابایت است", + "fileUploadFailed": "بارگذاری فایل انجام نشد", + "interrupted": "اجرا متوقف شد", + "legacyMaskEditorDeprecated": "ویرایشگر mask قدیمی منسوخ شده و به‌زودی حذف خواهد شد.", + "migrateToLitegraphReroute": "nodeهای reroute در نسخه‌های آینده حذف خواهند شد. برای مهاجرت به reroute بومی litegraph کلیک کنید.", + "modelLoadedSuccessfully": "مدل سه‌بعدی با موفقیت بارگذاری شد", + "no3dScene": "صحنه سه‌بعدی برای اعمال بافت وجود ندارد", + "no3dSceneToExport": "صحنه سه‌بعدی برای صادرات وجود ندارد", + "noTemplatesToExport": "قالبی برای صادرات وجود ندارد", + "nodeDefinitionsUpdated": "تعاریف node به‌روزرسانی شد", + "nothingSelected": "موردی انتخاب نشده است", + "nothingToGroup": "موردی برای گروه‌بندی وجود ندارد", + "nothingToQueue": "موردی برای صف‌بندی وجود ندارد", + "pendingTasksDeleted": "وظایف در انتظار حذف شدند", + "pleaseSelectNodesToGroup": "لطفاً nodeها (یا گروه‌های دیگر) را برای ایجاد گروه انتخاب کنید", + "pleaseSelectOutputNodes": "لطفاً nodeهای خروجی را انتخاب کنید", + "unableToGetModelFilePath": "امکان دریافت مسیر فایل مدل وجود ندارد", + "unauthorizedDomain": "دامنه شما ({domain}) مجاز به استفاده از این سرویس نیست. لطفاً برای افزودن دامنه خود به لیست سفید با {email} تماس بگیرید.", + "updateRequested": "درخواست به‌روزرسانی ثبت شد", + "useApiKeyTip": "نکته: به ورود عادی دسترسی ندارید؟ از گزینه Comfy API Key استفاده کنید.", + "userNotAuthenticated": "کاربر احراز هویت نشده است" + }, + "userSelect": { + "enterUsername": "نام کاربری را وارد کنید", + "existingUser": "کاربر موجود", + "newUser": "کاربر جدید", + "next": "بعدی", + "selectUser": "انتخاب کاربر" + }, + "userSettings": { + "accountSettings": "تنظیمات حساب کاربری", + "email": "ایمیل", + "name": "نام", + "notSet": "تنظیم نشده", + "provider": "ارائه‌دهنده ورود", + "title": "تنظیمات حساب کاربری من", + "updatePassword": "به‌روزرسانی گذرواژه", + "workspaceSettings": "تنظیمات محیط کاری" + }, + "validation": { + "descriptionRequired": "توضیحات الزامی است", + "invalidEmail": "آدرس ایمیل نامعتبر است", + "length": "باید {length} کاراکتر باشد", + "maxLength": "نباید بیش از {length} کاراکتر باشد", + "minLength": "باید حداقل {length} کاراکتر باشد", + "password": { + "lowercase": "باید حداقل یک حرف کوچک داشته باشد", + "match": "گذرواژه‌ها باید یکسان باشند", + "minLength": "باید بین ۸ تا ۳۲ کاراکتر باشد", + "number": "باید حداقل یک عدد داشته باشد", + "requirements": "الزامات گذرواژه", + "special": "باید حداقل یک کاراکتر ویژه داشته باشد", + "uppercase": "باید حداقل یک حرف بزرگ داشته باشد" + }, + "personalDataConsentRequired": "شما باید با پردازش داده‌های شخصی خود موافقت کنید.", + "prefix": "باید با {prefix} شروع شود", + "required": "ضروری" + }, + "versionMismatchWarning": { + "dismiss": "رد کردن", + "frontendNewer": "نسخه رابط کاربری {frontendVersion} ممکن است با نسخه پشتی {backendVersion} سازگار نباشد.", + "frontendOutdated": "نسخه رابط کاربری {frontendVersion} قدیمی است. نسخه پشتی نیاز به نسخه {requiredVersion} یا بالاتر دارد.", + "title": "هشدار ناسازگاری نسخه", + "updateFrontend": "به‌روزرسانی رابط کاربری" + }, + "vueNodesBanner": { + "desc": "– workflowهای منعطف‌تر، ابزارک‌های قدرتمند جدید، ساخته‌شده برای توسعه‌پذیری", + "title": "معرفی Nodes 2.0", + "tryItOut": "امتحان کنید" + }, + "vueNodesMigration": { + "button": "بازگشت", + "message": "طراحی قدیمی را ترجیح می‌دهید؟" + }, + "vueNodesMigrationMainMenu": { + "message": "در هر زمان می‌توانید از منوی اصلی به Nodes 2.0 بازگردید." + }, + "welcome": { + "getStarted": "شروع کنید", + "title": "به ComfyUI خوش آمدید" + }, + "whatsNewPopup": { + "later": "بعداً", + "learnMore": "بیشتر بدانید", + "noReleaseNotes": "یادداشت نسخه‌ای موجود نیست." + }, + "widgetFileUpload": { + "browseFiles": "انتخاب فایل‌ها", + "dropPrompt": "فایل خود را رها کنید یا" + }, + "widgets": { + "node2only": "فقط Node 2.0", + "selectModel": "انتخاب مدل", + "uploadSelect": { + "placeholder": "انتخاب...", + "placeholderAudio": "انتخاب صوت...", + "placeholderImage": "انتخاب تصویر...", + "placeholderModel": "انتخاب مدل...", + "placeholderUnknown": "انتخاب رسانه...", + "placeholderVideo": "انتخاب ویدیو..." + }, + "valueControl": { + "decrement": "کاهش مقدار", + "decrementDesc": "عدد ۱ از مقدار کم می‌کند یا گزینه قبلی را انتخاب می‌کند", + "editSettings": "ویرایش تنظیمات کنترل", + "fixed": "مقدار ثابت", + "fixedDesc": "مقدار را بدون تغییر باقی می‌گذارد", + "header": { + "after": "پس از", + "before": "پیش از", + "postfix": "اجرای workflow:", + "prefix": "به‌روزرسانی خودکار مقدار" + }, + "increment": "افزایش مقدار", + "incrementDesc": "عدد ۱ به مقدار اضافه می‌کند یا گزینه بعدی را انتخاب می‌کند", + "linkToGlobal": "پیوند به", + "linkToGlobalDesc": "مقدار یکتا که به تنظیم کنترل مقدار سراسری متصل است", + "linkToGlobalSeed": "مقدار سراسری", + "randomize": "تصادفی‌سازی مقدار", + "randomizeDesc": "مقدار را پس از هر تولید به صورت تصادفی تغییر می‌دهد" + } + }, + "workflowService": { + "enterFilename": "نام فایل را وارد کنید", + "exportWorkflow": "خروجی گرفتن از workflow", + "saveWorkflow": "ذخیره workflow" + }, + "workspace": { + "addedToWorkspace": "شما به {workspaceName} اضافه شدید", + "inviteAccepted": "دعوت پذیرفته شد", + "inviteFailed": "پذیرش دعوت ناموفق بود", + "unsavedChanges": { + "message": "شما تغییرات ذخیره‌نشده دارید. آیا می‌خواهید آن‌ها را رها کرده و فضای کاری را تغییر دهید؟", + "title": "تغییرات ذخیره‌نشده" + } + }, + "workspaceAuth": { + "errors": { + "accessDenied": "شما به این فضای کاری دسترسی ندارید.", + "invalidFirebaseToken": "احراز هویت ناموفق بود. لطفاً دوباره وارد شوید.", + "notAuthenticated": "برای دسترسی به فضاهای کاری باید وارد شوید.", + "tokenExchangeFailed": "احراز هویت با فضای کاری ناموفق بود: {error}", + "workspaceNotFound": "فضای کاری پیدا نشد." + } + }, + "workspacePanel": { + "createWorkspaceDialog": { + "create": "ایجاد", + "message": "محیط‌های کاری به اعضا اجازه می‌دهند از یک اعتبار مشترک استفاده کنند. پس از ایجاد، شما مالک خواهید بود.", + "nameLabel": "نام محیط کاری*", + "namePlaceholder": "نام محیط کاری را وارد کنید", + "title": "ایجاد محیط کاری جدید" + }, + "dashboard": { + "placeholder": "تنظیمات داشبورد فضای کاری" + }, + "deleteDialog": { + "message": "هرگونه اعتبار یا دارایی ذخیره‌نشده از بین خواهد رفت. این عملیات قابل بازگشت نیست.", + "messageWithName": "حذف «{name}»؟ هرگونه اعتبار یا دارایی ذخیره‌نشده از بین خواهد رفت. این عملیات قابل بازگشت نیست.", + "title": "حذف این محیط کاری؟" + }, + "editWorkspaceDialog": { + "nameLabel": "نام محیط کاری", + "save": "ذخیره", + "title": "ویرایش جزئیات محیط کاری" + }, + "invite": "دعوت", + "inviteLimitReached": "شما به حداکثر تعداد ۵۰ عضو رسیده‌اید", + "inviteMember": "دعوت عضو", + "inviteMemberDialog": { + "createLink": "ایجاد لینک", + "linkCopied": "کپی شد", + "linkCopyFailed": "کپی لینک ناموفق بود", + "linkStep": { + "copyLink": "کپی لینک", + "done": "انجام شد", + "message": "اطمینان حاصل کنید که حساب کاربری او از این ایمیل استفاده می‌کند.", + "title": "این لینک را برای شخص ارسال کنید" + }, + "message": "یک لینک دعوت قابل اشتراک‌گذاری برای ارسال به شخص ایجاد کنید", + "placeholder": "ایمیل شخص را وارد کنید", + "title": "دعوت یک نفر به این فضای کاری" + }, + "leaveDialog": { + "leave": "خروج", + "message": "تا زمانی که با مالک محیط کاری تماس نگیرید، امکان پیوستن مجدد نخواهید داشت.", + "title": "خروج از این محیط کاری؟" + }, + "members": { + "actions": { + "copyLink": "کپی لینک دعوت", + "removeMember": "حذف عضو", + "revokeInvite": "لغو دعوت" + }, + "columns": { + "expiryDate": "تاریخ انقضا", + "inviteDate": "تاریخ دعوت", + "joinDate": "تاریخ پیوستن" + }, + "createNewWorkspace": "یک فضای کاری جدید ایجاد کنید.", + "membersCount": "{count}/۵۰ عضو", + "noInvites": "هیچ دعوت‌نامه‌ای در انتظار نیست", + "noMembers": "هیچ عضوی وجود ندارد", + "pendingInvitesCount": "{count} دعوت‌نامه در انتظار | {count} دعوت‌نامه در انتظار", + "personalWorkspaceMessage": "در حال حاضر نمی‌توانید اعضای دیگری به فضای کاری شخصی خود دعوت کنید. برای افزودن اعضا به یک فضای کاری،", + "tabs": { + "active": "فعال", + "pendingCount": "در انتظار ({count})" + } + }, + "menu": { + "deleteWorkspace": "حذف محیط کاری", + "deleteWorkspaceDisabledTooltip": "ابتدا اشتراک فعال محیط کاری خود را لغو کنید", + "editWorkspace": "ویرایش جزئیات محیط کاری", + "leaveWorkspace": "خروج از محیط کاری" + }, + "removeMemberDialog": { + "error": "حذف عضو ناموفق بود", + "message": "این عضو از فضای کاری شما حذف خواهد شد. اعتباراتی که استفاده کرده‌اند بازگردانده نخواهد شد.", + "remove": "حذف عضو", + "success": "عضو حذف شد", + "title": "این عضو حذف شود؟" + }, + "revokeInviteDialog": { + "message": "این عضو دیگر نمی‌تواند به فضای کاری شما بپیوندد. لینک دعوت او نامعتبر خواهد شد.", + "revoke": "لغو دعوت", + "title": "دعوت این شخص لغو شود؟" + }, + "tabs": { + "dashboard": "داشبورد", + "membersCount": "اعضا ({count})", + "planCredits": "پلن و اعتبارها" + }, + "toast": { + "failedToCreateWorkspace": "ایجاد محیط کاری ناموفق بود", + "failedToDeleteWorkspace": "حذف محیط کاری ناموفق بود", + "failedToFetchWorkspaces": "بارگذاری فضاهای کاری ناموفق بود", + "failedToLeaveWorkspace": "خروج از محیط کاری ناموفق بود", + "failedToUpdateWorkspace": "به‌روزرسانی محیط کاری ناموفق بود", + "workspaceCreated": { + "message": "برای یک طرح اشتراک ثبت‌نام کنید، هم‌تیمی‌ها را دعوت کنید و همکاری را آغاز نمایید.", + "subscribe": "اشتراک", + "title": "فضای کاری ایجاد شد" + }, + "workspaceDeleted": { + "message": "فضای کاری به طور دائمی حذف شد.", + "title": "فضای کاری حذف شد" + }, + "workspaceLeft": { + "message": "شما فضای کاری را ترک کردید.", + "title": "ترک فضای کاری" + }, + "workspaceUpdated": { + "message": "جزئیات محیط کاری ذخیره شد.", + "title": "محیط کاری به‌روزرسانی شد" + } + } + }, + "workspaceSwitcher": { + "createWorkspace": "ایجاد محیط کاری جدید", + "maxWorkspacesReached": "شما فقط می‌توانید مالک ۱۰ محیط کاری باشید. برای ایجاد محیط کاری جدید، یکی را حذف کنید.", + "personal": "شخصی", + "roleMember": "عضو", + "roleOwner": "مالک", + "subscribe": "اشتراک", + "switchWorkspace": "تغییر محیط کاری" + }, + "zoomControls": { + "hideMinimap": "مخفی‌سازی نقشه کوچک", + "label": "کنترل‌های بزرگ‌نمایی", + "showMinimap": "نمایش نقشه کوچک", + "zoomToFit": "بزرگ‌نمایی متناسب" + } +} diff --git a/src/locales/fa/nodeDefs.json b/src/locales/fa/nodeDefs.json new file mode 100644 index 000000000..ccb6398df --- /dev/null +++ b/src/locales/fa/nodeDefs.json @@ -0,0 +1,16552 @@ +{ + "APG": { + "display_name": "راهنمایی تطبیقی تصویری", + "inputs": { + "eta": { + "name": "اتا", + "tooltip": "مقیاس بردار راهنمایی موازی را کنترل می‌کند. رفتار پیش‌فرض CFG در مقدار ۱." + }, + "model": { + "name": "مدل" + }, + "momentum": { + "name": "مومنتوم", + "tooltip": "میانگین‌گیری متحرک راهنمایی در طول انتشار را کنترل می‌کند، در مقدار ۰ غیرفعال است." + }, + "norm_threshold": { + "name": "آستانه نرمال‌سازی", + "tooltip": "بردار راهنمایی را به این مقدار نرمال‌سازی می‌کند، نرمال‌سازی در مقدار ۰ غیرفعال است." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AddNoise": { + "display_name": "AddNoise", + "inputs": { + "latent_image": { + "name": "تصویر latent" + }, + "model": { + "name": "مدل" + }, + "noise": { + "name": "نویز" + }, + "sigmas": { + "name": "سیگماها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AddTextPrefix": { + "display_name": "افزودن پیشوند به متن", + "inputs": { + "prefix": { + "name": "پیشوند", + "tooltip": "پیشوندی که باید افزوده شود." + }, + "texts": { + "name": "متن‌ها", + "tooltip": "متنی که باید پردازش شود." + } + }, + "outputs": { + "0": { + "name": "متن‌ها", + "tooltip": "متن‌های پردازش‌شده" + } + } + }, + "AddTextSuffix": { + "display_name": "افزودن پسوند به متن", + "inputs": { + "suffix": { + "name": "پسوند", + "tooltip": "پسوندی که باید افزوده شود." + }, + "texts": { + "name": "متن‌ها", + "tooltip": "متنی که باید پردازش شود." + } + }, + "outputs": { + "0": { + "name": "متن‌ها", + "tooltip": "متن‌های پردازش‌شده" + } + } + }, + "AdjustBrightness": { + "display_name": "تنظیم روشنایی", + "inputs": { + "factor": { + "name": "ضریب", + "tooltip": "ضریب روشنایی. ۱.۰ = بدون تغییر، کمتر از ۱.۰ = تیره‌تر، بیشتر از ۱.۰ = روشن‌تر." + }, + "images": { + "name": "تصاویر", + "tooltip": "تصویری که باید پردازش شود." + } + }, + "outputs": { + "0": { + "name": "تصاویر", + "tooltip": "تصاویر پردازش‌شده" + } + } + }, + "AdjustContrast": { + "display_name": "تنظیم کنتراست", + "inputs": { + "factor": { + "name": "ضریب", + "tooltip": "ضریب کنتراست. ۱.۰ = بدون تغییر، کمتر از ۱.۰ = کنتراست کمتر، بیشتر از ۱.۰ = کنتراست بیشتر." + }, + "images": { + "name": "تصاویر", + "tooltip": "تصویری که باید پردازش شود." + } + }, + "outputs": { + "0": { + "name": "تصاویر", + "tooltip": "تصاویر پردازش‌شده" + } + } + }, + "AlignYourStepsScheduler": { + "display_name": "AlignYourStepsScheduler", + "inputs": { + "denoise": { + "name": "کاهش نویز" + }, + "model_type": { + "name": "نوع مدل" + }, + "steps": { + "name": "گام‌ها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AudioAdjustVolume": { + "display_name": "تنظیم حجم صدا", + "inputs": { + "audio": { + "name": "صدا" + }, + "volume": { + "name": "حجم صدا", + "tooltip": "تنظیم حجم صدا بر حسب دسی‌بل (dB). ۰ = بدون تغییر، +۶ = دو برابر، -۶ = نصف و غیره" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AudioConcat": { + "description": "صداهای audio1 و audio2 را در جهت مشخص‌شده به هم متصل می‌کند.", + "display_name": "Audio Concat", + "inputs": { + "audio1": { + "name": "audio1" + }, + "audio2": { + "name": "audio2" + }, + "direction": { + "name": "جهت", + "tooltip": "آیا audio2 بعد یا قبل از audio1 اضافه شود." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AudioEncoderEncode": { + "display_name": "AudioEncoderEncode", + "inputs": { + "audio": { + "name": "audio" + }, + "audio_encoder": { + "name": "audio_encoder" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AudioEncoderLoader": { + "display_name": "AudioEncoderLoader", + "inputs": { + "audio_encoder_name": { + "name": "audio_encoder_name" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AudioMerge": { + "description": "دو ترک صوتی را با هم ترکیب می‌کند و موج‌های صوتی آن‌ها را روی هم قرار می‌دهد.", + "display_name": "Audio Merge", + "inputs": { + "audio1": { + "name": "audio1" + }, + "audio2": { + "name": "audio2" + }, + "merge_method": { + "name": "روش ترکیب", + "tooltip": "روشی که برای ترکیب موج‌های صوتی استفاده می‌شود." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BasicGuider": { + "display_name": "BasicGuider", + "inputs": { + "conditioning": { + "name": "شرط‌گذاری" + }, + "model": { + "name": "مدل" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BasicScheduler": { + "display_name": "BasicScheduler", + "inputs": { + "denoise": { + "name": "کاهش نویز" + }, + "model": { + "name": "مدل" + }, + "scheduler": { + "name": "زمان‌بندی" + }, + "steps": { + "name": "گام‌ها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchImagesNode": { + "display_name": "Batch Images", + "inputs": { + "images": { + "name": "تصاویر" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchLatentsNode": { + "display_name": "Batch Latents", + "inputs": { + "latents": { + "name": "latents" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchMasksNode": { + "display_name": "Batch Masks", + "inputs": { + "masks": { + "name": "ماسک‌ها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BetaSamplingScheduler": { + "display_name": "BetaSamplingScheduler", + "inputs": { + "alpha": { + "name": "آلفا" + }, + "beta": { + "name": "بتا" + }, + "model": { + "name": "مدل" + }, + "steps": { + "name": "گام‌ها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BriaImageEditNode": { + "description": "ویرایش تصاویر با استفاده از جدیدترین مدل Bria", + "display_name": "ویرایش تصویر Bria", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "guidance_scale": { + "name": "مقیاس راهنما", + "tooltip": "مقدار بالاتر باعث می‌شود تصویر بیشتر از پرامپت پیروی کند." + }, + "image": { + "name": "تصویر" + }, + "mask": { + "name": "ماسک", + "tooltip": "در صورت عدم انتخاب، ویرایش بر کل تصویر اعمال می‌شود." + }, + "model": { + "name": "مدل" + }, + "moderation": { + "name": "تنظیمات نظارت", + "tooltip": "تنظیمات نظارت" + }, + "moderation_prompt_content_moderation": { + "name": "نظارت بر محتوای پرامپت" + }, + "moderation_visual_input_moderation": { + "name": "نظارت بر ورودی تصویری" + }, + "moderation_visual_output_moderation": { + "name": "نظارت بر خروجی تصویری" + }, + "negative_prompt": { + "name": "پرامپت منفی" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "دستورالعمل برای ویرایش تصویر" + }, + "seed": { + "name": "بذر" + }, + "steps": { + "name": "گام‌ها" + }, + "structured_prompt": { + "name": "پرامپت ساختاریافته", + "tooltip": "یک رشته شامل پرامپت ویرایش ساختاریافته در قالب JSON. برای کنترل دقیق و برنامه‌نویسی شده، به جای پرامپت معمولی از این گزینه استفاده کنید." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "پرامپت ساختاریافته", + "tooltip": null + } + } + }, + "ByteDanceFirstLastFrameNode": { + "description": "تولید ویدیو با استفاده از پرامپت و اولین و آخرین فریم.", + "display_name": "تبدیل اولین و آخرین فریم به ویدیو ByteDance", + "inputs": { + "aspect_ratio": { + "name": "نسبت تصویر", + "tooltip": "نسبت تصویر ویدیوی خروجی." + }, + "camera_fixed": { + "name": "ثابت بودن دوربین", + "tooltip": "مشخص می‌کند که آیا دوربین ثابت باشد یا خیر. پلتفرم یک دستور برای ثابت کردن دوربین به پرامپت شما اضافه می‌کند، اما تضمینی برای نتیجه نهایی وجود ندارد." + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان", + "tooltip": "مدت زمان ویدیوی خروجی بر حسب ثانیه." + }, + "first_frame": { + "name": "اولین فریم", + "tooltip": "اولین فریم مورد استفاده برای ویدیو." + }, + "generate_audio": { + "name": "تولید صدا", + "tooltip": "این پارامتر فقط برای مدل seedance-1-5-pro معتبر است و برای سایر مدل‌ها نادیده گرفته می‌شود." + }, + "last_frame": { + "name": "آخرین فریم", + "tooltip": "آخرین فریم مورد استفاده برای ویدیو." + }, + "model": { + "name": "مدل" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "متن پرامپت مورد استفاده برای تولید ویدیو." + }, + "resolution": { + "name": "وضوح تصویر", + "tooltip": "وضوح تصویر ویدیوی خروجی." + }, + "seed": { + "name": "seed", + "tooltip": "seed مورد استفاده برای تولید." + }, + "watermark": { + "name": "واترمارک", + "tooltip": "آیا واترمارک «تولید شده توسط هوش مصنوعی» به ویدیو اضافه شود یا خیر." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ByteDanceImageEditNode": { + "description": "ویرایش تصاویر با استفاده از مدل‌های ByteDance از طریق API بر اساس پرامپت", + "display_name": "ویرایش تصویر ByteDance", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "guidance_scale": { + "name": "guidance_scale", + "tooltip": "مقدار بالاتر باعث می‌شود تصویر بیشتر از پرامپت پیروی کند" + }, + "image": { + "name": "تصویر", + "tooltip": "تصویر پایه برای ویرایش" + }, + "model": { + "name": "مدل" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "دستورالعمل ویرایش تصویر" + }, + "seed": { + "name": "seed", + "tooltip": "seed مورد استفاده برای تولید" + }, + "watermark": { + "name": "واترمارک", + "tooltip": "آیا واترمارک «تولید شده توسط هوش مصنوعی» به تصویر اضافه شود یا خیر" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ByteDanceImageNode": { + "description": "تولید تصویر با استفاده از مدل‌های ByteDance از طریق API بر اساس پرامپت", + "display_name": "تصویر ByteDance", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "guidance_scale": { + "name": "guidance_scale", + "tooltip": "مقدار بالاتر باعث می‌شود تصویر بیشتر از پرامپت پیروی کند" + }, + "height": { + "name": "ارتفاع", + "tooltip": "ارتفاع سفارشی برای تصویر. این مقدار فقط زمانی اعمال می‌شود که «پیش‌تنظیم اندازه» روی «سفارشی» باشد." + }, + "model": { + "name": "مدل" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "متن پرامپت مورد استفاده برای تولید تصویر" + }, + "seed": { + "name": "seed", + "tooltip": "seed مورد استفاده برای تولید" + }, + "size_preset": { + "name": "پیش‌تنظیم اندازه", + "tooltip": "یک اندازه پیشنهادی انتخاب کنید. برای استفاده از عرض و ارتفاع سفارشی، گزینه سفارشی را انتخاب کنید." + }, + "watermark": { + "name": "واترمارک", + "tooltip": "آیا واترمارک «تولید شده توسط هوش مصنوعی» به تصویر اضافه شود یا خیر" + }, + "width": { + "name": "عرض", + "tooltip": "عرض سفارشی برای تصویر. این مقدار فقط زمانی اعمال می‌شود که «پیش‌تنظیم اندازه» روی «سفارشی» باشد." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ByteDanceImageReferenceNode": { + "description": "تولید ویدیو با استفاده از پرامپت و تصاویر مرجع.", + "display_name": "تبدیل تصاویر مرجع ByteDance به ویدیو", + "inputs": { + "aspect_ratio": { + "name": "نسبت تصویر", + "tooltip": "نسبت تصویر ویدیوی خروجی." + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان", + "tooltip": "مدت زمان ویدیوی خروجی بر حسب ثانیه." + }, + "images": { + "name": "تصاویر", + "tooltip": "یک تا چهار تصویر." + }, + "model": { + "name": "مدل" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "متن پرامپت برای تولید ویدیو استفاده می‌شود." + }, + "resolution": { + "name": "رزولوشن", + "tooltip": "رزولوشن ویدیوی خروجی." + }, + "seed": { + "name": "seed", + "tooltip": "seed مورد استفاده برای تولید." + }, + "watermark": { + "name": "واترمارک", + "tooltip": "آیا واترمارک «تولید شده توسط هوش مصنوعی» به ویدیو اضافه شود؟" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ByteDanceImageToVideoNode": { + "description": "تولید ویدیو با استفاده از مدل‌های ByteDance از طریق API بر اساس تصویر و پرامپت", + "display_name": "تبدیل تصویر به ویدیو ByteDance", + "inputs": { + "aspect_ratio": { + "name": "نسبت تصویر", + "tooltip": "نسبت تصویر ویدیوی خروجی." + }, + "camera_fixed": { + "name": "ثابت بودن دوربین", + "tooltip": "مشخص می‌کند که آیا دوربین ثابت باشد. پلتفرم یک دستور برای ثابت نگه داشتن دوربین به پرامپت شما اضافه می‌کند، اما تضمینی برای نتیجه نهایی وجود ندارد." + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان", + "tooltip": "مدت زمان ویدیوی خروجی بر حسب ثانیه." + }, + "generate_audio": { + "name": "تولید صدا", + "tooltip": "این پارامتر فقط برای مدل seedance-1-5-pro معتبر است و برای سایر مدل‌ها نادیده گرفته می‌شود." + }, + "image": { + "name": "تصویر", + "tooltip": "اولین فریم مورد استفاده برای ویدیو." + }, + "model": { + "name": "مدل" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "متن پرامپت برای تولید ویدیو استفاده می‌شود." + }, + "resolution": { + "name": "رزولوشن", + "tooltip": "رزولوشن ویدیوی خروجی." + }, + "seed": { + "name": "seed", + "tooltip": "seed مورد استفاده برای تولید." + }, + "watermark": { + "name": "واترمارک", + "tooltip": "آیا واترمارک «تولید شده توسط هوش مصنوعی» به ویدیو اضافه شود؟" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ByteDanceSeedreamNode": { + "description": "تولید یکپارچه تصویر از متن و ویرایش دقیق جمله‌ای تا وضوح ۴K.", + "display_name": "ByteDance Seedream ۴.۵", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "fail_on_partial": { + "name": "توقف در صورت ناقص بودن", + "tooltip": "در صورت فعال بودن، اگر هر یک از تصاویر درخواستی موجود نباشد یا خطا رخ دهد، اجرا متوقف می‌شود." + }, + "height": { + "name": "ارتفاع", + "tooltip": "ارتفاع سفارشی برای تصویر. این مقدار فقط زمانی اعمال می‌شود که «پیش‌تنظیم اندازه» روی «سفارشی» باشد." + }, + "image": { + "name": "تصویر", + "tooltip": "تصویر(ها)ی ورودی برای تولید تصویر از تصویر. فهرستی از ۱ تا ۱۰ تصویر برای تولید تک یا چند مرجع." + }, + "max_images": { + "name": "حداکثر تصاویر", + "tooltip": "حداکثر تعداد تصاویری که هنگام فعال بودن تولید ترتیبی تصویر (auto) تولید می‌شود. مجموع تصاویر (ورودی + تولید شده) نباید از ۱۵ بیشتر باشد." + }, + "model": { + "name": "مدل", + "tooltip": "نام مدل" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت متنی برای ایجاد یا ویرایش تصویر." + }, + "seed": { + "name": "بذر", + "tooltip": "بذر مورد استفاده برای تولید." + }, + "sequential_image_generation": { + "name": "تولید ترتیبی تصویر", + "tooltip": "حالت گروهی تولید تصویر. «غیرفعال» یک تصویر تولید می‌کند. «خودکار» به مدل اجازه می‌دهد تصمیم بگیرد که چند تصویر مرتبط (مثلاً صحنه‌های داستان، واریاسیون شخصیت) تولید کند." + }, + "size_preset": { + "name": "پیش‌تنظیم اندازه", + "tooltip": "یک اندازه پیشنهادی انتخاب کنید. برای استفاده از عرض و ارتفاع سفارشی، گزینه سفارشی را انتخاب کنید." + }, + "watermark": { + "name": "واترمارک", + "tooltip": "آیا واترمارک «تولید شده توسط هوش مصنوعی» به تصویر اضافه شود یا خیر." + }, + "width": { + "name": "عرض", + "tooltip": "عرض سفارشی برای تصویر. این مقدار فقط زمانی اعمال می‌شود که «پیش‌تنظیم اندازه» روی «سفارشی» باشد." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ByteDanceTextToVideoNode": { + "description": "تولید ویدیو با استفاده از مدل‌های ByteDance از طریق API بر اساس پرامپت", + "display_name": "تبدیل متن به ویدیو ByteDance", + "inputs": { + "aspect_ratio": { + "name": "نسبت تصویر", + "tooltip": "نسبت تصویر ویدیوی خروجی." + }, + "camera_fixed": { + "name": "ثابت بودن دوربین", + "tooltip": "مشخص می‌کند که آیا دوربین ثابت باشد یا خیر. پلتفرم یک دستور برای ثابت کردن دوربین به پرامپت شما اضافه می‌کند، اما تضمینی برای نتیجه نهایی وجود ندارد." + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان", + "tooltip": "مدت زمان ویدیوی خروجی بر حسب ثانیه." + }, + "generate_audio": { + "name": "تولید صدا", + "tooltip": "این پارامتر فقط برای مدل seedance-1-5-pro معتبر است و برای سایر مدل‌ها نادیده گرفته می‌شود." + }, + "model": { + "name": "مدل" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت متنی مورد استفاده برای تولید ویدیو." + }, + "resolution": { + "name": "وضوح", + "tooltip": "وضوح ویدیوی خروجی." + }, + "seed": { + "name": "بذر", + "tooltip": "بذر مورد استفاده برای تولید." + }, + "watermark": { + "name": "واترمارک", + "tooltip": "آیا واترمارک «تولید شده توسط هوش مصنوعی» به ویدیو اضافه شود یا خیر." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CFGGuider": { + "display_name": "CFGGuider", + "inputs": { + "cfg": { + "name": "cfg" + }, + "model": { + "name": "مدل" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CFGNorm": { + "display_name": "CFGNorm", + "inputs": { + "model": { + "name": "مدل" + }, + "strength": { + "name": "شدت" + } + }, + "outputs": { + "0": { + "name": "مدل_اصلاح‌شده", + "tooltip": null + } + } + }, + "CFGZeroStar": { + "display_name": "CFGZeroStar", + "inputs": { + "model": { + "name": "مدل" + } + }, + "outputs": { + "0": { + "name": "مدل_اصلاح‌شده", + "tooltip": null + } + } + }, + "CLIPAttentionMultiply": { + "display_name": "CLIPAttentionMultiply", + "inputs": { + "clip": { + "name": "clip" + }, + "k": { + "name": "k" + }, + "out": { + "name": "out" + }, + "q": { + "name": "q" + }, + "v": { + "name": "v" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CLIPLoader": { + "description": "[دستورالعمل‌ها]\n\nstable_diffusion: clip-l\nstable_cascade: clip-g\nsd3: t5 xxl/ clip-g / clip-l\nstable_audio: t5 base\nmochi: t5 xxl\ncosmos: old t5 xxl\nlumina2: gemma 2 2B\nwan: umt5 xxl\n hidream: llama-3.1 (توصیه می‌شود) یا t5\nomnigen2: qwen vl 2.5 3B", + "display_name": "بارگذاری CLIP", + "inputs": { + "clip_name": { + "name": "clip_name" + }, + "device": { + "name": "device" + }, + "type": { + "name": "type" + } + } + }, + "CLIPMergeAdd": { + "display_name": "CLIPMergeAdd", + "inputs": { + "clip1": { + "name": "clip1" + }, + "clip2": { + "name": "clip2" + } + } + }, + "CLIPMergeSimple": { + "display_name": "CLIPMergeSimple", + "inputs": { + "clip1": { + "name": "clip1" + }, + "clip2": { + "name": "clip2" + }, + "ratio": { + "name": "ratio" + } + } + }, + "CLIPMergeSubtract": { + "display_name": "CLIPMergeSubtract", + "inputs": { + "clip1": { + "name": "clip1" + }, + "clip2": { + "name": "clip2" + }, + "multiplier": { + "name": "multiplier" + } + } + }, + "CLIPSave": { + "display_name": "ذخیره CLIP", + "inputs": { + "clip": { + "name": "clip" + }, + "filename_prefix": { + "name": "filename_prefix" + } + } + }, + "CLIPSetLastLayer": { + "display_name": "تنظیم آخرین لایه CLIP", + "inputs": { + "clip": { + "name": "clip" + }, + "stop_at_clip_layer": { + "name": "stop_at_clip_layer" + } + } + }, + "CLIPTextEncode": { + "description": "یک پرامپت متنی را با استفاده از مدل CLIP به یک بردار تعبیه‌شده تبدیل می‌کند که می‌تواند برای هدایت مدل diffusion جهت تولید تصاویر خاص استفاده شود.", + "display_name": "رمزگذاری متن CLIP (پرامپت)", + "inputs": { + "clip": { + "name": "clip", + "tooltip": "مدل CLIP مورد استفاده برای رمزگذاری متن." + }, + "text": { + "name": "متن", + "tooltip": "متنی که باید رمزگذاری شود." + } + }, + "outputs": { + "0": { + "tooltip": "یک conditioning شامل متن تعبیه‌شده برای هدایت مدل diffusion." + } + } + }, + "CLIPTextEncodeControlnet": { + "display_name": "CLIPTextEncodeControlnet", + "inputs": { + "clip": { + "name": "clip" + }, + "conditioning": { + "name": "conditioning" + }, + "text": { + "name": "متن" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CLIPTextEncodeFlux": { + "display_name": "CLIPTextEncodeFlux", + "inputs": { + "clip": { + "name": "clip" + }, + "clip_l": { + "name": "clip_l" + }, + "guidance": { + "name": "راهنما" + }, + "t5xxl": { + "name": "t5xxl" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CLIPTextEncodeHiDream": { + "display_name": "CLIPTextEncodeHiDream", + "inputs": { + "clip": { + "name": "clip" + }, + "clip_g": { + "name": "clip_g" + }, + "clip_l": { + "name": "clip_l" + }, + "llama": { + "name": "llama" + }, + "t5xxl": { + "name": "t5xxl" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CLIPTextEncodeHunyuanDiT": { + "display_name": "CLIPTextEncodeHunyuanDiT", + "inputs": { + "bert": { + "name": "bert" + }, + "clip": { + "name": "clip" + }, + "mt5xl": { + "name": "mt5xl" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CLIPTextEncodeKandinsky5": { + "display_name": "CLIPTextEncodeKandinsky5", + "inputs": { + "clip": { + "name": "clip" + }, + "clip_l": { + "name": "clip_l" + }, + "qwen25_7b": { + "name": "qwen25_7b" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CLIPTextEncodeLumina2": { + "description": "یک پرامپت سیستمی و یک پرامپت کاربر را با استفاده از مدل CLIP به یک بردار تعبیه‌شده تبدیل می‌کند که می‌تواند برای هدایت مدل diffusion جهت تولید تصاویر خاص استفاده شود.", + "display_name": "رمزگذاری متن CLIP برای Lumina2", + "inputs": { + "clip": { + "name": "clip", + "tooltip": "مدل CLIP مورد استفاده برای رمزگذاری متن." + }, + "system_prompt": { + "name": "پرامپت سیستمی", + "tooltip": "Lumina2 دو نوع پرامپت سیستمی ارائه می‌دهد: Superior: شما یک دستیار هستید که برای تولید تصاویر برتر با بالاترین میزان تطابق متن و تصویر بر اساس پرامپت‌های متنی یا پرامپت‌های کاربر طراحی شده‌اید. Alignment: شما یک دستیار هستید که برای تولید تصاویر باکیفیت با بالاترین میزان تطابق متن و تصویر بر اساس پرامپت‌های متنی طراحی شده‌اید." + }, + "user_prompt": { + "name": "پرامپت کاربر", + "tooltip": "متنی که باید رمزگذاری شود." + } + }, + "outputs": { + "0": { + "tooltip": "یک conditioning شامل متن تعبیه‌شده برای هدایت مدل diffusion." + } + } + }, + "CLIPTextEncodePixArtAlpha": { + "description": "متن را رمزگذاری می‌کند و شرط وضوح تصویر را برای PixArt Alpha تنظیم می‌نماید. برای PixArt Sigma اعمال نمی‌شود.", + "display_name": "CLIPTextEncodePixArtAlpha", + "inputs": { + "clip": { + "name": "clip" + }, + "height": { + "name": "ارتفاع" + }, + "text": { + "name": "متن" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CLIPTextEncodeSD3": { + "display_name": "CLIPTextEncodeSD3", + "inputs": { + "clip": { + "name": "clip" + }, + "clip_g": { + "name": "clip_g" + }, + "clip_l": { + "name": "clip_l" + }, + "empty_padding": { + "name": "فاصله خالی" + }, + "t5xxl": { + "name": "t5xxl" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CLIPTextEncodeSDXL": { + "display_name": "CLIPTextEncodeSDXL", + "inputs": { + "clip": { + "name": "clip" + }, + "crop_h": { + "name": "برش_ارتفاع" + }, + "crop_w": { + "name": "برش_عرض" + }, + "height": { + "name": "ارتفاع" + }, + "target_height": { + "name": "ارتفاع هدف" + }, + "target_width": { + "name": "عرض هدف" + }, + "text_g": { + "name": "متن_g" + }, + "text_l": { + "name": "متن_l" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CLIPTextEncodeSDXLRefiner": { + "display_name": "CLIPTextEncodeSDXLRefiner", + "inputs": { + "ascore": { + "name": "امتیاز" + }, + "clip": { + "name": "clip" + }, + "height": { + "name": "ارتفاع" + }, + "text": { + "name": "متن" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CLIPVisionEncode": { + "display_name": "رمزگذاری CLIP Vision", + "inputs": { + "clip_vision": { + "name": "clip_vision" + }, + "crop": { + "name": "برش" + }, + "image": { + "name": "تصویر" + } + } + }, + "CLIPVisionLoader": { + "display_name": "بارگذاری CLIP Vision", + "inputs": { + "clip_name": { + "name": "نام clip" + } + } + }, + "Canny": { + "display_name": "Canny", + "inputs": { + "high_threshold": { + "name": "آستانه بالا" + }, + "image": { + "name": "تصویر" + }, + "low_threshold": { + "name": "آستانه پایین" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CaseConverter": { + "display_name": "تبدیل حروف", + "inputs": { + "mode": { + "name": "حالت" + }, + "string": { + "name": "رشته" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CenterCropImages": { + "display_name": "برش مرکزی تصاویر", + "inputs": { + "height": { + "name": "ارتفاع", + "tooltip": "ارتفاع برش." + }, + "images": { + "name": "تصاویر", + "tooltip": "تصویر برای پردازش." + }, + "width": { + "name": "عرض", + "tooltip": "عرض برش." + } + }, + "outputs": { + "0": { + "name": "تصاویر", + "tooltip": "تصاویر پردازش‌شده" + } + } + }, + "CheckpointLoader": { + "display_name": "بارگذاری چک‌پوینت با تنظیمات (منسوخ‌شده)", + "inputs": { + "ckpt_name": { + "name": "نام چک‌پوینت" + }, + "config_name": { + "name": "نام تنظیمات" + } + } + }, + "CheckpointLoaderSimple": { + "description": "یک چک‌پوینت مدل diffusion را بارگذاری می‌کند. مدل‌های diffusion برای حذف نویز از latentها استفاده می‌شوند.", + "display_name": "بارگذاری چک‌پوینت", + "inputs": { + "ckpt_name": { + "name": "نام چک‌پوینت", + "tooltip": "نام چک‌پوینت (مدل) برای بارگذاری." + } + }, + "outputs": { + "0": { + "tooltip": "مدل مورد استفاده برای حذف نویز از latentها." + }, + "1": { + "tooltip": "مدل CLIP مورد استفاده برای رمزگذاری دستورات متنی." + }, + "2": { + "tooltip": "مدل VAE مورد استفاده برای رمزگذاری و رمزگشایی تصاویر به و از فضای latent." + } + } + }, + "CheckpointSave": { + "display_name": "ذخیره چک‌پوینت", + "inputs": { + "clip": { + "name": "clip" + }, + "filename_prefix": { + "name": "پیشوند نام فایل" + }, + "model": { + "name": "مدل" + }, + "vae": { + "name": "vae" + } + } + }, + "ChromaRadianceOptions": { + "description": "امکان تنظیم گزینه‌های پیشرفته برای مدل Chroma Radiance را فراهم می‌کند.", + "display_name": "ChromaRadianceOptions", + "inputs": { + "end_sigma": { + "name": "end_sigma", + "tooltip": "آخرین سیگما که این گزینه‌ها اعمال می‌شوند." + }, + "model": { + "name": "model" + }, + "nerf_tile_size": { + "name": "nerf_tile_size", + "tooltip": "امکان تغییر اندازه tile پیش‌فرض NeRF را فراهم می‌کند. مقدار ‎-۱‎ به معنی استفاده از مقدار پیش‌فرض (۳۲) است. مقدار ۰ به معنی استفاده از حالت بدون tile است (ممکن است به حافظه VRAM زیادی نیاز داشته باشد)." + }, + "preserve_wrapper": { + "name": "preserve_wrapper", + "tooltip": "در صورت فعال بودن، به یک wrapper تابع مدل موجود ارجاع داده می‌شود اگر وجود داشته باشد. معمولاً بهتر است این گزینه فعال باقی بماند." + }, + "start_sigma": { + "name": "start_sigma", + "tooltip": "اولین سیگما که این گزینه‌ها اعمال می‌شوند." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CombineHooks2": { + "display_name": "ترکیب هوک‌ها [۲]", + "inputs": { + "hooks_A": { + "name": "هوک_A" + }, + "hooks_B": { + "name": "هوک_B" + } + } + }, + "CombineHooks4": { + "display_name": "ترکیب هوک‌ها [۴]", + "inputs": { + "hooks_A": { + "name": "هوک_A" + }, + "hooks_B": { + "name": "هوک_B" + }, + "hooks_C": { + "name": "هوک_C" + }, + "hooks_D": { + "name": "هوک_D" + } + } + }, + "CombineHooks8": { + "display_name": "ترکیب قلاب‌ها [۸]", + "inputs": { + "hooks_A": { + "name": "hooks_A" + }, + "hooks_B": { + "name": "hooks_B" + }, + "hooks_C": { + "name": "hooks_C" + }, + "hooks_D": { + "name": "hooks_D" + }, + "hooks_E": { + "name": "hooks_E" + }, + "hooks_F": { + "name": "hooks_F" + }, + "hooks_G": { + "name": "hooks_G" + }, + "hooks_H": { + "name": "hooks_H" + } + } + }, + "ComfySwitchNode": { + "display_name": "سوئیچ", + "inputs": { + "on_false": { + "name": "در حالت نادرست" + }, + "on_true": { + "name": "در حالت درست" + }, + "switch": { + "name": "سوئیچ" + } + }, + "outputs": { + "0": { + "name": "خروجی", + "tooltip": null + } + } + }, + "ConditioningAverage": { + "display_name": "میانگین شرطی‌سازی", + "inputs": { + "conditioning_from": { + "name": "شرطی‌سازی مبدا" + }, + "conditioning_to": { + "name": "شرطی‌سازی مقصد" + }, + "conditioning_to_strength": { + "name": "قدرت شرطی‌سازی مقصد" + } + } + }, + "ConditioningCombine": { + "display_name": "شرطی‌سازی (ترکیب)", + "inputs": { + "conditioning_1": { + "name": "شرطی‌سازی ۱" + }, + "conditioning_2": { + "name": "شرطی‌سازی ۲" + } + } + }, + "ConditioningConcat": { + "display_name": "شرطی‌سازی (ادغام)", + "inputs": { + "conditioning_from": { + "name": "شرطی‌سازی مبدا" + }, + "conditioning_to": { + "name": "شرطی‌سازی مقصد" + } + } + }, + "ConditioningSetArea": { + "display_name": "شرطی‌سازی (تنظیم ناحیه)", + "inputs": { + "conditioning": { + "name": "شرطی‌سازی" + }, + "height": { + "name": "ارتفاع" + }, + "strength": { + "name": "قدرت" + }, + "width": { + "name": "عرض" + }, + "x": { + "name": "محور X" + }, + "y": { + "name": "محور Y" + } + } + }, + "ConditioningSetAreaPercentage": { + "display_name": "شرطی‌سازی (تنظیم ناحیه با درصد)", + "inputs": { + "conditioning": { + "name": "شرطی‌سازی" + }, + "height": { + "name": "ارتفاع" + }, + "strength": { + "name": "قدرت" + }, + "width": { + "name": "عرض" + }, + "x": { + "name": "محور X" + }, + "y": { + "name": "محور Y" + } + } + }, + "ConditioningSetAreaPercentageVideo": { + "display_name": "شرطی‌سازی (تنظیم ناحیه با درصد برای ویدیو)", + "inputs": { + "conditioning": { + "name": "شرطی‌سازی" + }, + "height": { + "name": "ارتفاع" + }, + "strength": { + "name": "قدرت" + }, + "temporal": { + "name": "زمانی" + }, + "width": { + "name": "عرض" + }, + "x": { + "name": "محور X" + }, + "y": { + "name": "محور Y" + }, + "z": { + "name": "محور Z" + } + } + }, + "ConditioningSetAreaStrength": { + "display_name": "شرطی‌سازی (تنظیم قدرت ناحیه)", + "inputs": { + "conditioning": { + "name": "شرطی‌سازی" + }, + "strength": { + "name": "قدرت" + } + } + }, + "ConditioningSetDefaultCombine": { + "display_name": "شرطی‌سازی (ترکیب پیش‌فرض)", + "inputs": { + "cond": { + "name": "شرط" + }, + "cond_DEFAULT": { + "name": "شرط پیش‌فرض" + }, + "hooks": { + "name": "قلاب‌ها" + } + } + }, + "ConditioningSetMask": { + "display_name": "شرط‌گذاری (تنظیم ماسک)", + "inputs": { + "conditioning": { + "name": "شرط‌گذاری" + }, + "mask": { + "name": "ماسک" + }, + "set_cond_area": { + "name": "تنظیم ناحیه شرط" + }, + "strength": { + "name": "شدت" + } + } + }, + "ConditioningSetProperties": { + "display_name": "ویژگی‌های مجموعه شرط", + "inputs": { + "cond_NEW": { + "name": "شرط_NEW" + }, + "hooks": { + "name": "هوک‌ها" + }, + "mask": { + "name": "ماسک" + }, + "set_cond_area": { + "name": "تنظیم ناحیه شرط" + }, + "strength": { + "name": "شدت" + }, + "timesteps": { + "name": "گام‌های زمانی" + } + } + }, + "ConditioningSetPropertiesAndCombine": { + "display_name": "ترکیب ویژگی‌های مجموعه شرط", + "inputs": { + "cond": { + "name": "شرط" + }, + "cond_NEW": { + "name": "شرط_NEW" + }, + "hooks": { + "name": "هوک‌ها" + }, + "mask": { + "name": "ماسک" + }, + "set_cond_area": { + "name": "تنظیم ناحیه شرط" + }, + "strength": { + "name": "شدت" + }, + "timesteps": { + "name": "گام‌های زمانی" + } + } + }, + "ConditioningSetTimestepRange": { + "display_name": "محدوده گام زمانی شرط‌گذاری", + "inputs": { + "conditioning": { + "name": "شرط‌گذاری" + }, + "end": { + "name": "پایان" + }, + "start": { + "name": "شروع" + } + } + }, + "ConditioningStableAudio": { + "display_name": "شرط‌گذاری Stable Audio", + "inputs": { + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + }, + "seconds_start": { + "name": "ثانیه شروع" + }, + "seconds_total": { + "name": "کل ثانیه" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + } + } + }, + "ConditioningTimestepsRange": { + "display_name": "محدوده گام‌های زمانی", + "inputs": { + "end_percent": { + "name": "درصد پایان" + }, + "start_percent": { + "name": "درصد شروع" + } + }, + "outputs": { + "1": { + "name": "قبل از محدوده" + }, + "2": { + "name": "بعد از محدوده" + } + } + }, + "ConditioningZeroOut": { + "display_name": "صفر کردن شرط‌گذاری", + "inputs": { + "conditioning": { + "name": "شرط‌گذاری" + } + } + }, + "ContextWindowsManual": { + "description": "تنظیم دستی پنجره‌های زمینه.", + "display_name": "پنجره‌های زمینه (دستی)", + "inputs": { + "closed_loop": { + "name": "حلقه بسته", + "tooltip": "آیا حلقه پنجره زمینه بسته شود؛ فقط برای برنامه‌ریزی حلقه‌ای قابل استفاده است." + }, + "context_length": { + "name": "طول پنجره زمینه", + "tooltip": "طول پنجره زمینه." + }, + "context_overlap": { + "name": "همپوشانی پنجره زمینه", + "tooltip": "میزان همپوشانی پنجره زمینه." + }, + "context_schedule": { + "name": "برنامه‌ریزی پنجره زمینه", + "tooltip": "گام‌بندی پنجره زمینه." + }, + "context_stride": { + "name": "گام پنجره زمینه", + "tooltip": "گام پنجره زمینه؛ فقط برای برنامه‌ریزی یکنواخت قابل استفاده است." + }, + "dim": { + "name": "بُعد", + "tooltip": "بُعدی که پنجره‌های زمینه بر آن اعمال می‌شود." + }, + "freenoise": { + "name": "FreeNoise", + "tooltip": "آیا نویز FreeNoise اعمال شود؛ باعث بهبود ترکیب پنجره‌ها می‌شود." + }, + "fuse_method": { + "name": "روش ادغام", + "tooltip": "روشی که برای ادغام پنجره‌های زمینه استفاده می‌شود." + }, + "model": { + "name": "مدل", + "tooltip": "مدلی که پنجره‌های زمینه هنگام نمونه‌گیری بر آن اعمال می‌شود." + } + }, + "outputs": { + "0": { + "tooltip": "مدل با پنجره‌های زمینه اعمال‌شده هنگام نمونه‌گیری." + } + } + }, + "ControlNetApply": { + "display_name": "اعمال ControlNet (قدیمی)", + "inputs": { + "conditioning": { + "name": "شرط‌گذاری" + }, + "control_net": { + "name": "control_net" + }, + "image": { + "name": "تصویر" + }, + "strength": { + "name": "شدت" + } + } + }, + "ControlNetApplyAdvanced": { + "display_name": "اعمال ControlNet", + "inputs": { + "control_net": { + "name": "control_net" + }, + "end_percent": { + "name": "درصد پایان" + }, + "image": { + "name": "تصویر" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + }, + "start_percent": { + "name": "درصد شروع" + }, + "strength": { + "name": "شدت" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "مثبت" + }, + "1": { + "name": "منفی" + } + } + }, + "ControlNetApplySD3": { + "display_name": "اعمال ControlNet با VAE", + "inputs": { + "control_net": { + "name": "control_net" + }, + "end_percent": { + "name": "درصد پایان" + }, + "image": { + "name": "تصویر" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + }, + "start_percent": { + "name": "درصد شروع" + }, + "strength": { + "name": "شدت" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + } + } + }, + "ControlNetInpaintingAliMamaApply": { + "display_name": "ControlNetInpaintingAliMamaApply", + "inputs": { + "control_net": { + "name": "control_net" + }, + "end_percent": { + "name": "درصد پایان" + }, + "image": { + "name": "تصویر" + }, + "mask": { + "name": "ماسک" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + }, + "start_percent": { + "name": "درصد شروع" + }, + "strength": { + "name": "قدرت" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + } + } + }, + "ControlNetLoader": { + "display_name": "بارگذاری مدل ControlNet", + "inputs": { + "control_net_name": { + "name": "نام control_net" + } + } + }, + "CosmosImageToVideoLatent": { + "display_name": "CosmosImageToVideoLatent", + "inputs": { + "batch_size": { + "name": "اندازه دسته" + }, + "end_image": { + "name": "تصویر پایان" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "start_image": { + "name": "تصویر شروع" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CosmosPredict2ImageToVideoLatent": { + "display_name": "CosmosPredict2ImageToVideoLatent", + "inputs": { + "batch_size": { + "name": "اندازه دسته" + }, + "end_image": { + "name": "تصویر پایان" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "start_image": { + "name": "تصویر شروع" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CreateHookKeyframe": { + "display_name": "ایجاد Keyframe قلاب", + "inputs": { + "prev_hook_kf": { + "name": "Keyframe قلاب قبلی" + }, + "start_percent": { + "name": "درصد شروع" + }, + "strength_mult": { + "name": "ضریب قدرت" + } + }, + "outputs": { + "0": { + "name": "HOOK_KF" + } + } + }, + "CreateHookKeyframesFromFloats": { + "display_name": "ایجاد Keyframe قلاب از اعداد اعشاری", + "inputs": { + "end_percent": { + "name": "درصد پایان" + }, + "floats_strength": { + "name": "قدرت اعشاری" + }, + "prev_hook_kf": { + "name": "Keyframe قلاب قبلی" + }, + "print_keyframes": { + "name": "چاپ Keyframeها" + }, + "start_percent": { + "name": "درصد شروع" + } + }, + "outputs": { + "0": { + "name": "HOOK_KF" + } + } + }, + "CreateHookKeyframesInterpolated": { + "display_name": "ایجاد Keyframe های Hook با میان‌یابی", + "inputs": { + "end_percent": { + "name": "درصد پایان" + }, + "interpolation": { + "name": "میان‌یابی" + }, + "keyframes_count": { + "name": "تعداد keyframe" + }, + "prev_hook_kf": { + "name": "keyframe قبلی hook" + }, + "print_keyframes": { + "name": "نمایش keyframeها" + }, + "start_percent": { + "name": "درصد شروع" + }, + "strength_end": { + "name": "قدرت پایان" + }, + "strength_start": { + "name": "قدرت شروع" + } + }, + "outputs": { + "0": { + "name": "HOOK_KF" + } + } + }, + "CreateHookLora": { + "display_name": "ایجاد Hook LoRA", + "inputs": { + "lora_name": { + "name": "نام LoRA" + }, + "prev_hooks": { + "name": "hookهای قبلی" + }, + "strength_clip": { + "name": "قدرت clip" + }, + "strength_model": { + "name": "قدرت مدل" + } + } + }, + "CreateHookLoraModelOnly": { + "display_name": "ایجاد Hook LoRA (فقط مدل)", + "inputs": { + "lora_name": { + "name": "نام LoRA" + }, + "prev_hooks": { + "name": "hookهای قبلی" + }, + "strength_model": { + "name": "قدرت مدل" + } + } + }, + "CreateHookModelAsLora": { + "display_name": "ایجاد Hook مدل به عنوان LoRA", + "inputs": { + "ckpt_name": { + "name": "نام checkpoint" + }, + "prev_hooks": { + "name": "hookهای قبلی" + }, + "strength_clip": { + "name": "قدرت clip" + }, + "strength_model": { + "name": "قدرت مدل" + } + } + }, + "CreateHookModelAsLoraModelOnly": { + "display_name": "ایجاد Hook مدل به عنوان LoRA (فقط مدل)", + "inputs": { + "ckpt_name": { + "name": "نام checkpoint" + }, + "prev_hooks": { + "name": "hookهای قبلی" + }, + "strength_model": { + "name": "قدرت مدل" + } + } + }, + "CreateVideo": { + "description": "ایجاد ویدیو از تصاویر.", + "display_name": "ایجاد ویدیو", + "inputs": { + "audio": { + "name": "صدا", + "tooltip": "صدایی که به ویدیو اضافه می‌شود." + }, + "fps": { + "name": "فریم بر ثانیه" + }, + "images": { + "name": "تصاویر", + "tooltip": "تصاویری که ویدیو از آن‌ها ساخته می‌شود." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CropMask": { + "display_name": "برش Mask", + "inputs": { + "height": { + "name": "ارتفاع" + }, + "mask": { + "name": "mask" + }, + "width": { + "name": "عرض" + }, + "x": { + "name": "محور x" + }, + "y": { + "name": "محور y" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CustomCombo": { + "display_name": "ترکیب سفارشی", + "inputs": { + "choice": { + "name": "انتخاب" + }, + "index": { + }, + "option1": { + } + }, + "outputs": [ + null, + { + "name": "INDEX", + "tooltip": null + } + ] + }, + "DiffControlNetLoader": { + "display_name": "بارگذاری مدل ControlNet (diff)", + "inputs": { + "control_net_name": { + "name": "نام ControlNet" + }, + "model": { + "name": "مدل" + } + } + }, + "DifferentialDiffusion": { + "display_name": "دیفرانسیل دیفیوژن", + "inputs": { + "model": { + "name": "مدل" + }, + "strength": { + "name": "شدت" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "DiffusersLoader": { + "display_name": "بارگذار Diffusers", + "inputs": { + "model_path": { + "name": "مسیر مدل" + } + } + }, + "DisableNoise": { + "display_name": "غیرفعال‌سازی نویز", + "outputs": { + "0": { + "tooltip": null + } + } + }, + "DualCFGGuider": { + "display_name": "راهنمای DualCFG", + "inputs": { + "cfg_cond2_negative": { + "name": "cfg_cond2 منفی" + }, + "cfg_conds": { + "name": "شرایط cfg" + }, + "cond1": { + "name": "شرط ۱" + }, + "cond2": { + "name": "شرط ۲" + }, + "model": { + "name": "مدل" + }, + "negative": { + "name": "منفی" + }, + "style": { + "name": "استایل" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "DualCLIPLoader": { + "description": "[دستورالعمل‌ها]\n\nsdxl: clip-l، clip-g\nsd3: clip-l، clip-g / clip-l، t5 / clip-g، t5\nflux: clip-l، t5\nhidream: حداقل یکی از t5 یا llama، توصیه می‌شود t5 و llama\nhunyuan_image: qwen2.5vl ۷b و byt5 small\nnewbie: gemma-۳-۴b-it، jina clip v2", + "display_name": "بارگذار DualCLIP", + "inputs": { + "clip_name1": { + "name": "نام clip ۱" + }, + "clip_name2": { + "name": "نام clip ۲" + }, + "device": { + "name": "دستگاه" + }, + "type": { + "name": "نوع" + } + } + }, + "EasyCache": { + "description": "پیاده‌سازی بومی EasyCache.", + "display_name": "ایزی‌کش", + "inputs": { + "end_percent": { + "name": "درصد پایان", + "tooltip": "مرحله نمونه‌گیری نسبی برای پایان استفاده از EasyCache." + }, + "model": { + "name": "مدل", + "tooltip": "مدلی که EasyCache به آن اضافه می‌شود." + }, + "reuse_threshold": { + "name": "آستانه استفاده مجدد", + "tooltip": "آستانه برای استفاده مجدد از مراحل کش شده." + }, + "start_percent": { + "name": "درصد شروع", + "tooltip": "مرحله نمونه‌گیری نسبی برای شروع استفاده از EasyCache." + }, + "verbose": { + "name": "گزارش‌گیری کامل", + "tooltip": "آیا اطلاعات کامل ثبت شود یا خیر." + } + }, + "outputs": { + "0": { + "tooltip": "مدل با EasyCache." + } + } + }, + "EmptyAceStepLatentAudio": { + "display_name": "EmptyAceStepLatentAudio", + "inputs": { + "batch_size": { + "name": "اندازه بچ", + "tooltip": "تعداد تصاویر latent در هر بچ." + }, + "seconds": { + "name": "ثانیه" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyAudio": { + "display_name": "صدای خالی", + "inputs": { + "channels": { + "name": "کانال‌ها", + "tooltip": "تعداد کانال‌های صوتی (۱ برای مونو، ۲ برای استریو)." + }, + "duration": { + "name": "مدت زمان", + "tooltip": "مدت زمان کلیپ صوتی خالی بر حسب ثانیه" + }, + "sample_rate": { + "name": "نرخ نمونه‌برداری", + "tooltip": "نرخ نمونه‌برداری کلیپ صوتی خالی." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyChromaRadianceLatentImage": { + "display_name": "تصویر نهفته ChromaRadiance خالی", + "inputs": { + "batch_size": { + "name": "اندازه دسته" + }, + "height": { + "name": "ارتفاع" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyCosmosLatentVideo": { + "display_name": "ویدیوی نهفته Cosmos خالی", + "inputs": { + "batch_size": { + "name": "اندازه دسته" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyFlux2LatentImage": { + "display_name": "تصویر نهفته Flux 2 خالی", + "inputs": { + "batch_size": { + "name": "اندازه دسته" + }, + "height": { + "name": "ارتفاع" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyHunyuanImageLatent": { + "display_name": "تصویر نهفته Hunyuan خالی", + "inputs": { + "batch_size": { + "name": "اندازه دسته" + }, + "height": { + "name": "ارتفاع" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyHunyuanLatentVideo": { + "display_name": "ویدیوی نهفته Hunyuan ۱.۰ خالی", + "inputs": { + "batch_size": { + "name": "اندازه دسته" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyHunyuanVideo15Latent": { + "display_name": "ویدیوی نهفته Hunyuan ۱.۵ خالی", + "inputs": { + "batch_size": { + "name": "اندازه دسته" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyImage": { + "display_name": "تصویر خالی", + "inputs": { + "batch_size": { + "name": "اندازه دسته" + }, + "color": { + "name": "رنگ" + }, + "height": { + "name": "ارتفاع" + }, + "width": { + "name": "عرض" + } + } + }, + "EmptyLTXVLatentVideo": { + "display_name": "ویدیوی نهفته خالی LTXV", + "inputs": { + "batch_size": { + "name": "اندازه دسته" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyLatentAudio": { + "display_name": "صدای نهفته خالی", + "inputs": { + "batch_size": { + "name": "اندازه دسته", + "tooltip": "تعداد تصاویر نهفته در دسته." + }, + "seconds": { + "name": "ثانیه" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyLatentHunyuan3Dv2": { + "display_name": "نهفته Hunyuan 3D v2 خالی", + "inputs": { + "batch_size": { + "name": "اندازه دسته", + "tooltip": "تعداد تصاویر نهفته در دسته." + }, + "resolution": { + "name": "وضوح" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyLatentImage": { + "description": "یک دسته جدید از تصاویر نهفته خالی برای حذف نویز از طریق نمونه‌گیری ایجاد کنید.", + "display_name": "تصویر نهفته خالی", + "inputs": { + "batch_size": { + "name": "اندازه دسته", + "tooltip": "تعداد تصاویر نهفته در هر دسته." + }, + "height": { + "name": "ارتفاع", + "tooltip": "ارتفاع تصاویر نهفته بر حسب پیکسل." + }, + "width": { + "name": "عرض", + "tooltip": "عرض تصاویر نهفته بر حسب پیکسل." + } + }, + "outputs": { + "0": { + "tooltip": "دسته تصاویر نهفته خالی." + } + } + }, + "EmptyMochiLatentVideo": { + "display_name": "ویدیوی نهفته خالی Mochi", + "inputs": { + "batch_size": { + "name": "اندازه دسته" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyQwenImageLayeredLatentImage": { + "display_name": "تصویر نهفته لایه‌ای Qwen خالی", + "inputs": { + "batch_size": { + "name": "اندازه دسته" + }, + "height": { + "name": "ارتفاع" + }, + "layers": { + "name": "لایه‌ها" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptySD3LatentImage": { + "display_name": "تصویر نهفته خالی SD3", + "inputs": { + "batch_size": { + "name": "اندازه دسته" + }, + "height": { + "name": "ارتفاع" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Epsilon Scaling": { + "display_name": "مقیاس‌دهی اپسیلون", + "inputs": { + "model": { + "name": "مدل" + }, + "scaling_factor": { + "name": "ضریب مقیاس‌دهی" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ExponentialScheduler": { + "display_name": "زمان‌بندی نمایی", + "inputs": { + "sigma_max": { + "name": "سیگما بیشینه" + }, + "sigma_min": { + "name": "سیگما کمینه" + }, + "steps": { + "name": "گام‌ها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ExtendIntermediateSigmas": { + "display_name": "گسترش سیگماهای میانی", + "inputs": { + "end_at_sigma": { + "name": "پایان در سیگما" + }, + "sigmas": { + "name": "سیگماها" + }, + "spacing": { + "name": "فاصله" + }, + "start_at_sigma": { + "name": "شروع از سیگما" + }, + "steps": { + "name": "گام‌ها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FeatherMask": { + "display_name": "FeatherMask", + "inputs": { + "bottom": { + "name": "پایین" + }, + "left": { + "name": "چپ" + }, + "mask": { + "name": "ماسک" + }, + "right": { + "name": "راست" + }, + "top": { + "name": "بالا" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FlipSigmas": { + "display_name": "FlipSigmas", + "inputs": { + "sigmas": { + "name": "سیگماها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2MaxImageNode": { + "description": "تولید تصویر به صورت همزمان بر اساس پرامپت و وضوح تصویر.", + "display_name": "Flux.2 [max] Image", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "height": { + "name": "ارتفاع" + }, + "images": { + "name": "تصاویر", + "tooltip": "حداکثر ۹ تصویر به عنوان مرجع قابل استفاده است." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت برای تولید یا ویرایش تصویر" + }, + "prompt_upsampling": { + "name": "افزایش وضوح پرامپت", + "tooltip": "آیا افزایش وضوح پرامپت انجام شود یا خیر. در صورت فعال بودن، پرامپت به طور خودکار برای تولید خلاقانه‌تر تغییر می‌کند." + }, + "seed": { + "name": "بذر", + "tooltip": "بذر تصادفی برای ایجاد نویز." + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2ProImageNode": { + "description": "تولید تصویر به صورت همزمان بر اساس پرامپت و وضوح تصویر.", + "display_name": "Flux.2 [pro] Image", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "height": { + "name": "ارتفاع" + }, + "images": { + "name": "تصاویر", + "tooltip": "حداکثر ۹ تصویر به عنوان مرجع قابل استفاده است." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت برای تولید یا ویرایش تصویر" + }, + "prompt_upsampling": { + "name": "افزایش وضوح پرامپت", + "tooltip": "آیا افزایش وضوح پرامپت انجام شود یا خیر. در صورت فعال بودن، پرامپت به طور خودکار برای تولید خلاقانه‌تر تغییر می‌کند." + }, + "seed": { + "name": "بذر", + "tooltip": "بذر تصادفی برای ایجاد نویز." + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2Scheduler": { + "display_name": "Flux2Scheduler", + "inputs": { + "height": { + "name": "ارتفاع" + }, + "steps": { + "name": "گام‌ها" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FluxDisableGuidance": { + "description": "این نود راهنمایی را به طور کامل در Flux و مدل‌های مشابه Flux غیرفعال می‌کند.", + "display_name": "FluxDisableGuidance", + "inputs": { + "conditioning": { + "name": "شرایط" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FluxGuidance": { + "display_name": "FluxGuidance", + "inputs": { + "conditioning": { + "name": "شرایط" + }, + "guidance": { + "name": "راهنمایی" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FluxKontextImageScale": { + "description": "این نود تصویر را به اندازه‌ای تغییر می‌دهد که برای flux kontext بهینه‌تر باشد.", + "display_name": "FluxKontextImageScale", + "inputs": { + "image": { + "name": "تصویر" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FluxKontextMaxImageNode": { + "description": "ویرایش تصاویر با استفاده از Flux.1 Kontext [pro] از طریق API بر اساس پرامپت و نسبت تصویر.", + "display_name": "Flux.1 Kontext [max] Image", + "inputs": { + "aspect_ratio": { + "name": "نسبت تصویر", + "tooltip": "نسبت تصویر؛ باید بین ۱:۴ تا ۴:۱ باشد." + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "guidance": { + "name": "راهنمایی", + "tooltip": "قدرت راهنمایی برای فرایند تولید تصویر" + }, + "input_image": { + "name": "تصویر ورودی" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت برای تولید تصویر - مشخص کنید چه چیزی و چگونه ویرایش شود." + }, + "prompt_upsampling": { + "name": "افزایش وضوح پرامپت", + "tooltip": "آیا افزایش وضوح روی پرامپت انجام شود یا خیر. در صورت فعال بودن، پرامپت به طور خودکار برای تولید خلاقانه‌تر تغییر می‌کند، اما نتایج غیرقطعی خواهند بود (همان بذر دقیقاً همان نتیجه را تولید نمی‌کند)." + }, + "seed": { + "name": "بذر", + "tooltip": "بذر تصادفی مورد استفاده برای ایجاد نویز." + }, + "steps": { + "name": "گام‌ها", + "tooltip": "تعداد گام‌ها برای فرایند تولید تصویر" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FluxKontextMultiReferenceLatentMethod": { + "display_name": "روش مرجع مدل ویرایش", + "inputs": { + "conditioning": { + "name": "شرط‌گذاری" + }, + "reference_latents_method": { + "name": "روش latent مرجع" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FluxKontextProImageNode": { + "description": "ویرایش تصاویر با استفاده از Flux.1 Kontext [pro] از طریق API بر اساس پرامپت و نسبت تصویر.", + "display_name": "Flux.1 Kontext [pro] Image", + "inputs": { + "aspect_ratio": { + "name": "نسبت تصویر", + "tooltip": "نسبت تصویر؛ باید بین ۱:۴ تا ۴:۱ باشد." + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "guidance": { + "name": "راهنمایی", + "tooltip": "قدرت راهنمایی برای فرایند تولید تصویر" + }, + "input_image": { + "name": "تصویر ورودی" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت برای تولید تصویر - مشخص کنید چه چیزی و چگونه ویرایش شود." + }, + "prompt_upsampling": { + "name": "افزایش وضوح پرامپت", + "tooltip": "آیا افزایش وضوح روی پرامپت انجام شود یا خیر. در صورت فعال بودن، پرامپت به طور خودکار برای تولید خلاقانه‌تر تغییر می‌کند، اما نتایج غیرقطعی خواهند بود (همان بذر دقیقاً همان نتیجه را تولید نمی‌کند)." + }, + "seed": { + "name": "بذر", + "tooltip": "بذر تصادفی مورد استفاده برای ایجاد نویز." + }, + "steps": { + "name": "گام‌ها", + "tooltip": "تعداد گام‌ها برای فرایند تولید تصویر" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FluxProExpandNode": { + "description": "تصویر را بر اساس پرامپت گسترش می‌دهد.", + "display_name": "Flux.1 گسترش تصویر", + "inputs": { + "bottom": { + "name": "پایین", + "tooltip": "تعداد پیکسل‌هایی که باید به پایین تصویر افزوده شود" + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "guidance": { + "name": "قدرت راهنمایی", + "tooltip": "قدرت راهنمایی برای فرآیند تولید تصویر" + }, + "image": { + "name": "تصویر" + }, + "left": { + "name": "چپ", + "tooltip": "تعداد پیکسل‌هایی که باید به سمت چپ تصویر افزوده شود" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت برای تولید تصویر" + }, + "prompt_upsampling": { + "name": "افزایش وضوح پرامپت", + "tooltip": "آیا افزایش وضوح روی پرامپت انجام شود یا خیر. در صورت فعال بودن، پرامپت به طور خودکار برای تولید خلاقانه‌تر تغییر می‌کند، اما نتایج غیرقطعی خواهند بود (یک بذر یکسان دقیقاً همان نتیجه را تولید نمی‌کند)." + }, + "right": { + "name": "راست", + "tooltip": "تعداد پیکسل‌هایی که باید به سمت راست تصویر افزوده شود" + }, + "seed": { + "name": "بذر", + "tooltip": "بذر تصادفی مورد استفاده برای ایجاد نویز." + }, + "steps": { + "name": "تعداد مراحل", + "tooltip": "تعداد مراحل برای فرآیند تولید تصویر" + }, + "top": { + "name": "بالا", + "tooltip": "تعداد پیکسل‌هایی که باید به بالای تصویر افزوده شود" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FluxProFillNode": { + "description": "تصویر را بر اساس ماسک و پرامپت بازسازی می‌کند.", + "display_name": "Flux.1 پر کردن تصویر", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "guidance": { + "name": "قدرت راهنمایی", + "tooltip": "قدرت راهنمایی برای فرآیند تولید تصویر" + }, + "image": { + "name": "تصویر" + }, + "mask": { + "name": "ماسک" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت برای تولید تصویر" + }, + "prompt_upsampling": { + "name": "افزایش وضوح پرامپت", + "tooltip": "آیا افزایش وضوح روی پرامپت انجام شود یا خیر. در صورت فعال بودن، پرامپت به طور خودکار برای تولید خلاقانه‌تر تغییر می‌کند، اما نتایج غیرقطعی خواهند بود (یک بذر یکسان دقیقاً همان نتیجه را تولید نمی‌کند)." + }, + "seed": { + "name": "بذر", + "tooltip": "بذر تصادفی مورد استفاده برای ایجاد نویز." + }, + "steps": { + "name": "تعداد مراحل", + "tooltip": "تعداد مراحل برای فرآیند تولید تصویر" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FluxProUltraImageNode": { + "description": "تولید تصویر با استفاده از Flux Pro ۱.۱ Ultra از طریق API بر اساس پرامپت و وضوح تصویر.", + "display_name": "Flux ۱.۱ [pro] Ultra Image", + "inputs": { + "aspect_ratio": { + "name": "نسبت تصویر", + "tooltip": "نسبت تصویر؛ باید بین ۱:۴ تا ۴:۱ باشد." + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "image_prompt": { + "name": "پرامپت تصویری" + }, + "image_prompt_strength": { + "name": "قدرت پرامپت تصویری", + "tooltip": "ترکیب بین پرامپت و پرامپت تصویری." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت برای تولید تصویر" + }, + "prompt_upsampling": { + "name": "افزایش وضوح پرامپت", + "tooltip": "آیا افزایش وضوح روی پرامپت انجام شود یا خیر. اگر فعال باشد، پرامپت به طور خودکار برای تولید خلاقانه‌تر تغییر می‌کند، اما نتایج غیرقطعی خواهند بود (یک seed یکسان دقیقاً همان نتیجه را تولید نمی‌کند)." + }, + "raw": { + "name": "خام", + "tooltip": "در صورت فعال بودن، تصاویر با پردازش کمتر و ظاهر طبیعی‌تر تولید می‌شوند." + }, + "seed": { + "name": "seed", + "tooltip": "seed تصادفی مورد استفاده برای ایجاد نویز." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FreSca": { + "description": "اعمال مقیاس‌دهی وابسته به فرکانس بر راهنمایی", + "display_name": "FreSca", + "inputs": { + "freq_cutoff": { + "name": "آستانه فرکانس", + "tooltip": "تعداد اندیس‌های فرکانسی اطراف مرکز که به عنوان فرکانس پایین در نظر گرفته می‌شوند" + }, + "model": { + "name": "مدل" + }, + "scale_high": { + "name": "ضریب مقیاس بالا", + "tooltip": "ضریب مقیاس برای اجزای با فرکانس بالا" + }, + "scale_low": { + "name": "ضریب مقیاس پایین", + "tooltip": "ضریب مقیاس برای اجزای با فرکانس پایین" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FreeU": { + "display_name": "FreeU", + "inputs": { + "b1": { + "name": "b1" + }, + "b2": { + "name": "b2" + }, + "model": { + "name": "مدل" + }, + "s1": { + "name": "s1" + }, + "s2": { + "name": "s2" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FreeU_V2": { + "display_name": "FreeU_V2", + "inputs": { + "b1": { + "name": "b1" + }, + "b2": { + "name": "b2" + }, + "model": { + "name": "مدل" + }, + "s1": { + "name": "s1" + }, + "s2": { + "name": "s2" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "GITSScheduler": { + "display_name": "برنامه‌ریز GITS", + "inputs": { + "coeff": { + "name": "ضریب" + }, + "denoise": { + "name": "کاهش نویز" + }, + "steps": { + "name": "گام‌ها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "GLIGENLoader": { + "display_name": "بارگذار GLIGEN", + "inputs": { + "gligen_name": { + "name": "نام GLIGEN" + } + } + }, + "GLIGENTextBoxApply": { + "display_name": "GLIGENTextBoxApply", + "inputs": { + "clip": { + "name": "clip" + }, + "conditioning_to": { + "name": "شرط‌دهی به" + }, + "gligen_textbox_model": { + "name": "مدل GLIGEN Textbox" + }, + "height": { + "name": "ارتفاع" + }, + "text": { + "name": "متن" + }, + "width": { + "name": "عرض" + }, + "x": { + "name": "محور افقی (x)" + }, + "y": { + "name": "محور عمودی (y)" + } + } + }, + "GeminiImage2Node": { + "description": "تولید یا ویرایش تصاویر به صورت همزمان از طریق Google Vertex API.", + "display_name": "Nano Banana Pro (Google Gemini Image)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "اگر روی 'auto' تنظیم شود، نسبت تصویر ورودی شما را تطبیق می‌دهد؛ اگر تصویری ارائه نشود، معمولاً یک تصویر مربعی با نسبت ۱۶:۹ تولید می‌شود." + }, + "control_after_generate": { + "name": "control after generate" + }, + "files": { + "name": "files", + "tooltip": "فایل(های) اختیاری برای استفاده به عنوان زمینه برای مدل. ورودی‌ها را از node Gemini Generate Content Input Files می‌پذیرد." + }, + "images": { + "name": "images", + "tooltip": "تصویر(های) مرجع اختیاری. برای افزودن چند تصویر، از node Batch Images استفاده کنید (تا ۱۴ تصویر)." + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "متن راهنما برای توصیف تصویری که باید تولید شود یا ویرایش‌هایی که باید اعمال گردد. هرگونه محدودیت، سبک یا جزئیاتی که مدل باید رعایت کند را وارد کنید." + }, + "resolution": { + "name": "resolution", + "tooltip": "وضوح خروجی مورد نظر. برای 2K/4K از upscaler بومی Gemini استفاده می‌شود." + }, + "response_modalities": { + "name": "response_modalities", + "tooltip": "برای خروجی فقط تصویر، 'IMAGE' را انتخاب کنید یا برای دریافت هر دو تصویر تولید شده و پاسخ متنی، 'IMAGE+TEXT' را انتخاب نمایید." + }, + "seed": { + "name": "seed", + "tooltip": "زمانی که مقدار seed به یک مقدار مشخص تنظیم شود، مدل تلاش می‌کند تا پاسخ مشابهی برای درخواست‌های تکراری ارائه دهد. خروجی قطعی تضمین نمی‌شود. همچنین، تغییر مدل یا تنظیمات پارامترها مانند دما (temperature) می‌تواند باعث تغییر در پاسخ حتی با همان مقدار seed شود. به طور پیش‌فرض، مقدار seed به صورت تصادفی انتخاب می‌شود." + }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "دستورالعمل‌های پایه‌ای که رفتار هوش مصنوعی را تعیین می‌کند." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "tooltip": null + } + } + }, + "GeminiImageNode": { + "description": "ویرایش تصاویر به صورت همزمان از طریق Google API.", + "display_name": "Nano Banana (Google Gemini Image)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "به طور پیش‌فرض اندازه تصویر خروجی را با تصویر ورودی شما تطبیق می‌دهد، یا در غیر این صورت تصاویر مربعی ۱:۱ تولید می‌کند." + }, + "control_after_generate": { + "name": "control after generate" + }, + "files": { + "name": "files", + "tooltip": "فایل(های) اختیاری برای استفاده به عنوان زمینه برای مدل. ورودی‌ها را از node Gemini Generate Content Input Files می‌پذیرد." + }, + "images": { + "name": "images", + "tooltip": "تصویر(های) اختیاری برای استفاده به عنوان زمینه برای مدل. برای افزودن چند تصویر می‌توانید از node Batch Images استفاده کنید." + }, + "model": { + "name": "model", + "tooltip": "مدل Gemini مورد استفاده برای تولید پاسخ‌ها." + }, + "prompt": { + "name": "prompt", + "tooltip": "متن راهنما برای تولید" + }, + "response_modalities": { + "name": "response_modalities", + "tooltip": "برای خروجی فقط تصویر، 'IMAGE' را انتخاب کنید یا برای دریافت هر دو تصویر تولید شده و پاسخ متنی، 'IMAGE+TEXT' را انتخاب نمایید." + }, + "seed": { + "name": "seed", + "tooltip": "زمانی که مقدار seed به یک مقدار مشخص تنظیم شود، مدل تلاش می‌کند تا پاسخ مشابهی برای درخواست‌های تکراری ارائه دهد. خروجی قطعی تضمین نمی‌شود. همچنین، تغییر مدل یا تنظیمات پارامترها مانند دما (temperature) می‌تواند باعث تغییر در پاسخ حتی با همان مقدار seed شود. به طور پیش‌فرض، مقدار seed به صورت تصادفی انتخاب می‌شود." + }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "دستورالعمل‌های پایه‌ای که رفتار هوش مصنوعی را تعیین می‌کند." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "tooltip": null + } + } + }, + "GeminiInputFiles": { + "description": "فایل‌های ورودی را بارگذاری و آماده می‌کند تا به عنوان ورودی برای نودهای Gemini LLM استفاده شوند. این فایل‌ها هنگام تولید پاسخ توسط مدل Gemini خوانده می‌شوند. محتوای فایل متنی در محدودیت توکن محاسبه می‌شود. 🛈 نکته: می‌توانید این نود را با سایر نودهای فایل ورودی Gemini زنجیره کنید.", + "display_name": "فایل‌های ورودی Gemini", + "inputs": { + "GEMINI_INPUT_FILES": { + "name": "GEMINI_INPUT_FILES", + "tooltip": "یک یا چند فایل اضافی اختیاری برای گروه‌بندی با فایلی که از این نود بارگذاری شده است. امکان زنجیره‌سازی فایل‌های ورودی را فراهم می‌کند تا یک پیام بتواند شامل چندین فایل ورودی باشد." + }, + "file": { + "name": "فایل", + "tooltip": "فایل‌های ورودی برای افزودن به زمینه مدل. در حال حاضر فقط فایل‌های متنی (.txt) و PDF (.pdf) پذیرفته می‌شوند." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "GeminiNode": { + "description": "تولید پاسخ متنی با مدل هوش مصنوعی Gemini گوگل. می‌توانید انواع مختلفی از ورودی‌ها (متن، تصویر، صوت، ویدئو) را به عنوان زمینه برای تولید پاسخ‌های مرتبط‌تر و معنادارتر ارائه دهید.", + "display_name": "Google Gemini", + "inputs": { + "audio": { + "name": "صوت", + "tooltip": "صوت اختیاری برای استفاده به عنوان زمینه مدل." + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "files": { + "name": "فایل‌ها", + "tooltip": "فایل(های) اختیاری برای استفاده به عنوان زمینه مدل. ورودی را از نود Gemini Generate Content Input Files می‌پذیرد." + }, + "images": { + "name": "تصاویر", + "tooltip": "تصاویر اختیاری برای استفاده به عنوان زمینه مدل. برای افزودن چندین تصویر می‌توانید از نود Batch Images استفاده کنید." + }, + "model": { + "name": "مدل", + "tooltip": "مدل Gemini مورد استفاده برای تولید پاسخ‌ها." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "ورودی‌های متنی به مدل برای تولید پاسخ. می‌توانید دستورالعمل‌های دقیق، پرسش‌ها یا زمینه مورد نظر را برای مدل وارد کنید." + }, + "seed": { + "name": "seed", + "tooltip": "زمانی که مقدار seed ثابت باشد، مدل تلاش می‌کند تا برای درخواست‌های تکراری پاسخ یکسانی ارائه دهد. خروجی قطعی تضمین نمی‌شود. همچنین تغییر مدل یا تنظیمات پارامترها مانند دما می‌تواند باعث تغییر پاسخ حتی با همان مقدار seed شود. به طور پیش‌فرض، مقدار seed به صورت تصادفی انتخاب می‌شود." + }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "دستورالعمل‌های پایه‌ای که رفتار هوش مصنوعی را تعیین می‌کند." + }, + "video": { + "name": "ویدئو", + "tooltip": "ویدئوی اختیاری برای استفاده به عنوان زمینه مدل." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "GenerateTracks": { + "display_name": "تولید مسیرها", + "inputs": { + "bezier": { + "name": "Bezier", + "tooltip": "فعال‌سازی مسیر منحنی Bezier با استفاده از نقطه میانی به عنوان نقطه کنترل." + }, + "end_x": { + "name": "مختصات X پایان", + "tooltip": "مختصات X نرمال‌شده (۰-۱) برای موقعیت پایان." + }, + "end_y": { + "name": "مختصات Y پایان", + "tooltip": "مختصات Y نرمال‌شده (۰-۱) برای موقعیت پایان." + }, + "height": { + "name": "ارتفاع" + }, + "interpolation": { + "name": "درون‌یابی", + "tooltip": "کنترل زمان‌بندی/سرعت حرکت در طول مسیر." + }, + "mid_x": { + "name": "کنترل X میانی", + "tooltip": "نقطه کنترل X نرمال‌شده برای منحنی Bezier. فقط زمانی استفاده می‌شود که 'bezier' فعال باشد." + }, + "mid_y": { + "name": "کنترل Y میانی", + "tooltip": "نقطه کنترل Y نرمال‌شده برای منحنی Bezier. فقط زمانی استفاده می‌شود که 'bezier' فعال باشد." + }, + "num_frames": { + "name": "تعداد فریم‌ها" + }, + "num_tracks": { + "name": "تعداد مسیرها" + }, + "start_x": { + "name": "مختصات X شروع", + "tooltip": "مختصات X نرمال‌شده (۰-۱) برای موقعیت شروع." + }, + "start_y": { + "name": "مختصات Y شروع", + "tooltip": "مختصات Y نرمال‌شده (۰-۱) برای موقعیت شروع." + }, + "track_mask": { + "name": "ماسک مسیر", + "tooltip": "ماسک اختیاری برای نمایش فریم‌های قابل مشاهده." + }, + "track_spread": { + "name": "فاصله مسیرها", + "tooltip": "فاصله نرمال‌شده بین مسیرها. مسیرها عمود بر جهت حرکت پخش می‌شوند." + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "طول مسیر", + "tooltip": null + } + } + }, + "GetImageSize": { + "description": "عرض و ارتفاع تصویر را بازمی‌گرداند و تصویر را بدون تغییر عبور می‌دهد.", + "display_name": "دریافت ابعاد تصویر", + "inputs": { + "image": { + "name": "تصویر" + } + }, + "outputs": { + "0": { + "name": "عرض", + "tooltip": null + }, + "1": { + "name": "ارتفاع", + "tooltip": null + }, + "2": { + "name": "اندازه دسته", + "tooltip": null + } + } + }, + "GetVideoComponents": { + "description": "تمام اجزای ویدیو را استخراج می‌کند: فریم‌ها، صدا و نرخ فریم.", + "display_name": "استخراج اجزای ویدیو", + "inputs": { + "video": { + "name": "ویدیو", + "tooltip": "ویدیویی که اجزا از آن استخراج می‌شوند." + } + }, + "outputs": { + "0": { + "name": "تصاویر", + "tooltip": null + }, + "1": { + "name": "صدا", + "tooltip": null + }, + "2": { + "name": "نرخ فریم", + "tooltip": null + } + } + }, + "GrowMask": { + "display_name": "گسترش ماسک", + "inputs": { + "expand": { + "name": "گسترش" + }, + "mask": { + "name": "ماسک" + }, + "tapered_corners": { + "name": "گوشه‌های نرم" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Hunyuan3Dv2Conditioning": { + "display_name": "Hunyuan3Dv2Conditioning", + "inputs": { + "clip_vision_output": { + "name": "خروجی بینایی clip" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + } + } + }, + "Hunyuan3Dv2ConditioningMultiView": { + "display_name": "Hunyuan3Dv2ConditioningMultiView", + "inputs": { + "back": { + "name": "پشت" + }, + "front": { + "name": "جلو" + }, + "left": { + "name": "چپ" + }, + "right": { + "name": "راست" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + } + } + }, + "HunyuanImageToVideo": { + "display_name": "HunyuanImageToVideo", + "inputs": { + "batch_size": { + "name": "اندازه دسته" + }, + "guidance_type": { + "name": "نوع راهنما" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "positive": { + "name": "مثبت" + }, + "start_image": { + "name": "تصویر شروع" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "latent", + "tooltip": null + } + } + }, + "HunyuanRefinerLatent": { + "display_name": "HunyuanRefinerLatent", + "inputs": { + "latent": { + "name": "latent" + }, + "negative": { + "name": "منفی" + }, + "noise_augmentation": { + "name": "افزایش نویز" + }, + "positive": { + "name": "مثبت" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "HunyuanVideo15ImageToVideo": { + "display_name": "HunyuanVideo15ImageToVideo", + "inputs": { + "batch_size": { + "name": "اندازه دسته" + }, + "clip_vision_output": { + "name": "خروجی clip vision" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + }, + "start_image": { + "name": "تصویر شروع" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "HunyuanVideo15LatentUpscaleWithModel": { + "display_name": "بزرگ‌نمایی Latent ویدیو ۱۵ با مدل", + "inputs": { + "crop": { + "name": "برش" + }, + "height": { + "name": "ارتفاع" + }, + "model": { + "name": "مدل" + }, + "samples": { + "name": "نمونه‌ها" + }, + "upscale_method": { + "name": "روش بزرگ‌نمایی" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "HunyuanVideo15SuperResolution": { + "display_name": "HunyuanVideo15SuperResolution", + "inputs": { + "clip_vision_output": { + "name": "خروجی clip vision" + }, + "latent": { + "name": "latent" + }, + "negative": { + "name": "منفی" + }, + "noise_augmentation": { + "name": "افزایش نویز" + }, + "positive": { + "name": "مثبت" + }, + "start_image": { + "name": "تصویر شروع" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "HyperTile": { + "display_name": "HyperTile", + "inputs": { + "max_depth": { + "name": "حداکثر عمق" + }, + "model": { + "name": "مدل" + }, + "scale_depth": { + "name": "مقیاس عمق" + }, + "swap_size": { + "name": "اندازه تعویض" + }, + "tile_size": { + "name": "اندازه کاشی" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "HypernetworkLoader": { + "display_name": "بارگذاری Hypernetwork", + "inputs": { + "hypernetwork_name": { + "name": "نام hypernetwork" + }, + "model": { + "name": "مدل" + }, + "strength": { + "name": "شدت" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "IdeogramV1": { + "description": "تولید تصویر با استفاده از مدل Ideogram V1.", + "display_name": "Ideogram V1", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "نسبت ابعاد برای تولید تصویر." + }, + "control_after_generate": { + "name": "control after generate" + }, + "magic_prompt_option": { + "name": "magic_prompt_option", + "tooltip": "تعیین اینکه آیا MagicPrompt در تولید استفاده شود یا خیر" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "توضیح مواردی که باید از تصویر حذف شوند" + }, + "num_images": { + "name": "num_images" + }, + "prompt": { + "name": "prompt", + "tooltip": "دستور متنی برای تولید تصویر" + }, + "seed": { + "name": "seed" + }, + "turbo": { + "name": "turbo", + "tooltip": "آیا از حالت توربو استفاده شود (تولید سریع‌تر، احتمالاً با کیفیت پایین‌تر)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "IdeogramV2": { + "description": "تولید تصویر با استفاده از مدل Ideogram V2.", + "display_name": "Ideogram V2", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "نسبت ابعاد برای تولید تصویر. اگر وضوح روی AUTO تنظیم نشده باشد، نادیده گرفته می‌شود." + }, + "control_after_generate": { + "name": "control after generate" + }, + "magic_prompt_option": { + "name": "magic_prompt_option", + "tooltip": "تعیین اینکه آیا MagicPrompt در تولید استفاده شود یا خیر" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "توضیح مواردی که باید از تصویر حذف شوند" + }, + "num_images": { + "name": "num_images" + }, + "prompt": { + "name": "prompt", + "tooltip": "دستور متنی برای تولید تصویر" + }, + "resolution": { + "name": "resolution", + "tooltip": "وضوح تصویر برای تولید. اگر روی AUTO تنظیم نشده باشد، این مقدار نسبت ابعاد را نادیده می‌گیرد." + }, + "seed": { + "name": "seed" + }, + "style_type": { + "name": "style_type", + "tooltip": "نوع سبک برای تولید (فقط V2)" + }, + "turbo": { + "name": "turbo", + "tooltip": "آیا از حالت توربو استفاده شود (تولید سریع‌تر، احتمالاً با کیفیت پایین‌تر)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "IdeogramV3": { + "description": "تولید تصویر با استفاده از مدل Ideogram V3. پشتیبانی از تولید تصویر معمولی بر اساس پرامپت متنی و ویرایش تصویر با ماسک.", + "display_name": "Ideogram V3", + "inputs": { + "aspect_ratio": { + "name": "نسبت ابعاد", + "tooltip": "نسبت ابعاد برای تولید تصویر. اگر وضوح روی خودکار نباشد، نادیده گرفته می‌شود." + }, + "character_image": { + "name": "تصویر کاراکتر", + "tooltip": "تصویری برای استفاده به عنوان مرجع کاراکتر." + }, + "character_mask": { + "name": "ماسک کاراکتر", + "tooltip": "ماسک اختیاری برای تصویر مرجع کاراکتر." + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "image": { + "name": "تصویر", + "tooltip": "تصویر مرجع اختیاری برای ویرایش تصویر." + }, + "magic_prompt_option": { + "name": "گزینه MagicPrompt", + "tooltip": "تعیین می‌کند که آیا MagicPrompt در تولید استفاده شود یا خیر" + }, + "mask": { + "name": "ماسک", + "tooltip": "ماسک اختیاری برای inpainting (ناحیه‌های سفید جایگزین می‌شوند)" + }, + "num_images": { + "name": "تعداد تصاویر" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت برای تولید یا ویرایش تصویر" + }, + "rendering_speed": { + "name": "سرعت رندر", + "tooltip": "کنترل تعادل بین سرعت تولید و کیفیت" + }, + "resolution": { + "name": "وضوح", + "tooltip": "وضوح تصویر تولیدی. اگر روی خودکار نباشد، نسبت ابعاد را نادیده می‌گیرد." + }, + "seed": { + "name": "بذر" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageAddNoise": { + "display_name": "افزودن نویز به تصویر", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "image": { + "name": "تصویر" + }, + "seed": { + "name": "بذر", + "tooltip": "بذر تصادفی برای ایجاد نویز." + }, + "strength": { + "name": "شدت" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageBatch": { + "display_name": "پردازش دسته‌ای تصاویر", + "inputs": { + "image1": { + "name": "تصویر ۱" + }, + "image2": { + "name": "تصویر ۲" + } + } + }, + "ImageBlend": { + "display_name": "ترکیب تصاویر", + "inputs": { + "blend_factor": { + "name": "ضریب ترکیب" + }, + "blend_mode": { + "name": "حالت ترکیب" + }, + "image1": { + "name": "تصویر ۱" + }, + "image2": { + "name": "تصویر ۲" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageBlur": { + "display_name": "تار کردن تصویر", + "inputs": { + "blur_radius": { + "name": "شعاع تاری" + }, + "image": { + "name": "تصویر" + }, + "sigma": { + "name": "سیگما" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageColorToMask": { + "display_name": "تبدیل رنگ به ماسک", + "inputs": { + "color": { + "name": "رنگ" + }, + "image": { + "name": "تصویر" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageCompare": { + "description": "دو تصویر را به صورت کنار هم با اسلایدر مقایسه می‌کند.", + "display_name": "مقایسه تصویر", + "inputs": { + "compare_view": { + "name": "نمای مقایسه" + }, + "image_a": { + "name": "تصویر A" + }, + "image_b": { + "name": "تصویر B" + } + } + }, + "ImageCompositeMasked": { + "display_name": "ترکیب تصویر با ماسک", + "inputs": { + "destination": { + "name": "مقصد" + }, + "mask": { + "name": "ماسک" + }, + "resize_source": { + "name": "تغییر اندازه مبدأ" + }, + "source": { + "name": "مبدأ" + }, + "x": { + "name": "مختصات افقی (x)" + }, + "y": { + "name": "مختصات عمودی (y)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageCrop": { + "display_name": "برش تصویر", + "inputs": { + "height": { + "name": "ارتفاع" + }, + "image": { + "name": "تصویر" + }, + "width": { + "name": "عرض" + }, + "x": { + "name": "مختصات افقی (x)" + }, + "y": { + "name": "مختصات عمودی (y)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageDeduplication": { + "display_name": "حذف تصاویر تکراری", + "inputs": { + "images": { + "name": "تصاویر", + "tooltip": "فهرست تصاویر برای پردازش." + }, + "similarity_threshold": { + "name": "آستانه شباهت", + "tooltip": "آستانه شباهت (۰-۱). مقدار بالاتر به معنای شباهت بیشتر است. تصاویری که بالاتر از این آستانه باشند تکراری در نظر گرفته می‌شوند." + } + }, + "outputs": { + "0": { + "name": "تصاویر", + "tooltip": "تصاویر پردازش‌شده" + } + } + }, + "ImageFlip": { + "display_name": "وارونه‌سازی تصویر", + "inputs": { + "flip_method": { + "name": "روش وارونه‌سازی" + }, + "image": { + "name": "تصویر" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageFromBatch": { + "display_name": "تصویر از دسته", + "inputs": { + "batch_index": { + "name": "اندیس دسته" + }, + "image": { + "name": "تصویر" + }, + "length": { + "name": "تعداد" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageGrid": { + "display_name": "شبکه تصاویر", + "inputs": { + "cell_height": { + "name": "ارتفاع سلول", + "tooltip": "ارتفاع هر سلول در شبکه." + }, + "cell_width": { + "name": "عرض سلول", + "tooltip": "عرض هر سلول در شبکه." + }, + "columns": { + "name": "ستون‌ها", + "tooltip": "تعداد ستون‌ها در شبکه." + }, + "images": { + "name": "تصاویر", + "tooltip": "فهرست تصاویر برای پردازش." + }, + "padding": { + "name": "فاصله", + "tooltip": "فاصله بین تصاویر." + } + }, + "outputs": { + "0": { + "name": "تصاویر", + "tooltip": "تصاویر پردازش‌شده" + } + } + }, + "ImageInvert": { + "display_name": "معکوس‌سازی تصویر", + "inputs": { + "image": { + "name": "تصویر" + } + } + }, + "ImageOnlyCheckpointLoader": { + "display_name": "بارگذاری Checkpoint فقط تصویر (مدل img2vid)", + "inputs": { + "ckpt_name": { + "name": "نام Checkpoint" + } + } + }, + "ImageOnlyCheckpointSave": { + "display_name": "ذخیره Checkpoint فقط تصویر", + "inputs": { + "clip_vision": { + "name": "clip_vision" + }, + "filename_prefix": { + "name": "پیشوند نام فایل" + }, + "model": { + "name": "مدل" + }, + "vae": { + "name": "vae" + } + } + }, + "ImagePadForOutpaint": { + "display_name": "افزودن حاشیه به تصویر برای Outpainting", + "inputs": { + "bottom": { + "name": "پایین" + }, + "feathering": { + "name": "محو کردن لبه‌ها" + }, + "image": { + "name": "تصویر" + }, + "left": { + "name": "چپ" + }, + "right": { + "name": "راست" + }, + "top": { + "name": "بالا" + } + } + }, + "ImageQuantize": { + "display_name": "ImageQuantize", + "inputs": { + "colors": { + "name": "تعداد رنگ‌ها" + }, + "dither": { + "name": "Dither" + }, + "image": { + "name": "تصویر" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageRGBToYUV": { + "display_name": "تبدیل تصویر RGB به YUV", + "inputs": { + "image": { + "name": "تصویر" + } + }, + "outputs": { + "0": { + "name": "Y", + "tooltip": null + }, + "1": { + "name": "U", + "tooltip": null + }, + "2": { + "name": "V", + "tooltip": null + } + } + }, + "ImageRotate": { + "display_name": "چرخش تصویر", + "inputs": { + "image": { + "name": "تصویر" + }, + "rotation": { + "name": "زاویه چرخش" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageScale": { + "display_name": "بزرگ‌نمایی تصویر", + "inputs": { + "crop": { + "name": "برش" + }, + "height": { + "name": "ارتفاع" + }, + "image": { + "name": "تصویر" + }, + "upscale_method": { + "name": "روش بزرگ‌نمایی" + }, + "width": { + "name": "عرض" + } + } + }, + "ImageScaleBy": { + "display_name": "بزرگ‌نمایی تصویر بر اساس ضریب", + "inputs": { + "image": { + "name": "تصویر" + }, + "scale_by": { + "name": "ضریب بزرگ‌نمایی" + }, + "upscale_method": { + "name": "روش بزرگ‌نمایی" + } + } + }, + "ImageScaleToMaxDimension": { + "display_name": "تغییر اندازه تصویر به بیشترین بُعد", + "inputs": { + "image": { + "name": "تصویر" + }, + "largest_size": { + "name": "بیشترین اندازه" + }, + "upscale_method": { + "name": "روش بزرگ‌نمایی" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageScaleToTotalPixels": { + "display_name": "تغییر اندازه تصویر بر اساس تعداد کل پیکسل‌ها", + "inputs": { + "image": { + "name": "تصویر" + }, + "megapixels": { + "name": "مگاپیکسل" + }, + "resolution_steps": { + "name": "گام‌های وضوح" + }, + "upscale_method": { + "name": "روش بزرگ‌نمایی" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageSharpen": { + "display_name": "شارپ‌سازی تصویر", + "inputs": { + "alpha": { + "name": "آلفا" + }, + "image": { + "name": "تصویر" + }, + "sharpen_radius": { + "name": "شعاع شارپ‌سازی" + }, + "sigma": { + "name": "سیگما" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageStitch": { + "description": "تصویر image2 را در جهت مشخص‌شده به image1 متصل می‌کند.\nاگر image2 ارائه نشود، image1 بدون تغییر بازگردانده می‌شود.\nامکان افزودن فاصله اختیاری بین تصاویر وجود دارد.", + "display_name": "اتصال تصاویر", + "inputs": { + "direction": { + "name": "جهت" + }, + "image1": { + "name": "image1" + }, + "image2": { + "name": "image2" + }, + "match_image_size": { + "name": "همسان‌سازی اندازه تصویر" + }, + "spacing_color": { + "name": "رنگ فاصله" + }, + "spacing_width": { + "name": "عرض فاصله" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageToMask": { + "display_name": "تبدیل تصویر به ماسک", + "inputs": { + "channel": { + "name": "کانال" + }, + "image": { + "name": "تصویر" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageUpscaleWithModel": { + "display_name": "بزرگ‌نمایی تصویر (با مدل)", + "inputs": { + "image": { + "name": "تصویر" + }, + "upscale_model": { + "name": "مدل بزرگ‌نمایی" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageYUVToRGB": { + "display_name": "تبدیل YUV تصویر به RGB", + "inputs": { + "U": { + "name": "U" + }, + "V": { + "name": "V" + }, + "Y": { + "name": "Y" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "InpaintModelConditioning": { + "display_name": "شرط‌گذاری مدل Inpaint", + "inputs": { + "mask": { + "name": "ماسک" + }, + "negative": { + "name": "منفی" + }, + "noise_mask": { + "name": "ماسک نویز", + "tooltip": "یک ماسک نویز به latent اضافه می‌کند تا نمونه‌گیری فقط درون ماسک انجام شود. ممکن است نتایج را بهبود دهد یا کاملاً خراب کند، بسته به مدل." + }, + "pixels": { + "name": "پیکسل‌ها" + }, + "positive": { + "name": "مثبت" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "مثبت" + }, + "1": { + "name": "منفی" + }, + "2": { + "name": "latent" + } + } + }, + "InstructPixToPixConditioning": { + "display_name": "شرط‌گذاری InstructPixToPix", + "inputs": { + "negative": { + "name": "منفی" + }, + "pixels": { + "name": "پیکسل‌ها" + }, + "positive": { + "name": "مثبت" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "InvertMask": { + "display_name": "وارونه‌سازی ماسک", + "inputs": { + "mask": { + "name": "ماسک" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "JoinAudioChannels": { + "description": "کانال‌های صوتی مونو چپ و راست را به یک صدای استریو ادغام می‌کند.", + "display_name": "ادغام کانال‌های صوتی", + "inputs": { + "audio_left": { + "name": "صدای چپ" + }, + "audio_right": { + "name": "صدای راست" + } + }, + "outputs": { + "0": { + "name": "صدا", + "tooltip": null + } + } + }, + "JoinImageWithAlpha": { + "display_name": "ترکیب تصویر با Alpha", + "inputs": { + "alpha": { + "name": "آلفا" + }, + "image": { + "name": "تصویر" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KSampler": { + "description": "با استفاده از مدل ارائه‌شده و شرط‌های مثبت و منفی، تصویر نهفته را از نویز پاک‌سازی می‌کند.", + "display_name": "KSampler", + "inputs": { + "cfg": { + "name": "cfg", + "tooltip": "مقیاس Classifier-Free Guidance تعادل بین خلاقیت و پایبندی به دستور را برقرار می‌کند. مقادیر بالاتر باعث می‌شود تصویر بیشتر با دستور مطابقت داشته باشد، اما مقادیر بیش از حد بالا می‌تواند کیفیت را کاهش دهد." + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "denoise": { + "name": "میزان پاک‌سازی نویز", + "tooltip": "مقدار پاک‌سازی نویز اعمال‌شده؛ مقادیر کمتر ساختار تصویر اولیه را حفظ می‌کند و امکان نمونه‌گیری تصویر به تصویر را فراهم می‌سازد." + }, + "latent_image": { + "name": "تصویر نهفته", + "tooltip": "تصویر نهفته‌ای که باید پاک‌سازی نویز شود." + }, + "model": { + "name": "مدل", + "tooltip": "مدلی که برای پاک‌سازی نویز تصویر نهفته ورودی استفاده می‌شود." + }, + "negative": { + "name": "شرط منفی", + "tooltip": "شرطی که ویژگی‌هایی را که نمی‌خواهید در تصویر وجود داشته باشد توصیف می‌کند." + }, + "positive": { + "name": "شرط مثبت", + "tooltip": "شرطی که ویژگی‌هایی را که می‌خواهید در تصویر وجود داشته باشد توصیف می‌کند." + }, + "sampler_name": { + "name": "sampler_name", + "tooltip": "الگوریتمی که هنگام نمونه‌گیری استفاده می‌شود و می‌تواند بر کیفیت، سرعت و سبک خروجی تولیدشده تأثیر بگذارد." + }, + "scheduler": { + "name": "زمان‌بند", + "tooltip": "زمان‌بند کنترل می‌کند که چگونه نویز به تدریج حذف شود تا تصویر شکل بگیرد." + }, + "seed": { + "name": "seed", + "tooltip": "بذر تصادفی که برای ایجاد نویز استفاده می‌شود." + }, + "steps": { + "name": "گام‌ها", + "tooltip": "تعداد گام‌های استفاده‌شده در فرآیند پاک‌سازی نویز." + } + }, + "outputs": { + "0": { + "tooltip": "تصویر نهفته پاک‌سازی‌شده." + } + } + }, + "KSamplerAdvanced": { + "display_name": "KSampler (پیشرفته)", + "inputs": { + "add_noise": { + "name": "افزودن نویز" + }, + "cfg": { + "name": "cfg" + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "end_at_step": { + "name": "پایان در گام" + }, + "latent_image": { + "name": "تصویر نهفته" + }, + "model": { + "name": "مدل" + }, + "negative": { + "name": "شرط منفی" + }, + "noise_seed": { + "name": "بذر نویز" + }, + "positive": { + "name": "شرط مثبت" + }, + "return_with_leftover_noise": { + "name": "بازگشت با نویز باقی‌مانده" + }, + "sampler_name": { + "name": "sampler_name" + }, + "scheduler": { + "name": "زمان‌بند" + }, + "start_at_step": { + "name": "شروع از گام" + }, + "steps": { + "name": "گام‌ها" + } + } + }, + "KSamplerSelect": { + "display_name": "KSamplerSelect", + "inputs": { + "sampler_name": { + "name": "sampler_name" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Kandinsky5ImageToVideo": { + "display_name": "Kandinsky5ImageToVideo", + "inputs": { + "batch_size": { + "name": "batch_size" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + }, + "start_image": { + "name": "تصویر شروع" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": "ویدیوی خالی latent" + }, + "3": { + "name": "cond_latent", + "tooltip": "تصاویر شروع رمزگذاری‌شده و پاک، برای جایگزینی شروع نویزی خروجی مدل latent استفاده می‌شود" + } + } + }, + "KarrasScheduler": { + "display_name": "KarrasScheduler", + "inputs": { + "rho": { + "name": "rho" + }, + "sigma_max": { + "name": "حداکثر سیگما" + }, + "sigma_min": { + "name": "حداقل سیگما" + }, + "steps": { + "name": "گام‌ها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingCameraControlI2VNode": { + "description": "تبدیل تصاویر ثابت به ویدیوهای سینمایی با حرکات حرفه‌ای دوربین که شبیه‌ساز فیلم‌برداری واقعی است. کنترل حرکات مجازی دوربین شامل زوم، چرخش، پن، تیلت و نمای اول شخص، در حالی که تمرکز بر تصویر اصلی شما حفظ می‌شود.", + "display_name": "تبدیل تصویر به ویدیو Kling (کنترل دوربین)", + "inputs": { + "aspect_ratio": { + "name": "نسبت تصویر" + }, + "camera_control": { + "name": "کنترل دوربین", + "tooltip": "می‌تواند با استفاده از node کنترل دوربین Kling ساخته شود. حرکات و حرکت دوربین را در طول تولید ویدیو کنترل می‌کند." + }, + "cfg_scale": { + "name": "cfg_scale" + }, + "negative_prompt": { + "name": "پرامپت منفی", + "tooltip": "پرامپت متنی منفی" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت متنی مثبت" + }, + "start_frame": { + "name": "فریم شروع", + "tooltip": "تصویر مرجع - آدرس URL یا رشته Base64 رمزگذاری‌شده، نباید بیش از ۱۰ مگابایت باشد، وضوح کمتر از ۳۰۰×۳۰۰ پیکسل نباشد، نسبت تصویر بین ۱:۲.۵ تا ۲.۵:۱. Base64 نباید شامل پیشوند data:image باشد." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "شناسه ویدیو", + "tooltip": null + }, + "2": { + "name": "مدت زمان", + "tooltip": null + } + } + }, + "KlingCameraControlT2VNode": { + "description": "تبدیل متن به ویدیوهای سینمایی با حرکات حرفه‌ای دوربین که شبیه‌سازی فیلم‌برداری واقعی را انجام می‌دهد. کنترل حرکات مجازی دوربین شامل زوم، چرخش، پن، تیلت و نمای اول شخص، در حالی که تمرکز بر متن اصلی حفظ می‌شود.", + "display_name": "Kling تبدیل متن به ویدیو (کنترل دوربین)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "camera_control": { + "name": "camera_control", + "tooltip": "می‌تواند با استفاده از node کنترل دوربین Kling ایجاد شود. حرکت و کنترل دوربین در طول تولید ویدیو را مدیریت می‌کند." + }, + "cfg_scale": { + "name": "cfg_scale" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "پرامپت متنی منفی" + }, + "prompt": { + "name": "prompt", + "tooltip": "پرامپت متنی مثبت" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "video_id", + "tooltip": null + }, + "2": { + "name": "duration", + "tooltip": null + } + } + }, + "KlingCameraControls": { + "description": "امکان تعیین گزینه‌های پیکربندی برای کنترل‌های دوربین Kling و افکت‌های کنترل حرکت را فراهم می‌کند.", + "display_name": "کنترل‌های دوربین Kling", + "inputs": { + "camera_control_type": { + "name": "camera_control_type" + }, + "horizontal_movement": { + "name": "horizontal_movement", + "tooltip": "حرکت دوربین در محور افقی (محور x) را کنترل می‌کند. مقدار منفی به معنای حرکت به چپ و مقدار مثبت به معنای حرکت به راست است." + }, + "pan": { + "name": "pan", + "tooltip": "چرخش دوربین در صفحه عمودی (محور x) را کنترل می‌کند. مقدار منفی به معنای چرخش به پایین و مقدار مثبت به معنای چرخش به بالا است." + }, + "roll": { + "name": "roll", + "tooltip": "میزان چرخش دوربین در محور z را کنترل می‌کند. مقدار منفی به معنای چرخش پادساعتگرد و مقدار مثبت به معنای چرخش ساعتگرد است." + }, + "tilt": { + "name": "tilt", + "tooltip": "چرخش دوربین در صفحه افقی (محور y) را کنترل می‌کند. مقدار منفی به معنای چرخش به چپ و مقدار مثبت به معنای چرخش به راست است." + }, + "vertical_movement": { + "name": "vertical_movement", + "tooltip": "حرکت دوربین در محور عمودی (محور y) را کنترل می‌کند. مقدار منفی به معنای حرکت به پایین و مقدار مثبت به معنای حرکت به بالا است." + }, + "zoom": { + "name": "zoom", + "tooltip": "تغییر فاصله کانونی دوربین را کنترل می‌کند. مقدار منفی به معنای زاویه دید باریک‌تر و مقدار مثبت به معنای زاویه دید بازتر است." + } + }, + "outputs": { + "0": { + "name": "camera_control", + "tooltip": null + } + } + }, + "KlingDualCharacterVideoEffectNode": { + "description": "هنگام تولید ویدیو بر اساس effect_scene افکت‌های ویژه مختلفی را اعمال کنید. تصویر اول در سمت چپ و تصویر دوم در سمت راست ترکیب قرار می‌گیرد.", + "display_name": "افکت‌های ویدیویی دو کاراکتر Kling", + "inputs": { + "duration": { + "name": "duration" + }, + "effect_scene": { + "name": "effect_scene" + }, + "image_left": { + "name": "image_left", + "tooltip": "تصویر سمت چپ" + }, + "image_right": { + "name": "image_right", + "tooltip": "تصویر سمت راست" + }, + "mode": { + "name": "mode" + }, + "model_name": { + "name": "model_name" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "duration", + "tooltip": null + } + } + }, + "KlingImage2VideoNode": { + "display_name": "تبدیل تصویر Kling (فریم اول) به ویدیو", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "cfg_scale": { + "name": "cfg_scale" + }, + "duration": { + "name": "duration" + }, + "mode": { + "name": "mode" + }, + "model_name": { + "name": "model_name" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "پرامپت متنی منفی" + }, + "prompt": { + "name": "prompt", + "tooltip": "پرامپت متنی مثبت" + }, + "start_frame": { + "name": "start_frame", + "tooltip": "تصویر مرجع برای تولید ویدیو استفاده می‌شود." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "video_id", + "tooltip": null + }, + "2": { + "name": "duration", + "tooltip": null + } + } + }, + "KlingImageGenerationNode": { + "description": "نود تولید تصویر Kling. تولید تصویر از یک پرامپت متنی با امکان استفاده از تصویر مرجع.", + "display_name": "تولید تصویر Kling", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "human_fidelity": { + "name": "human_fidelity", + "tooltip": "شباهت مرجع سوژه" + }, + "image": { + "name": "image" + }, + "image_fidelity": { + "name": "image_fidelity", + "tooltip": "شدت مرجع برای تصاویر بارگذاری‌شده توسط کاربر" + }, + "image_type": { + "name": "image_type" + }, + "model_name": { + "name": "model_name" + }, + "n": { + "name": "n", + "tooltip": "تعداد تصاویر تولیدشده" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "پرامپت متنی منفی" + }, + "prompt": { + "name": "prompt", + "tooltip": "پرامپت متنی مثبت" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingImageToVideoWithAudio": { + "display_name": "تبدیل تصویر Kling (فریم اول) به ویدیو همراه با صدا", + "inputs": { + "duration": { + "name": "duration" + }, + "generate_audio": { + "name": "generate_audio" + }, + "mode": { + "name": "mode" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "پرامپت متنی مثبت." + }, + "start_frame": { + "name": "start_frame" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingLipSyncAudioToVideoNode": { + "description": "نود همگام‌سازی لب Kling. حرکات دهان در یک فایل ویدیویی را با محتوای صوتی یک فایل صوتی هماهنگ می‌کند. هنگام استفاده، اطمینان حاصل کنید که صدا دارای وکال واضح و ویدیو دارای چهره مشخص باشد. حجم فایل صوتی نباید بیشتر از ۵ مگابایت باشد. حجم فایل ویدیویی نباید بیشتر از ۱۰۰ مگابایت باشد، ارتفاع/عرض بین ۷۲۰ تا ۱۹۲۰ پیکسل و مدت زمان بین ۲ تا ۱۰ ثانیه باشد.", + "display_name": "همگام‌سازی لب Kling ویدیو با صدا", + "inputs": { + "audio": { + "name": "audio" + }, + "video": { + "name": "video" + }, + "voice_language": { + "name": "voice_language" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "video_id", + "tooltip": null + }, + "2": { + "name": "duration", + "tooltip": null + } + } + }, + "KlingLipSyncTextToVideoNode": { + "description": "گره همگام‌سازی لب Kling از متن به ویدیو. حرکات دهان در یک فایل ویدیویی را با یک متن همگام می‌کند. حجم فایل ویدیو نباید بیش از ۱۰۰ مگابایت باشد، ارتفاع/عرض باید بین ۷۲۰ تا ۱۹۲۰ پیکسل باشد و مدت زمان آن باید بین ۲ تا ۱۰ ثانیه باشد.", + "display_name": "همگام‌سازی لب Kling با ویدیو و متن", + "inputs": { + "text": { + "name": "متن", + "tooltip": "محتوای متنی برای تولید ویدیوی همگام‌سازی لب. هنگام انتخاب حالت text2video الزامی است. حداکثر طول ۱۲۰ کاراکتر." + }, + "video": { + "name": "ویدیو" + }, + "voice": { + "name": "صدا" + }, + "voice_speed": { + "name": "سرعت صدا", + "tooltip": "سرعت گفتار. بازه معتبر: ۰.۸ تا ۲.۰، با دقت تا یک رقم اعشار." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "شناسه ویدیو", + "tooltip": null + }, + "2": { + "name": "مدت زمان", + "tooltip": null + } + } + }, + "KlingMotionControl": { + "display_name": "کنترل حرکت Kling", + "inputs": { + "character_orientation": { + "name": "جهت‌گیری شخصیت", + "tooltip": "تعیین می‌کند جهت/زاویه شخصیت از کجا گرفته شود.\nvideo: حرکات، حالات چهره، حرکات دوربین و جهت‌گیری مطابق ویدیوی مرجع حرکت (سایر جزئیات از طریق پرامپت).\nimage: حرکات و حالات چهره همچنان از ویدیوی مرجع حرکت پیروی می‌کنند، اما جهت‌گیری شخصیت مطابق تصویر مرجع است (دوربین/سایر جزئیات از طریق پرامپت)." + }, + "keep_original_sound": { + "name": "حفظ صدای اصلی" + }, + "mode": { + "name": "حالت" + }, + "prompt": { + "name": "پرامپت" + }, + "reference_image": { + "name": "تصویر مرجع" + }, + "reference_video": { + "name": "ویدیوی مرجع", + "tooltip": "ویدیوی مرجع حرکت برای هدایت حرکات/بیان چهره.\nمحدودیت مدت زمان بسته به character_orientation:\n - تصویر: ۳ تا ۱۰ ثانیه (حداکثر ۱۰ ثانیه)\n - ویدیو: ۳ تا ۳۰ ثانیه (حداکثر ۳۰ ثانیه)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProEditVideoNode": { + "description": "ویرایش یک ویدیوی موجود با جدیدترین مدل Kling.", + "display_name": "ویرایش ویدیوی Omni Kling (حرفه‌ای)", + "inputs": { + "keep_original_sound": { + "name": "حفظ صدای اصلی" + }, + "model_name": { + "name": "نام مدل" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "یک پرامپت متنی برای توصیف محتوای ویدیو. می‌تواند شامل توصیفات مثبت و منفی باشد." + }, + "reference_images": { + "name": "تصاویر مرجع", + "tooltip": "حداکثر ۴ تصویر مرجع اضافی." + }, + "resolution": { + "name": "وضوح" + }, + "video": { + "name": "ویدیو", + "tooltip": "ویدیو برای ویرایش. طول ویدیوی خروجی همانند ورودی خواهد بود." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProFirstLastFrameNode": { + "description": "استفاده از یک فریم شروع، یک فریم پایانی اختیاری یا تصاویر مرجع با جدیدترین مدل Kling.", + "display_name": "Kling Omni تبدیل اولین-آخرین فریم به ویدیو (Pro)", + "inputs": { + "duration": { + "name": "duration" + }, + "end_frame": { + "name": "end_frame", + "tooltip": "یک فریم پایانی اختیاری برای ویدیو. این گزینه نمی‌تواند همزمان با 'reference_images' استفاده شود." + }, + "first_frame": { + "name": "first_frame" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "یک پرامپت متنی برای توصیف محتوای ویدیو. این می‌تواند شامل توصیفات مثبت و منفی باشد." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "حداکثر ۶ تصویر مرجع اضافی." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageNode": { + "description": "ایجاد یا ویرایش تصاویر با جدیدترین مدل Kling.", + "display_name": "Kling Omni تصویر (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "یک پرامپت متنی برای توصیف محتوای تصویر. این می‌تواند شامل توصیفات مثبت و منفی باشد." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "حداکثر ۱۰ تصویر مرجع اضافی." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageToVideoNode": { + "description": "استفاده از حداکثر ۷ تصویر مرجع برای تولید ویدیو با جدیدترین مدل Kling.", + "display_name": "Kling Omni تبدیل تصویر به ویدیو (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "یک پرامپت متنی برای توصیف محتوای ویدیو. این می‌تواند شامل توصیفات مثبت و منفی باشد." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "حداکثر ۷ تصویر مرجع." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProTextToVideoNode": { + "description": "استفاده از پرامپت‌های متنی برای تولید ویدیو با جدیدترین مدل Kling.", + "display_name": "Kling Omni تبدیل متن به ویدیو (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "یک پرامپت متنی برای توصیف محتوای ویدیو. این می‌تواند شامل توصیفات مثبت و منفی باشد." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProVideoToVideoNode": { + "description": "با استفاده از یک ویدیو و حداکثر ۴ تصویر مرجع، یک ویدیو با مدل جدید Kling تولید کنید.", + "display_name": "Kling Omni Video به Video (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "keep_original_sound": { + "name": "keep_original_sound" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "یک پرامپت متنی برای توصیف محتوای ویدیو. این می‌تواند شامل توصیفات مثبت و منفی باشد." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "حداکثر ۴ تصویر مرجع اضافی." + }, + "reference_video": { + "name": "reference_video", + "tooltip": "ویدیوی مرجع برای استفاده." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingSingleImageVideoEffectNode": { + "description": "هنگام تولید ویدیو بر اساس effect_scene، افکت‌های ویژه مختلفی را اعمال کنید.", + "display_name": "افکت‌های ویدیویی Kling", + "inputs": { + "duration": { + "name": "duration" + }, + "effect_scene": { + "name": "effect_scene" + }, + "image": { + "name": "image", + "tooltip": "تصویر مرجع. آدرس URL یا رشته Base64 (بدون پیشوند data:image). حجم فایل نباید از ۱۰ مگابایت بیشتر باشد، وضوح کمتر از ۳۰۰×۳۰۰ پیکسل نباشد، نسبت تصویر بین ۱:۲.۵ تا ۲.۵:۱ باشد." + }, + "model_name": { + "name": "model_name" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "video_id", + "tooltip": null + }, + "2": { + "name": "duration", + "tooltip": null + } + } + }, + "KlingStartEndFrameNode": { + "description": "یک توالی ویدیویی تولید کنید که بین تصاویر شروع و پایان ارائه‌شده شما انتقال ایجاد می‌کند. این نود تمام فریم‌های میانی را ایجاد کرده و یک تبدیل نرم از اولین فریم به آخرین فریم ارائه می‌دهد.", + "display_name": "Kling شروع-پایان فریم به ویدیو", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "cfg_scale": { + "name": "cfg_scale" + }, + "end_frame": { + "name": "end_frame", + "tooltip": "تصویر مرجع - کنترل فریم پایانی. آدرس URL یا رشته Base64، نباید از ۱۰ مگابایت بیشتر باشد، وضوح کمتر از ۳۰۰×۳۰۰ پیکسل نباشد. Base64 نباید شامل پیشوند data:image باشد." + }, + "mode": { + "name": "mode", + "tooltip": "پیکربندی مورد استفاده برای تولید ویدیو با فرمت: mode / duration / model_name." + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "پرامپت متنی منفی" + }, + "prompt": { + "name": "prompt", + "tooltip": "پرامپت متنی مثبت" + }, + "start_frame": { + "name": "start_frame", + "tooltip": "تصویر مرجع - آدرس URL یا رشته Base64، نباید از ۱۰ مگابایت بیشتر باشد، وضوح کمتر از ۳۰۰×۳۰۰ پیکسل نباشد، نسبت تصویر بین ۱:۲.۵ تا ۲.۵:۱ باشد. Base64 نباید شامل پیشوند data:image باشد." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "video_id", + "tooltip": null + }, + "2": { + "name": "duration", + "tooltip": null + } + } + }, + "KlingTextToVideoNode": { + "description": "گره Kling تبدیل متن به ویدیو", + "display_name": "Kling تبدیل متن به ویدیو", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "cfg_scale": { + "name": "cfg_scale" + }, + "mode": { + "name": "mode", + "tooltip": "پیکربندی مورد استفاده برای تولید ویدیو به فرمت: mode / duration / model_name." + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "پرامپت متنی منفی" + }, + "prompt": { + "name": "prompt", + "tooltip": "پرامپت متنی مثبت" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "video_id", + "tooltip": null + }, + "2": { + "name": "duration", + "tooltip": null + } + } + }, + "KlingTextToVideoWithAudio": { + "display_name": "Kling تبدیل متن به ویدیو با صدا", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "generate_audio": { + "name": "generate_audio" + }, + "mode": { + "name": "mode" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "پرامپت متنی مثبت." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingVideoExtendNode": { + "description": "گره Kling گسترش ویدیو. ویدیوهای ساخته‌شده توسط سایر گره‌های Kling را گسترش دهید. video_id توسط سایر گره‌های Kling ایجاد می‌شود.", + "display_name": "Kling گسترش ویدیو", + "inputs": { + "cfg_scale": { + "name": "cfg_scale" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "پرامپت متنی منفی برای اجتناب از عناصر ناخواسته در ویدیوی گسترش‌یافته" + }, + "prompt": { + "name": "prompt", + "tooltip": "پرامپت متنی مثبت برای راهنمایی گسترش ویدیو" + }, + "video_id": { + "name": "video_id", + "tooltip": "شناسه ویدیویی که باید گسترش یابد. از ویدیوهای تولیدشده توسط تبدیل متن به ویدیو، تصویر به ویدیو و عملیات گسترش ویدیوی قبلی پشتیبانی می‌کند. پس از گسترش، مدت زمان کل نباید از ۳ دقیقه بیشتر شود." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "video_id", + "tooltip": null + }, + "2": { + "name": "duration", + "tooltip": null + } + } + }, + "KlingVirtualTryOnNode": { + "description": "گره Kling پرو لباس مجازی. یک تصویر انسان و یک تصویر لباس وارد کنید تا لباس را روی انسان امتحان کنید. می‌توانید چندین تصویر لباس را در یک تصویر با پس‌زمینه سفید ادغام کنید.", + "display_name": "Kling پرو لباس مجازی", + "inputs": { + "cloth_image": { + "name": "cloth_image" + }, + "human_image": { + "name": "human_image" + }, + "model_name": { + "name": "model_name" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LTXAVTextEncoderLoader": { + "description": "[دستورالعمل‌ها]\n\nltxav: gemma ۳ ۱۲B", + "display_name": "بارگذاری رمزگذار متنی LTXV Audio", + "inputs": { + "ckpt_name": { + "name": "نام checkpoint" + }, + "device": { + "name": "دستگاه" + }, + "text_encoder": { + "name": "رمزگذار متنی" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LTXVAddGuide": { + "display_name": "LTXVAddGuide", + "inputs": { + "frame_idx": { + "name": "اندیس فریم", + "tooltip": "اندیس فریم برای شروع شرط‌گذاری. برای تصاویر تک‌فریم یا ویدیوهای ۱ تا ۸ فریم، هر مقدار frame_idx قابل قبول است. برای ویدیوهای ۹ فریم به بالا، frame_idx باید بر ۸ بخش‌پذیر باشد، در غیر این صورت به نزدیک‌ترین مضرب ۸ گرد می‌شود. مقادیر منفی از انتهای ویدیو شمرده می‌شوند." + }, + "image": { + "name": "تصویر", + "tooltip": "تصویر یا ویدیو برای شرط‌گذاری latent ویدیو. باید شامل ۸*n+۱ فریم باشد. اگر ویدیو این تعداد فریم نداشته باشد، به نزدیک‌ترین ۸*n+۱ فریم برش داده می‌شود." + }, + "latent": { + "name": "latent" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + }, + "strength": { + "name": "شدت" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "LTXVAudioVAEDecode": { + "display_name": "LTXV رمزگشایی Audio VAE", + "inputs": { + "audio_vae": { + "name": "Audio VAE", + "tooltip": "مدل Audio VAE مورد استفاده برای رمزگشایی لاتنت." + }, + "samples": { + "name": "نمونه‌ها", + "tooltip": "لاتنت برای رمزگشایی." + } + }, + "outputs": { + "0": { + "name": "صدا", + "tooltip": null + } + } + }, + "LTXVAudioVAEEncode": { + "display_name": "LTXV کدگذاری Audio VAE", + "inputs": { + "audio": { + "name": "صدا", + "tooltip": "صدایی که باید کدگذاری شود." + }, + "audio_vae": { + "name": "Audio VAE", + "tooltip": "مدل Audio VAE برای کدگذاری." + } + }, + "outputs": { + "0": { + "name": "لاتنت صدا", + "tooltip": null + } + } + }, + "LTXVAudioVAELoader": { + "display_name": "LTXV بارگذاری Audio VAE", + "inputs": { + "ckpt_name": { + "name": "نام چک‌پوینت", + "tooltip": "چک‌پوینت Audio VAE برای بارگذاری." + } + }, + "outputs": { + "0": { + "name": "Audio VAE", + "tooltip": null + } + } + }, + "LTXVConcatAVLatent": { + "display_name": "LTXVConcatAVLatent", + "inputs": { + "audio_latent": { + "name": "لاتنت صدا" + }, + "video_latent": { + "name": "لاتنت ویدیو" + } + }, + "outputs": { + "0": { + "name": "لاتنت", + "tooltip": null + } + } + }, + "LTXVConditioning": { + "display_name": "LTXVConditioning", + "inputs": { + "frame_rate": { + "name": "نرخ فریم" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + } + } + }, + "LTXVCropGuides": { + "display_name": "LTXVCropGuides", + "inputs": { + "latent": { + "name": "لاتنت" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "لاتنت", + "tooltip": null + } + } + }, + "LTXVEmptyLatentAudio": { + "display_name": "LTXV Empty Latent Audio", + "inputs": { + "audio_vae": { + "name": "audio_vae", + "tooltip": "مدل Audio VAE برای دریافت پیکربندی." + }, + "batch_size": { + "name": "batch_size", + "tooltip": "تعداد نمونه‌های latent audio در هر دسته." + }, + "frame_rate": { + "name": "frame_rate", + "tooltip": "تعداد فریم در هر ثانیه." + }, + "frames_number": { + "name": "frames_number", + "tooltip": "تعداد فریم‌ها." + } + }, + "outputs": { + "0": { + "name": "Latent", + "tooltip": null + } + } + }, + "LTXVImgToVideo": { + "display_name": "LTXVImgToVideo", + "inputs": { + "batch_size": { + "name": "batch_size" + }, + "height": { + "name": "height" + }, + "image": { + "name": "image" + }, + "length": { + "name": "length" + }, + "negative": { + "name": "negative" + }, + "positive": { + "name": "positive" + }, + "strength": { + "name": "strength" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "width" + } + }, + "outputs": { + "0": { + "name": "positive", + "tooltip": null + }, + "1": { + "name": "negative", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "LTXVImgToVideoInplace": { + "display_name": "LTXVImgToVideoInplace", + "inputs": { + "bypass": { + "name": "bypass", + "tooltip": "عبور از شرط‌گذاری." + }, + "image": { + "name": "image" + }, + "latent": { + "name": "latent" + }, + "strength": { + "name": "strength" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, + "LTXVLatentUpsampler": { + "display_name": "LTXVLatentUpsampler", + "inputs": { + "samples": { + "name": "samples" + }, + "upscale_model": { + "name": "upscale_model" + }, + "vae": { + "name": "vae" + } + } + }, + "LTXVPreprocess": { + "display_name": "LTXVPreprocess", + "inputs": { + "image": { + "name": "image" + }, + "img_compression": { + "name": "img_compression", + "tooltip": "میزان فشرده‌سازی اعمال‌شده روی تصویر." + } + }, + "outputs": { + "0": { + "name": "output_image", + "tooltip": null + } + } + }, + "LTXVScheduler": { + "display_name": "LTXVScheduler", + "inputs": { + "base_shift": { + "name": "base_shift" + }, + "latent": { + "name": "latent" + }, + "max_shift": { + "name": "max_shift" + }, + "steps": { + "name": "steps" + }, + "stretch": { + "name": "stretch", + "tooltip": "کشیدن سیگماها به بازه [terminal, ۱]." + }, + "terminal": { + "name": "terminal", + "tooltip": "مقدار نهایی سیگماها پس از کشیدن." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LTXVSeparateAVLatent": { + "description": "جداکننده AV Latent در LTXV", + "display_name": "LTXVSeparateAVLatent", + "inputs": { + "av_latent": { + "name": "av_latent" + } + }, + "outputs": { + "0": { + "name": "video_latent", + "tooltip": null + }, + "1": { + "name": "audio_latent", + "tooltip": null + } + } + }, + "LaplaceScheduler": { + "display_name": "LaplaceScheduler", + "inputs": { + "beta": { + "name": "بتا" + }, + "mu": { + "name": "مو" + }, + "sigma_max": { + "name": "سیگما بیشینه" + }, + "sigma_min": { + "name": "سیگما کمینه" + }, + "steps": { + "name": "گام‌ها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentAdd": { + "display_name": "LatentAdd", + "inputs": { + "samples1": { + "name": "نمونه ۱" + }, + "samples2": { + "name": "نمونه ۲" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentApplyOperation": { + "display_name": "LatentApplyOperation", + "inputs": { + "operation": { + "name": "عملیات" + }, + "samples": { + "name": "نمونه‌ها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentApplyOperationCFG": { + "display_name": "LatentApplyOperationCFG", + "inputs": { + "model": { + "name": "مدل" + }, + "operation": { + "name": "عملیات" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentBatch": { + "display_name": "LatentBatch", + "inputs": { + "samples1": { + "name": "نمونه‌ها۱" + }, + "samples2": { + "name": "نمونه‌ها۲" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentBatchSeedBehavior": { + "display_name": "LatentBatchSeedBehavior", + "inputs": { + "samples": { + "name": "نمونه‌ها" + }, + "seed_behavior": { + "name": "رفتار seed" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentBlend": { + "display_name": "Latent Blend", + "inputs": { + "blend_factor": { + "name": "ضریب ترکیب" + }, + "samples1": { + "name": "نمونه‌ها۱" + }, + "samples2": { + "name": "نمونه‌ها۲" + } + } + }, + "LatentComposite": { + "display_name": "Latent Composite", + "inputs": { + "feather": { + "name": "پرکنندگی" + }, + "samples_from": { + "name": "نمونه‌های مبدا" + }, + "samples_to": { + "name": "نمونه‌های مقصد" + }, + "x": { + "name": "x" + }, + "y": { + "name": "y" + } + } + }, + "LatentCompositeMasked": { + "display_name": "LatentCompositeMasked", + "inputs": { + "destination": { + "name": "مقصد" + }, + "mask": { + "name": "ماسک" + }, + "resize_source": { + "name": "تغییر اندازه مبدا" + }, + "source": { + "name": "مبدا" + }, + "x": { + "name": "x" + }, + "y": { + "name": "y" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentConcat": { + "display_name": "LatentConcat", + "inputs": { + "dim": { + "name": "بُعد" + }, + "samples1": { + "name": "نمونه‌ها۱" + }, + "samples2": { + "name": "نمونه‌ها۲" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentCrop": { + "display_name": "Crop Latent", + "inputs": { + "height": { + "name": "ارتفاع" + }, + "samples": { + "name": "نمونه‌ها" + }, + "width": { + "name": "عرض" + }, + "x": { + "name": "x" + }, + "y": { + "name": "y" + } + } + }, + "LatentCut": { + "display_name": "LatentCut", + "inputs": { + "amount": { + "name": "مقدار" + }, + "dim": { + "name": "بُعد" + }, + "index": { + "name": "ایندکس" + }, + "samples": { + "name": "نمونه‌ها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentCutToBatch": { + "display_name": "برش Latent به دسته", + "inputs": { + "dim": { + "name": "بُعد" + }, + "samples": { + "name": "نمونه‌ها" + }, + "slice_size": { + "name": "اندازه برش" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentFlip": { + "display_name": "وارونه‌سازی Latent", + "inputs": { + "flip_method": { + "name": "روش وارونه‌سازی" + }, + "samples": { + "name": "نمونه‌ها" + } + } + }, + "LatentFromBatch": { + "display_name": "Latent از دسته", + "inputs": { + "batch_index": { + "name": "شاخص دسته" + }, + "length": { + "name": "طول" + }, + "samples": { + "name": "نمونه‌ها" + } + } + }, + "LatentInterpolate": { + "display_name": "درون‌یابی Latent", + "inputs": { + "ratio": { + "name": "نسبت" + }, + "samples1": { + "name": "نمونه‌ها ۱" + }, + "samples2": { + "name": "نمونه‌ها ۲" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentMultiply": { + "display_name": "ضرب Latent", + "inputs": { + "multiplier": { + "name": "ضریب" + }, + "samples": { + "name": "نمونه‌ها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentOperationSharpen": { + "display_name": "تیز کردن Latent", + "inputs": { + "alpha": { + "name": "آلفا" + }, + "sharpen_radius": { + "name": "شعاع تیز کردن" + }, + "sigma": { + "name": "سیگما" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentOperationTonemapReinhard": { + "display_name": "تنظیم تون Reinhard برای Latent", + "inputs": { + "multiplier": { + "name": "ضریب" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentRotate": { + "display_name": "چرخش Latent", + "inputs": { + "rotation": { + "name": "زاویه چرخش" + }, + "samples": { + "name": "نمونه‌ها" + } + } + }, + "LatentSubtract": { + "display_name": "تفریق Latent", + "inputs": { + "samples1": { + "name": "نمونه‌ها ۱" + }, + "samples2": { + "name": "نمونه‌ها ۲" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentUpscale": { + "display_name": "بزرگ‌نمایی Latent", + "inputs": { + "crop": { + "name": "برش" + }, + "height": { + "name": "ارتفاع" + }, + "samples": { + "name": "نمونه‌ها" + }, + "upscale_method": { + "name": "روش بزرگ‌نمایی" + }, + "width": { + "name": "عرض" + } + } + }, + "LatentUpscaleBy": { + "display_name": "بزرگ‌نمایی Latent بر اساس", + "inputs": { + "samples": { + "name": "نمونه‌ها" + }, + "scale_by": { + "name": "مقیاس" + }, + "upscale_method": { + "name": "روش بزرگ‌نمایی" + } + } + }, + "LatentUpscaleModelLoader": { + "display_name": "بارگذاری مدل بزرگ‌نمایی Latent", + "inputs": { + "model_name": { + "name": "نام مدل" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LazyCache": { + "description": "نسخه خانگی EasyCache - نسخه‌ای حتی «ساده‌تر» از EasyCache برای پیاده‌سازی. به طور کلی عملکرد ضعیف‌تری نسبت به EasyCache دارد، اما در برخی موارد نادر بهتر عمل می‌کند و با همه چیز در ComfyUI سازگاری کامل دارد.", + "display_name": "LazyCache", + "inputs": { + "end_percent": { + "name": "درصد پایان", + "tooltip": "مرحله نمونه‌گیری نسبی برای پایان استفاده از LazyCache." + }, + "model": { + "name": "مدل", + "tooltip": "مدلی که قرار است LazyCache به آن افزوده شود." + }, + "reuse_threshold": { + "name": "آستانه استفاده مجدد", + "tooltip": "آستانه‌ای برای استفاده مجدد از مراحل کش شده." + }, + "start_percent": { + "name": "درصد شروع", + "tooltip": "مرحله نمونه‌گیری نسبی برای شروع استفاده از LazyCache." + }, + "verbose": { + "name": "گزارش‌گیری کامل", + "tooltip": "آیا اطلاعات کامل ثبت شود یا خیر." + } + }, + "outputs": { + "0": { + "tooltip": "مدل با LazyCache." + } + } + }, + "Load3D": { + "display_name": "بارگذاری ۳بعدی و انیمیشن", + "inputs": { + "clear": { + }, + "height": { + "name": "ارتفاع" + }, + "image": { + "name": "تصویر" + }, + "model_file": { + "name": "فایل مدل" + }, + "upload 3d model": { + }, + "upload extra resources": { + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "تصویر", + "tooltip": null + }, + "1": { + "name": "ماسک", + "tooltip": null + }, + "2": { + "name": "مسیر مش", + "tooltip": null + }, + "3": { + "name": "نرمال", + "tooltip": null + }, + "4": { + "name": "اطلاعات دوربین", + "tooltip": null + }, + "5": { + "name": "ویدئوی ضبط‌شده", + "tooltip": null + } + } + }, + "LoadAudio": { + "display_name": "بارگذاری صوت", + "inputs": { + "audio": { + "name": "صوت" + }, + "audioUI": { + "name": "رابط صوتی" + }, + "upload": { + "name": "انتخاب فایل برای بارگذاری" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LoadImage": { + "display_name": "بارگذاری تصویر", + "inputs": { + "image": { + "name": "تصویر" + }, + "upload": { + "name": "انتخاب فایل برای بارگذاری" + } + } + }, + "LoadImageDataSetFromFolder": { + "display_name": "بارگذاری مجموعه تصاویر از پوشه", + "inputs": { + "folder": { + "name": "پوشه", + "tooltip": "پوشه‌ای که تصاویر از آن بارگذاری می‌شوند." + } + }, + "outputs": { + "0": { + "name": "تصاویر", + "tooltip": "فهرست تصاویر بارگذاری‌شده" + } + } + }, + "LoadImageMask": { + "display_name": "بارگذاری تصویر (به عنوان ماسک)", + "inputs": { + "channel": { + "name": "کانال" + }, + "image": { + "name": "تصویر" + }, + "upload": { + "name": "انتخاب فایل برای بارگذاری" + } + } + }, + "LoadImageOutput": { + "description": "بارگذاری یک تصویر از پوشه خروجی. با کلیک روی دکمه تازه‌سازی، فهرست تصاویر به‌روزرسانی شده و اولین تصویر به طور خودکار انتخاب می‌شود تا تکرار آسان‌تر شود.", + "display_name": "بارگذاری تصویر (از خروجی‌ها)", + "inputs": { + "Auto-refresh after generation": { + }, + "image": { + "name": "تصویر" + }, + "refresh": { + }, + "upload": { + "name": "انتخاب فایل برای بارگذاری" + } + } + }, + "LoadImageTextDataSetFromFolder": { + "display_name": "بارگذاری مجموعه داده تصویر و متن از پوشه", + "inputs": { + "folder": { + "name": "پوشه", + "tooltip": "پوشه‌ای که تصاویر از آن بارگذاری می‌شوند." + } + }, + "outputs": { + "0": { + "name": "تصاویر", + "tooltip": "فهرست تصاویر بارگذاری‌شده" + }, + "1": { + "name": "متون", + "tooltip": "فهرست کپشن‌های متنی" + } + } + }, + "LoadLatent": { + "display_name": "LoadLatent", + "inputs": { + "latent": { + "name": "latent" + } + } + }, + "LoadTrainingDataset": { + "display_name": "بارگذاری مجموعه داده آموزشی", + "inputs": { + "folder_name": { + "name": "نام پوشه", + "tooltip": "نام پوشه‌ای که مجموعه داده ذخیره‌شده را شامل می‌شود (درون پوشه خروجی)." + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "فهرست دیکشنری‌های latent" + }, + "1": { + "name": "شرایط‌دهی", + "tooltip": "فهرست لیست‌های شرایط‌دهی" + } + } + }, + "LoadVideo": { + "display_name": "بارگذاری ویدیو", + "inputs": { + "file": { + "name": "فایل" + }, + "upload": { + "name": "انتخاب فایل برای بارگذاری" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LoraLoader": { + "description": "LoRAها برای تغییر مدل‌های diffusion و CLIP استفاده می‌شوند و نحوه حذف نویز از latents را تغییر می‌دهند، مانند اعمال سبک‌ها. چندین node LoRA می‌توانند به هم متصل شوند.", + "display_name": "بارگذاری LoRA", + "inputs": { + "clip": { + "name": "clip", + "tooltip": "مدل CLIP که LoRA روی آن اعمال می‌شود." + }, + "lora_name": { + "name": "lora_name", + "tooltip": "نام LoRA." + }, + "model": { + "name": "مدل", + "tooltip": "مدل diffusion که LoRA روی آن اعمال می‌شود." + }, + "strength_clip": { + "name": "قدرت_clip", + "tooltip": "میزان تغییر مدل CLIP. این مقدار می‌تواند منفی باشد." + }, + "strength_model": { + "name": "قدرت_مدل", + "tooltip": "میزان تغییر مدل diffusion. این مقدار می‌تواند منفی باشد." + } + }, + "outputs": { + "0": { + "tooltip": "مدل diffusion تغییر یافته." + }, + "1": { + "tooltip": "مدل CLIP تغییر یافته." + } + } + }, + "LoraLoaderModelOnly": { + "description": "LoRAها برای تغییر مدل‌های diffusion و CLIP استفاده می‌شوند و نحوه حذف نویز از latents را تغییر می‌دهند، مانند اعمال سبک‌ها. چندین node LoRA می‌توانند به هم متصل شوند.", + "display_name": "LoraLoaderModelOnly", + "inputs": { + "lora_name": { + "name": "lora_name" + }, + "model": { + "name": "مدل" + }, + "strength_model": { + "name": "قدرت_مدل" + } + }, + "outputs": { + "0": { + "tooltip": "مدل diffusion تغییر یافته." + } + } + }, + "LoraModelLoader": { + "display_name": "بارگذاری مدل LoRA", + "inputs": { + "lora": { + "name": "lora", + "tooltip": "مدل LoRA که باید روی مدل diffusion اعمال شود." + }, + "model": { + "name": "مدل", + "tooltip": "مدل diffusion که LoRA روی آن اعمال می‌شود." + }, + "strength_model": { + "name": "قدرت_مدل", + "tooltip": "میزان تغییر مدل diffusion. این مقدار می‌تواند منفی باشد." + } + }, + "outputs": { + "0": { + "name": "مدل", + "tooltip": "مدل diffusion تغییر یافته." + } + } + }, + "LoraSave": { + "display_name": "استخراج و ذخیره Lora", + "inputs": { + "bias_diff": { + "name": "تفاوت بایاس" + }, + "filename_prefix": { + "name": "پیشوند نام فایل" + }, + "lora_type": { + "name": "نوع lora" + }, + "model_diff": { + "name": "تفاوت مدل", + "tooltip": "خروجی ModelSubtract که باید به lora تبدیل شود." + }, + "rank": { + "name": "رتبه" + }, + "text_encoder_diff": { + "name": "تفاوت رمزگذار متنی", + "tooltip": "خروجی CLIPSubtract که باید به lora تبدیل شود." + } + } + }, + "LossGraphNode": { + "display_name": "نمایش نمودار Loss", + "inputs": { + "filename_prefix": { + "name": "پیشوند نام فایل", + "tooltip": "پیشوند برای تصویر نمودار loss ذخیره‌شده." + }, + "loss": { + "name": "مقدار loss", + "tooltip": "نقشه loss از node آموزش." + } + } + }, + "LotusConditioning": { + "display_name": "LotusConditioning", + "outputs": { + "0": { + "name": "شرط‌گذاری", + "tooltip": null + } + } + }, + "LtxvApiImageToVideo": { + "description": "ویدیوهای حرفه‌ای با مدت و وضوح قابل تنظیم بر اساس تصویر شروع.", + "display_name": "تبدیل تصویر به ویدیو LTXV", + "inputs": { + "duration": { + "name": "مدت" + }, + "fps": { + "name": "فریم بر ثانیه" + }, + "generate_audio": { + "name": "تولید صدا", + "tooltip": "در صورت فعال بودن، ویدیوی تولیدشده شامل صدای تولیدشده توسط هوش مصنوعی متناسب با صحنه خواهد بود." + }, + "image": { + "name": "تصویر", + "tooltip": "اولین فریم مورد استفاده برای ویدیو." + }, + "model": { + "name": "مدل" + }, + "prompt": { + "name": "پرامپت" + }, + "resolution": { + "name": "وضوح" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LtxvApiTextToVideo": { + "description": "ویدیوهای حرفه‌ای با مدت زمان و وضوح قابل تنظیم.", + "display_name": "LTXV تبدیل متن به ویدیو", + "inputs": { + "duration": { + "name": "مدت زمان" + }, + "fps": { + "name": "فریم بر ثانیه" + }, + "generate_audio": { + "name": "تولید صدا", + "tooltip": "در صورت فعال بودن، ویدیوی تولید شده شامل صدای تولید شده توسط هوش مصنوعی متناسب با صحنه خواهد بود." + }, + "model": { + "name": "مدل" + }, + "prompt": { + "name": "پرامپت" + }, + "resolution": { + "name": "وضوح" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LumaConceptsNode": { + "description": "مفاهیم دوربین برای استفاده با گره‌های Luma Text to Video و Luma Image to Video.", + "display_name": "مفاهیم Luma", + "inputs": { + "concept1": { + "name": "concept1" + }, + "concept2": { + "name": "concept2" + }, + "concept3": { + "name": "concept3" + }, + "concept4": { + "name": "concept4" + }, + "luma_concepts": { + "name": "luma_concepts", + "tooltip": "مفاهیم دوربین اختیاری برای افزودن به موارد انتخاب‌شده در اینجا." + } + }, + "outputs": { + "0": { + "name": "luma_concepts", + "tooltip": null + } + } + }, + "LumaImageModifyNode": { + "description": "تغییر تصاویر به صورت همزمان بر اساس پرامپت و نسبت تصویر.", + "display_name": "Luma Image to Image", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "image": { + "name": "image" + }, + "image_weight": { + "name": "image_weight", + "tooltip": "وزن تصویر؛ هرچه به ۱.۰ نزدیک‌تر باشد، تصویر کمتر تغییر می‌کند." + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "پرامپت برای تولید تصویر" + }, + "seed": { + "name": "seed", + "tooltip": "بذر برای تعیین اجرای مجدد گره؛ نتایج واقعی صرف‌نظر از بذر غیرقطعی هستند." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LumaImageNode": { + "description": "تولید تصاویر به صورت همزمان بر اساس پرامپت و نسبت تصویر.", + "display_name": "Luma Text to Image", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "character_image": { + "name": "character_image", + "tooltip": "تصاویر مرجع شخصیت؛ می‌تواند یک دسته از چند تصویر باشد، تا ۴ تصویر می‌تواند در نظر گرفته شود." + }, + "control_after_generate": { + "name": "control after generate" + }, + "image_luma_ref": { + "name": "image_luma_ref", + "tooltip": "اتصال گره مرجع Luma برای تأثیرگذاری بر تولید با تصاویر ورودی؛ تا ۴ تصویر می‌تواند در نظر گرفته شود." + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "پرامپت برای تولید تصویر" + }, + "seed": { + "name": "seed", + "tooltip": "بذر برای تعیین اجرای مجدد گره؛ نتایج واقعی صرف‌نظر از بذر غیرقطعی هستند." + }, + "style_image": { + "name": "style_image", + "tooltip": "تصویر مرجع سبک؛ فقط ۱ تصویر استفاده خواهد شد." + }, + "style_image_weight": { + "name": "style_image_weight", + "tooltip": "وزن تصویر سبک. اگر style_image ارائه نشود، نادیده گرفته می‌شود." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LumaImageToVideoNode": { + "description": "ویدیوها را به صورت همزمان بر اساس پرامپت، تصاویر ورودی و اندازه خروجی تولید می‌کند.", + "display_name": "تبدیل تصویر به ویدیو لاما", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان" + }, + "first_image": { + "name": "اولین تصویر", + "tooltip": "اولین فریم ویدیوی تولید شده." + }, + "last_image": { + "name": "آخرین تصویر", + "tooltip": "آخرین فریم ویدیوی تولید شده." + }, + "loop": { + "name": "حلقه" + }, + "luma_concepts": { + "name": "مفاهیم لاما", + "tooltip": "مفاهیم دوربین اختیاری برای تعیین حرکت دوربین از طریق node مفاهیم لاما." + }, + "model": { + "name": "مدل" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت برای تولید ویدیو" + }, + "resolution": { + "name": "وضوح" + }, + "seed": { + "name": "بذر", + "tooltip": "بذر برای تعیین اینکه آیا node باید دوباره اجرا شود؛ نتایج واقعی صرف‌نظر از بذر غیرقطعی هستند." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LumaReferenceNode": { + "description": "یک تصویر و وزن را برای استفاده در node تولید تصویر لاما نگه می‌دارد.", + "display_name": "مرجع لاما", + "inputs": { + "image": { + "name": "تصویر", + "tooltip": "تصویری که به عنوان مرجع استفاده می‌شود." + }, + "luma_ref": { + "name": "مرجع لاما" + }, + "weight": { + "name": "وزن", + "tooltip": "وزن مرجع تصویر." + } + }, + "outputs": { + "0": { + "name": "مرجع لاما", + "tooltip": null + } + } + }, + "LumaVideoNode": { + "description": "ویدیوها را به صورت همزمان بر اساس پرامپت و اندازه خروجی تولید می‌کند.", + "display_name": "تبدیل متن به ویدیو لاما", + "inputs": { + "aspect_ratio": { + "name": "نسبت تصویر" + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان" + }, + "loop": { + "name": "حلقه" + }, + "luma_concepts": { + "name": "مفاهیم لاما", + "tooltip": "مفاهیم دوربین اختیاری برای تعیین حرکت دوربین از طریق node مفاهیم لاما." + }, + "model": { + "name": "مدل" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت برای تولید ویدیو" + }, + "resolution": { + "name": "وضوح" + }, + "seed": { + "name": "بذر", + "tooltip": "بذر برای تعیین اینکه آیا node باید دوباره اجرا شود؛ نتایج واقعی صرف‌نظر از بذر غیرقطعی هستند." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Mahiro": { + "description": "راهنمایی را به گونه‌ای تغییر می‌دهد که بیشتر بر «جهت» پرامپت مثبت تمرکز کند تا تفاوت بین پرامپت منفی.", + "display_name": "Mahiro CFG", + "inputs": { + "model": { + "name": "مدل" + } + }, + "outputs": { + "0": { + "name": "مدل اصلاح‌شده", + "tooltip": null + } + } + }, + "MakeTrainingDataset": { + "display_name": "ایجاد دیتاست آموزش", + "inputs": { + "clip": { + "name": "clip", + "tooltip": "مدل CLIP برای کدگذاری متن به conditioning." + }, + "images": { + "name": "تصاویر", + "tooltip": "فهرست تصاویر برای کدگذاری." + }, + "texts": { + "name": "متون", + "tooltip": "فهرست کپشن‌های متنی. می‌تواند به طول n (مطابق با تصاویر)، ۱ (تکرار برای همه)، یا حذف شده (استفاده از رشته خالی) باشد." + }, + "vae": { + "name": "vae", + "tooltip": "مدل VAE برای کدگذاری تصاویر به فضای latent." + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "فهرست دیکشنری‌های latent" + }, + "1": { + "name": "conditioning", + "tooltip": "فهرست لیست‌های conditioning" + } + } + }, + "ManualSigmas": { + "display_name": "ManualSigmas", + "inputs": { + "sigmas": { + "name": "سیگماها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MaskComposite": { + "display_name": "ترکیب ماسک", + "inputs": { + "destination": { + "name": "مقصد" + }, + "operation": { + "name": "عملیات" + }, + "source": { + "name": "مبدأ" + }, + "x": { + "name": "x" + }, + "y": { + "name": "y" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MaskPreview": { + "description": "تصاویر ورودی را در پوشه خروجی ComfyUI شما ذخیره می‌کند.", + "display_name": "پیش‌نمایش ماسک", + "inputs": { + "mask": { + "name": "ماسک" + } + } + }, + "MaskToImage": { + "display_name": "تبدیل ماسک به تصویر", + "inputs": { + "mask": { + "name": "ماسک" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MergeImageLists": { + "display_name": "ادغام فهرست تصاویر", + "inputs": { + "images": { + "name": "تصاویر", + "tooltip": "فهرست تصاویر برای پردازش." + } + }, + "outputs": { + "0": { + "name": "تصاویر", + "tooltip": "تصاویر پردازش‌شده" + } + } + }, + "MergeTextLists": { + "display_name": "ادغام فهرست متون", + "inputs": { + "texts": { + "name": "متون", + "tooltip": "فهرست متون برای پردازش." + } + }, + "outputs": { + "0": { + "name": "متون", + "tooltip": "متون پردازش‌شده" + } + } + }, + "MeshyAnimateModelNode": { + "description": "یک اکشن انیمیشن خاص را به کاراکتری که قبلاً ریگ شده است اعمال کنید.", + "display_name": "Meshy: متحرک‌سازی مدل", + "inputs": { + "action_id": { + "name": "action_id", + "tooltip": "برای مشاهده لیست مقادیر موجود به https://docs.meshy.ai/en/api/animation-library مراجعه کنید." + }, + "rig_task_id": { + "name": "rig_task_id" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + } + } + }, + "MeshyImageToModelNode": { + "display_name": "Meshy: تصویر به مدل", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "image": { + "name": "image" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "حالت pose برای مدل تولیدشده را مشخص کنید." + }, + "seed": { + "name": "seed", + "tooltip": "Seed کنترل می‌کند که node دوباره اجرا شود یا نه؛ نتایج صرف‌نظر از مقدار seed غیرقطعی هستند." + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "اگر روی false تنظیم شود، یک مش مثلثی پردازش‌نشده بازمی‌گرداند." + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "should_texture": { + "name": "should_texture", + "tooltip": "تعیین می‌کند که آیا تکسچر تولید شود یا خیر. اگر روی false تنظیم شود، مرحله تکسچر رد شده و مش بدون تکسچر بازمی‌گردد." + }, + "should_texture_enable_pbr": { + "name": "enable_pbr" + }, + "should_texture_texture_prompt": { + "name": "texture_prompt" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyMultiImageToModelNode": { + "display_name": "Meshy: چند تصویر به مدل", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "حالت pose برای مدل تولیدشده را مشخص کنید." + }, + "seed": { + "name": "seed", + "tooltip": "Seed کنترل می‌کند که node دوباره اجرا شود یا نه؛ نتایج صرف‌نظر از مقدار seed غیرقطعی هستند." + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "اگر روی false تنظیم شود، یک مش مثلثی پردازش‌نشده بازمی‌گرداند." + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "should_texture": { + "name": "should_texture", + "tooltip": "تعیین می‌کند که آیا تکسچر تولید شود یا خیر. اگر روی false تنظیم شود، مرحله تکسچر رد شده و مش بدون تکسچر بازمی‌گردد." + }, + "should_texture_enable_pbr": { + "name": "enable_pbr" + }, + "should_texture_texture_prompt": { + "name": "texture_prompt" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRefineNode": { + "description": "یک مدل پیش‌نویس ایجادشده قبلی را بهبود دهید.", + "display_name": "Meshy: بهبود مدل پیش‌نویس", + "inputs": { + "enable_pbr": { + "name": "enable_pbr", + "tooltip": "تولید نقشه‌های PBR (فلزی، زبری، نرمال) علاوه بر رنگ پایه. توجه: هنگام استفاده از سبک Sculpture، این گزینه باید غیرفعال باشد، زیرا سبک Sculpture نقشه‌های PBR مخصوص خود را تولید می‌کند." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "model": { + "name": "model" + }, + "texture_image": { + "name": "texture_image", + "tooltip": "فقط یکی از 'texture_image' یا 'texture_prompt' را می‌توان همزمان استفاده کرد." + }, + "texture_prompt": { + "name": "texture_prompt", + "tooltip": "یک متن راهنما برای هدایت فرایند بافت‌دهی وارد کنید. حداکثر ۶۰۰ کاراکتر. نمی‌توان همزمان با 'texture_image' استفاده کرد." + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRigModelNode": { + "description": "یک کاراکتر ریگ‌شده در فرمت‌های استاندارد ارائه می‌دهد. ریگ خودکار در حال حاضر برای مش‌های بدون بافت، دارایی‌های غیرانسان‌نما یا دارایی‌های انسان‌نمایی که ساختار اندام و بدن آن‌ها نامشخص است، مناسب نیست.", + "display_name": "Meshy: ریگ مدل", + "inputs": { + "height_meters": { + "name": "height_meters", + "tooltip": "ارتفاع تقریبی مدل کاراکتر به متر. این مقدار به دقت مقیاس‌بندی و ریگ کمک می‌کند." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "texture_image": { + "name": "texture_image", + "tooltip": "تصویر بافت رنگ پایه مدل با UV باز شده." + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "rig_task_id", + "tooltip": null + } + } + }, + "MeshyTextToModelNode": { + "display_name": "Meshy: متن به مدل", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "حالت ژست برای مدل تولیدشده را مشخص کنید." + }, + "prompt": { + "name": "prompt" + }, + "seed": { + "name": "seed", + "tooltip": "Seed کنترل می‌کند که node دوباره اجرا شود یا نه؛ نتایج صرف‌نظر از seed غیرقطعی هستند." + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "در صورت غیرفعال بودن، یک مش مثلثی پردازش‌نشده بازمی‌گرداند." + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "style": { + "name": "style" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyTextureNode": { + "display_name": "Meshy: مدل بافت", + "inputs": { + "enable_original_uv": { + "name": "فعال‌سازی UV اصلی", + "tooltip": "استفاده از UV اصلی مدل به جای تولید UV جدید. در صورت فعال بودن، Meshy بافت‌های موجود مدل بارگذاری‌شده را حفظ می‌کند. اگر مدل UV اصلی نداشته باشد، کیفیت خروجی ممکن است به خوبی نباشد." + }, + "image_style": { + "name": "سبک تصویر", + "tooltip": "یک تصویر دوبعدی برای راهنمایی فرآیند بافت‌دهی. نمی‌توان همزمان با 'text_style_prompt' استفاده کرد." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "model": { + "name": "مدل" + }, + "pbr": { + "name": "PBR" + }, + "text_style_prompt": { + "name": "توضیح سبک بافت متنی", + "tooltip": "سبک بافت مورد نظر شیء را با استفاده از متن توصیف کنید. حداکثر ۶۰۰ کاراکتر. نمی‌توان همزمان با 'image_style' استفاده کرد." + } + }, + "outputs": { + "0": { + "name": "فایل مدل", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MinimaxHailuoVideoNode": { + "description": "تولید ویدئو از طریق پرامپت، با امکان استفاده از فریم ابتدایی با مدل جدید MiniMax Hailuo-02.", + "display_name": "ویدئوی MiniMax Hailuo", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان", + "tooltip": "طول ویدئوی خروجی بر حسب ثانیه." + }, + "first_frame_image": { + "name": "تصویر فریم اول", + "tooltip": "تصویر اختیاری برای استفاده به عنوان فریم اول ویدئو." + }, + "prompt_optimizer": { + "name": "بهینه‌ساز پرامپت", + "tooltip": "بهینه‌سازی پرامپت برای بهبود کیفیت تولید در صورت نیاز." + }, + "prompt_text": { + "name": "متن پرامپت", + "tooltip": "متن راهنما برای تولید ویدئو." + }, + "resolution": { + "name": "وضوح تصویر", + "tooltip": "ابعاد نمایش ویدئو. ۱۰۸۰p معادل ۱۹۲۰x۱۰۸۰، ۷۶۸p معادل ۱۳۶۶x۷۶۸ است." + }, + "seed": { + "name": "بذر", + "tooltip": "بذر تصادفی برای ایجاد نویز." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MinimaxImageToVideoNode": { + "description": "ویدیوها را به صورت همزمان بر اساس یک تصویر و متن راهنما و پارامترهای اختیاری تولید می‌کند.", + "display_name": "MiniMax تبدیل تصویر به ویدیو", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "image": { + "name": "تصویر", + "tooltip": "تصویری که به عنوان اولین فریم تولید ویدیو استفاده می‌شود" + }, + "model": { + "name": "مدل", + "tooltip": "مدلی که برای تولید ویدیو استفاده می‌شود" + }, + "prompt_text": { + "name": "متن راهنما", + "tooltip": "متن راهنما برای هدایت تولید ویدیو" + }, + "seed": { + "name": "بذر تصادفی", + "tooltip": "بذر تصادفی که برای ایجاد نویز استفاده می‌شود." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MinimaxTextToVideoNode": { + "description": "ویدیوها را به صورت همزمان بر اساس یک متن راهنما و پارامترهای اختیاری تولید می‌کند.", + "display_name": "MiniMax تبدیل متن به ویدیو", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "model": { + "name": "مدل", + "tooltip": "مدلی که برای تولید ویدیو استفاده می‌شود" + }, + "prompt_text": { + "name": "متن راهنما", + "tooltip": "متن راهنما برای هدایت تولید ویدیو" + }, + "seed": { + "name": "بذر تصادفی", + "tooltip": "بذر تصادفی که برای ایجاد نویز استفاده می‌شود." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ModelComputeDtype": { + "display_name": "ModelComputeDtype", + "inputs": { + "dtype": { + "name": "نوع داده" + }, + "model": { + "name": "مدل" + } + } + }, + "ModelMergeAdd": { + "display_name": "ModelMergeAdd", + "inputs": { + "model1": { + "name": "مدل۱" + }, + "model2": { + "name": "مدل۲" + } + } + }, + "ModelMergeAuraflow": { + "display_name": "ModelMergeAuraflow", + "inputs": { + "cond_seq_linear_": { + "name": "cond_seq_linear." + }, + "double_layers_0_": { + "name": "double_layers.۰." + }, + "double_layers_1_": { + "name": "double_layers.۱." + }, + "double_layers_2_": { + "name": "double_layers.۲." + }, + "double_layers_3_": { + "name": "double_layers.۳." + }, + "final_linear_": { + "name": "final_linear." + }, + "init_x_linear_": { + "name": "init_x_linear." + }, + "modF_": { + "name": "modF." + }, + "model1": { + "name": "مدل۱" + }, + "model2": { + "name": "مدل۲" + }, + "positional_encoding": { + "name": "کدگذاری موقعیتی" + }, + "register_tokens": { + "name": "ثبت توکن‌ها" + }, + "single_layers_0_": { + "name": "single_layers.۰." + }, + "single_layers_10_": { + "name": "single_layers.۱۰." + }, + "single_layers_11_": { + "name": "single_layers.۱۱." + }, + "single_layers_12_": { + "name": "single_layers.۱۲." + }, + "single_layers_13_": { + "name": "single_layers.۱۳." + }, + "single_layers_14_": { + "name": "single_layers.۱۴." + }, + "single_layers_15_": { + "name": "single_layers.۱۵." + }, + "single_layers_16_": { + "name": "single_layers.۱۶." + }, + "single_layers_17_": { + "name": "single_layers.۱۷." + }, + "single_layers_18_": { + "name": "single_layers.۱۸." + }, + "single_layers_19_": { + "name": "single_layers.۱۹." + }, + "single_layers_1_": { + "name": "single_layers.۱." + }, + "single_layers_20_": { + "name": "single_layers.۲۰." + }, + "single_layers_21_": { + "name": "single_layers.۲۱." + }, + "single_layers_22_": { + "name": "single_layers.۲۲." + }, + "single_layers_23_": { + "name": "single_layers.۲۳." + }, + "single_layers_24_": { + "name": "single_layers.۲۴." + }, + "single_layers_25_": { + "name": "single_layers.۲۵." + }, + "single_layers_26_": { + "name": "single_layers.۲۶." + }, + "single_layers_27_": { + "name": "single_layers.۲۷." + }, + "single_layers_28_": { + "name": "single_layers.۲۸." + }, + "single_layers_29_": { + "name": "single_layers.۲۹." + }, + "single_layers_2_": { + "name": "single_layers.۲." + }, + "single_layers_30_": { + "name": "single_layers.۳۰." + }, + "single_layers_31_": { + "name": "single_layers.۳۱." + }, + "single_layers_3_": { + "name": "single_layers.۳." + }, + "single_layers_4_": { + "name": "single_layers.۴." + }, + "single_layers_5_": { + "name": "single_layers.۵." + }, + "single_layers_6_": { + "name": "single_layers.۶." + }, + "single_layers_7_": { + "name": "single_layers.۷." + }, + "single_layers_8_": { + "name": "single_layers.۸." + }, + "single_layers_9_": { + "name": "single_layers.۹." + }, + "t_embedder_": { + "name": "t_embedder." + } + } + }, + "ModelMergeBlocks": { + "display_name": "ModelMergeBlocks", + "inputs": { + "input": { + "name": "ورودی" + }, + "middle": { + "name": "میانی" + }, + "model1": { + "name": "مدل۱" + }, + "model2": { + "name": "مدل۲" + }, + "out": { + "name": "خروجی" + } + } + }, + "ModelMergeCosmos14B": { + "display_name": "ModelMergeCosmos14B", + "inputs": { + "affline_norm_": { + "name": "affline_norm." + }, + "blocks_block0_": { + "name": "بلوک ۰." + }, + "blocks_block10_": { + "name": "بلوک ۱۰." + }, + "blocks_block11_": { + "name": "بلوک ۱۱." + }, + "blocks_block12_": { + "name": "بلوک ۱۲." + }, + "blocks_block13_": { + "name": "بلوک ۱۳." + }, + "blocks_block14_": { + "name": "بلوک ۱۴." + }, + "blocks_block15_": { + "name": "بلوک ۱۵." + }, + "blocks_block16_": { + "name": "بلوک ۱۶." + }, + "blocks_block17_": { + "name": "بلوک ۱۷." + }, + "blocks_block18_": { + "name": "بلوک ۱۸." + }, + "blocks_block19_": { + "name": "بلوک ۱۹." + }, + "blocks_block1_": { + "name": "بلوک ۱." + }, + "blocks_block20_": { + "name": "بلوک ۲۰." + }, + "blocks_block21_": { + "name": "بلوک ۲۱." + }, + "blocks_block22_": { + "name": "بلوک ۲۲." + }, + "blocks_block23_": { + "name": "بلوک ۲۳." + }, + "blocks_block24_": { + "name": "بلوک ۲۴." + }, + "blocks_block25_": { + "name": "بلوک ۲۵." + }, + "blocks_block26_": { + "name": "بلوک ۲۶." + }, + "blocks_block27_": { + "name": "بلوک ۲۷." + }, + "blocks_block28_": { + "name": "بلوک ۲۸." + }, + "blocks_block29_": { + "name": "بلوک ۲۹." + }, + "blocks_block2_": { + "name": "بلوک ۲." + }, + "blocks_block30_": { + "name": "بلوک ۳۰." + }, + "blocks_block31_": { + "name": "بلوک ۳۱." + }, + "blocks_block32_": { + "name": "بلوک ۳۲." + }, + "blocks_block33_": { + "name": "بلوک ۳۳." + }, + "blocks_block34_": { + "name": "بلوک ۳۴." + }, + "blocks_block35_": { + "name": "بلوک ۳۵." + }, + "blocks_block3_": { + "name": "بلوک ۳." + }, + "blocks_block4_": { + "name": "بلوک ۴." + }, + "blocks_block5_": { + "name": "بلوک ۵." + }, + "blocks_block6_": { + "name": "بلوک ۶." + }, + "blocks_block7_": { + "name": "بلوک ۷." + }, + "blocks_block8_": { + "name": "بلوک ۸." + }, + "blocks_block9_": { + "name": "بلوک ۹." + }, + "extra_pos_embedder_": { + "name": "extra_pos_embedder." + }, + "final_layer_": { + "name": "لایه نهایی." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "pos_embedder_": { + "name": "pos_embedder." + }, + "t_embedder_": { + "name": "t_embedder." + }, + "x_embedder_": { + "name": "x_embedder." + } + } + }, + "ModelMergeCosmos7B": { + "display_name": "ModelMergeCosmos7B", + "inputs": { + "affline_norm_": { + "name": "affline_norm." + }, + "blocks_block0_": { + "name": "بلوک ۰." + }, + "blocks_block10_": { + "name": "بلوک ۱۰." + }, + "blocks_block11_": { + "name": "بلوک ۱۱." + }, + "blocks_block12_": { + "name": "بلوک ۱۲." + }, + "blocks_block13_": { + "name": "بلوک ۱۳." + }, + "blocks_block14_": { + "name": "بلوک ۱۴." + }, + "blocks_block15_": { + "name": "بلوک ۱۵." + }, + "blocks_block16_": { + "name": "بلوک ۱۶." + }, + "blocks_block17_": { + "name": "بلوک ۱۷." + }, + "blocks_block18_": { + "name": "بلوک ۱۸." + }, + "blocks_block19_": { + "name": "بلوک ۱۹." + }, + "blocks_block1_": { + "name": "بلوک ۱." + }, + "blocks_block20_": { + "name": "بلوک ۲۰." + }, + "blocks_block21_": { + "name": "بلوک ۲۱." + }, + "blocks_block22_": { + "name": "بلوک ۲۲." + }, + "blocks_block23_": { + "name": "بلوک ۲۳." + }, + "blocks_block24_": { + "name": "بلوک ۲۴." + }, + "blocks_block25_": { + "name": "بلوک ۲۵." + }, + "blocks_block26_": { + "name": "بلوک ۲۶." + }, + "blocks_block27_": { + "name": "بلوک ۲۷." + }, + "blocks_block2_": { + "name": "بلوک ۲." + }, + "blocks_block3_": { + "name": "بلوک ۳." + }, + "blocks_block4_": { + "name": "بلوک ۴." + }, + "blocks_block5_": { + "name": "بلوک ۵." + }, + "blocks_block6_": { + "name": "بلوک ۶." + }, + "blocks_block7_": { + "name": "بلوک ۷." + }, + "blocks_block8_": { + "name": "بلوک ۸." + }, + "blocks_block9_": { + "name": "بلوک ۹." + }, + "extra_pos_embedder_": { + "name": "extra_pos_embedder." + }, + "final_layer_": { + "name": "لایه نهایی." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "pos_embedder_": { + "name": "pos_embedder." + }, + "t_embedder_": { + "name": "t_embedder." + }, + "x_embedder_": { + "name": "x_embedder." + } + } + }, + "ModelMergeCosmosPredict2_14B": { + "display_name": "ModelMergeCosmosPredict2_14B", + "inputs": { + "blocks_0_": { + "name": "blocks.۰." + }, + "blocks_10_": { + "name": "blocks.۱۰." + }, + "blocks_11_": { + "name": "blocks.۱۱." + }, + "blocks_12_": { + "name": "blocks.۱۲." + }, + "blocks_13_": { + "name": "blocks.۱۳." + }, + "blocks_14_": { + "name": "blocks.۱۴." + }, + "blocks_15_": { + "name": "blocks.۱۵." + }, + "blocks_16_": { + "name": "blocks.۱۶." + }, + "blocks_17_": { + "name": "blocks.۱۷." + }, + "blocks_18_": { + "name": "blocks.۱۸." + }, + "blocks_19_": { + "name": "blocks.۱۹." + }, + "blocks_1_": { + "name": "blocks.۱." + }, + "blocks_20_": { + "name": "blocks.۲۰." + }, + "blocks_21_": { + "name": "blocks.۲۱." + }, + "blocks_22_": { + "name": "blocks.۲۲." + }, + "blocks_23_": { + "name": "blocks.۲۳." + }, + "blocks_24_": { + "name": "blocks.۲۴." + }, + "blocks_25_": { + "name": "blocks.۲۵." + }, + "blocks_26_": { + "name": "blocks.۲۶." + }, + "blocks_27_": { + "name": "blocks.۲۷." + }, + "blocks_28_": { + "name": "blocks.۲۸." + }, + "blocks_29_": { + "name": "blocks.۲۹." + }, + "blocks_2_": { + "name": "blocks.۲." + }, + "blocks_30_": { + "name": "blocks.۳۰." + }, + "blocks_31_": { + "name": "blocks.۳۱." + }, + "blocks_32_": { + "name": "blocks.۳۲." + }, + "blocks_33_": { + "name": "blocks.۳۳." + }, + "blocks_34_": { + "name": "blocks.۳۴." + }, + "blocks_35_": { + "name": "blocks.۳۵." + }, + "blocks_3_": { + "name": "blocks.۳." + }, + "blocks_4_": { + "name": "blocks.۴." + }, + "blocks_5_": { + "name": "blocks.۵." + }, + "blocks_6_": { + "name": "blocks.۶." + }, + "blocks_7_": { + "name": "blocks.۷." + }, + "blocks_8_": { + "name": "blocks.۸." + }, + "blocks_9_": { + "name": "blocks.۹." + }, + "final_layer_": { + "name": "final_layer." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "pos_embedder_": { + "name": "pos_embedder." + }, + "t_embedder_": { + "name": "t_embedder." + }, + "t_embedding_norm_": { + "name": "t_embedding_norm." + }, + "x_embedder_": { + "name": "x_embedder." + } + } + }, + "ModelMergeCosmosPredict2_2B": { + "display_name": "ModelMergeCosmosPredict2_2B", + "inputs": { + "blocks_0_": { + "name": "blocks.۰." + }, + "blocks_10_": { + "name": "blocks.۱۰." + }, + "blocks_11_": { + "name": "blocks.۱۱." + }, + "blocks_12_": { + "name": "blocks.۱۲." + }, + "blocks_13_": { + "name": "blocks.۱۳." + }, + "blocks_14_": { + "name": "blocks.۱۴." + }, + "blocks_15_": { + "name": "blocks.۱۵." + }, + "blocks_16_": { + "name": "blocks.۱۶." + }, + "blocks_17_": { + "name": "blocks.۱۷." + }, + "blocks_18_": { + "name": "blocks.۱۸." + }, + "blocks_19_": { + "name": "blocks.۱۹." + }, + "blocks_1_": { + "name": "blocks.۱." + }, + "blocks_20_": { + "name": "blocks.۲۰." + }, + "blocks_21_": { + "name": "blocks.۲۱." + }, + "blocks_22_": { + "name": "blocks.۲۲." + }, + "blocks_23_": { + "name": "blocks.۲۳." + }, + "blocks_24_": { + "name": "blocks.۲۴." + }, + "blocks_25_": { + "name": "blocks.۲۵." + }, + "blocks_26_": { + "name": "blocks.۲۶." + }, + "blocks_27_": { + "name": "blocks.۲۷." + }, + "blocks_2_": { + "name": "blocks.۲." + }, + "blocks_3_": { + "name": "blocks.۳." + }, + "blocks_4_": { + "name": "blocks.۴." + }, + "blocks_5_": { + "name": "blocks.۵." + }, + "blocks_6_": { + "name": "blocks.۶." + }, + "blocks_7_": { + "name": "blocks.۷." + }, + "blocks_8_": { + "name": "blocks.۸." + }, + "blocks_9_": { + "name": "blocks.۹." + }, + "final_layer_": { + "name": "final_layer." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "pos_embedder_": { + "name": "pos_embedder." + }, + "t_embedder_": { + "name": "t_embedder." + }, + "t_embedding_norm_": { + "name": "t_embedding_norm." + }, + "x_embedder_": { + "name": "x_embedder." + } + } + }, + "ModelMergeFlux1": { + "display_name": "ModelMergeFlux1", + "inputs": { + "double_blocks_0_": { + "name": "بلوک دوتایی ۰" + }, + "double_blocks_10_": { + "name": "بلوک دوتایی ۱۰" + }, + "double_blocks_11_": { + "name": "بلوک دوتایی ۱۱" + }, + "double_blocks_12_": { + "name": "بلوک دوتایی ۱۲" + }, + "double_blocks_13_": { + "name": "بلوک دوتایی ۱۳" + }, + "double_blocks_14_": { + "name": "بلوک دوتایی ۱۴" + }, + "double_blocks_15_": { + "name": "بلوک دوتایی ۱۵" + }, + "double_blocks_16_": { + "name": "بلوک دوتایی ۱۶" + }, + "double_blocks_17_": { + "name": "بلوک دوتایی ۱۷" + }, + "double_blocks_18_": { + "name": "بلوک دوتایی ۱۸" + }, + "double_blocks_1_": { + "name": "بلوک دوتایی ۱" + }, + "double_blocks_2_": { + "name": "بلوک دوتایی ۲" + }, + "double_blocks_3_": { + "name": "بلوک دوتایی ۳" + }, + "double_blocks_4_": { + "name": "بلوک دوتایی ۴" + }, + "double_blocks_5_": { + "name": "بلوک دوتایی ۵" + }, + "double_blocks_6_": { + "name": "بلوک دوتایی ۶" + }, + "double_blocks_7_": { + "name": "بلوک دوتایی ۷" + }, + "double_blocks_8_": { + "name": "بلوک دوتایی ۸" + }, + "double_blocks_9_": { + "name": "بلوک دوتایی ۹" + }, + "final_layer_": { + "name": "لایه نهایی" + }, + "guidance_in": { + "name": "ورودی راهنما" + }, + "img_in_": { + "name": "ورودی تصویر" + }, + "model1": { + "name": "مدل۱" + }, + "model2": { + "name": "مدل۲" + }, + "single_blocks_0_": { + "name": "بلوک تکی ۰" + }, + "single_blocks_10_": { + "name": "بلوک تکی ۱۰" + }, + "single_blocks_11_": { + "name": "بلوک تکی ۱۱" + }, + "single_blocks_12_": { + "name": "بلوک تکی ۱۲" + }, + "single_blocks_13_": { + "name": "بلوک تکی ۱۳" + }, + "single_blocks_14_": { + "name": "بلوک تکی ۱۴" + }, + "single_blocks_15_": { + "name": "بلوک تکی ۱۵" + }, + "single_blocks_16_": { + "name": "بلوک تکی ۱۶" + }, + "single_blocks_17_": { + "name": "بلوک تکی ۱۷" + }, + "single_blocks_18_": { + "name": "بلوک تکی ۱۸" + }, + "single_blocks_19_": { + "name": "بلوک تکی ۱۹" + }, + "single_blocks_1_": { + "name": "بلوک تکی ۱" + }, + "single_blocks_20_": { + "name": "بلوک تکی ۲۰" + }, + "single_blocks_21_": { + "name": "بلوک تکی ۲۱" + }, + "single_blocks_22_": { + "name": "بلوک تکی ۲۲" + }, + "single_blocks_23_": { + "name": "بلوک تکی ۲۳" + }, + "single_blocks_24_": { + "name": "بلوک تکی ۲۴" + }, + "single_blocks_25_": { + "name": "بلوک تکی ۲۵" + }, + "single_blocks_26_": { + "name": "بلوک تکی ۲۶" + }, + "single_blocks_27_": { + "name": "بلوک تکی ۲۷" + }, + "single_blocks_28_": { + "name": "بلوک تکی ۲۸" + }, + "single_blocks_29_": { + "name": "بلوک تکی ۲۹" + }, + "single_blocks_2_": { + "name": "بلوک تکی ۲" + }, + "single_blocks_30_": { + "name": "بلوک تکی ۳۰" + }, + "single_blocks_31_": { + "name": "بلوک تکی ۳۱" + }, + "single_blocks_32_": { + "name": "بلوک تکی ۳۲" + }, + "single_blocks_33_": { + "name": "بلوک تکی ۳۳" + }, + "single_blocks_34_": { + "name": "بلوک تکی ۳۴" + }, + "single_blocks_35_": { + "name": "بلوک تکی ۳۵" + }, + "single_blocks_36_": { + "name": "بلوک تکی ۳۶" + }, + "single_blocks_37_": { + "name": "بلوک تکی ۳۷" + }, + "single_blocks_3_": { + "name": "بلوک تکی ۳" + }, + "single_blocks_4_": { + "name": "بلوک تکی ۴" + }, + "single_blocks_5_": { + "name": "بلوک تکی ۵" + }, + "single_blocks_6_": { + "name": "بلوک تکی ۶" + }, + "single_blocks_7_": { + "name": "بلوک تکی ۷" + }, + "single_blocks_8_": { + "name": "بلوک تکی ۸" + }, + "single_blocks_9_": { + "name": "بلوک تکی ۹" + }, + "time_in_": { + "name": "ورودی زمان" + }, + "txt_in_": { + "name": "ورودی متن" + }, + "vector_in_": { + "name": "ورودی بردار" + } + } + }, + "ModelMergeLTXV": { + "display_name": "ModelMergeLTXV", + "inputs": { + "adaln_single_": { + "name": "adaln_single." + }, + "caption_projection_": { + "name": "caption_projection." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "patchify_proj_": { + "name": "patchify_proj." + }, + "proj_out_": { + "name": "proj_out." + }, + "scale_shift_table": { + "name": "scale_shift_table" + }, + "transformer_blocks_0_": { + "name": "transformer_blocks.۰." + }, + "transformer_blocks_10_": { + "name": "transformer_blocks.۱۰." + }, + "transformer_blocks_11_": { + "name": "transformer_blocks.۱۱." + }, + "transformer_blocks_12_": { + "name": "transformer_blocks.۱۲." + }, + "transformer_blocks_13_": { + "name": "transformer_blocks.۱۳." + }, + "transformer_blocks_14_": { + "name": "transformer_blocks.۱۴." + }, + "transformer_blocks_15_": { + "name": "transformer_blocks.۱۵." + }, + "transformer_blocks_16_": { + "name": "transformer_blocks.۱۶." + }, + "transformer_blocks_17_": { + "name": "transformer_blocks.۱۷." + }, + "transformer_blocks_18_": { + "name": "transformer_blocks.۱۸." + }, + "transformer_blocks_19_": { + "name": "transformer_blocks.۱۹." + }, + "transformer_blocks_1_": { + "name": "transformer_blocks.۱." + }, + "transformer_blocks_20_": { + "name": "transformer_blocks.۲۰." + }, + "transformer_blocks_21_": { + "name": "transformer_blocks.۲۱." + }, + "transformer_blocks_22_": { + "name": "transformer_blocks.۲۲." + }, + "transformer_blocks_23_": { + "name": "transformer_blocks.۲۳." + }, + "transformer_blocks_24_": { + "name": "transformer_blocks.۲۴." + }, + "transformer_blocks_25_": { + "name": "transformer_blocks.۲۵." + }, + "transformer_blocks_26_": { + "name": "transformer_blocks.۲۶." + }, + "transformer_blocks_27_": { + "name": "transformer_blocks.۲۷." + }, + "transformer_blocks_2_": { + "name": "transformer_blocks.۲." + }, + "transformer_blocks_3_": { + "name": "transformer_blocks.۳." + }, + "transformer_blocks_4_": { + "name": "transformer_blocks.۴." + }, + "transformer_blocks_5_": { + "name": "transformer_blocks.۵." + }, + "transformer_blocks_6_": { + "name": "transformer_blocks.۶." + }, + "transformer_blocks_7_": { + "name": "transformer_blocks.۷." + }, + "transformer_blocks_8_": { + "name": "transformer_blocks.۸." + }, + "transformer_blocks_9_": { + "name": "transformer_blocks.۹." + } + } + }, + "ModelMergeMochiPreview": { + "display_name": "ModelMergeMochiPreview", + "inputs": { + "blocks_0_": { + "name": "blocks.۰." + }, + "blocks_10_": { + "name": "blocks.۱۰." + }, + "blocks_11_": { + "name": "blocks.۱۱." + }, + "blocks_12_": { + "name": "blocks.۱۲." + }, + "blocks_13_": { + "name": "blocks.۱۳." + }, + "blocks_14_": { + "name": "blocks.۱۴." + }, + "blocks_15_": { + "name": "blocks.۱۵." + }, + "blocks_16_": { + "name": "blocks.۱۶." + }, + "blocks_17_": { + "name": "blocks.۱۷." + }, + "blocks_18_": { + "name": "blocks.۱۸." + }, + "blocks_19_": { + "name": "blocks.۱۹." + }, + "blocks_1_": { + "name": "blocks.۱." + }, + "blocks_20_": { + "name": "blocks.۲۰." + }, + "blocks_21_": { + "name": "blocks.۲۱." + }, + "blocks_22_": { + "name": "blocks.۲۲." + }, + "blocks_23_": { + "name": "blocks.۲۳." + }, + "blocks_24_": { + "name": "blocks.۲۴." + }, + "blocks_25_": { + "name": "blocks.۲۵." + }, + "blocks_26_": { + "name": "blocks.۲۶." + }, + "blocks_27_": { + "name": "blocks.۲۷." + }, + "blocks_28_": { + "name": "blocks.۲۸." + }, + "blocks_29_": { + "name": "blocks.۲۹." + }, + "blocks_2_": { + "name": "blocks.۲." + }, + "blocks_30_": { + "name": "blocks.۳۰." + }, + "blocks_31_": { + "name": "blocks.۳۱." + }, + "blocks_32_": { + "name": "blocks.۳۲." + }, + "blocks_33_": { + "name": "blocks.۳۳." + }, + "blocks_34_": { + "name": "blocks.۳۴." + }, + "blocks_35_": { + "name": "blocks.۳۵." + }, + "blocks_36_": { + "name": "blocks.۳۶." + }, + "blocks_37_": { + "name": "blocks.۳۷." + }, + "blocks_38_": { + "name": "blocks.۳۸." + }, + "blocks_39_": { + "name": "blocks.۳۹." + }, + "blocks_3_": { + "name": "blocks.۳." + }, + "blocks_40_": { + "name": "blocks.۴۰." + }, + "blocks_41_": { + "name": "blocks.۴۱." + }, + "blocks_42_": { + "name": "blocks.۴۲." + }, + "blocks_43_": { + "name": "blocks.۴۳." + }, + "blocks_44_": { + "name": "blocks.۴۴." + }, + "blocks_45_": { + "name": "blocks.۴۵." + }, + "blocks_46_": { + "name": "blocks.۴۶." + }, + "blocks_47_": { + "name": "blocks.۴۷." + }, + "blocks_4_": { + "name": "blocks.۴." + }, + "blocks_5_": { + "name": "blocks.۵." + }, + "blocks_6_": { + "name": "blocks.۶." + }, + "blocks_7_": { + "name": "blocks.۷." + }, + "blocks_8_": { + "name": "blocks.۸." + }, + "blocks_9_": { + "name": "blocks.۹." + }, + "final_layer_": { + "name": "final_layer." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "pos_frequencies_": { + "name": "pos_frequencies." + }, + "t5_y_embedder_": { + "name": "t5_y_embedder." + }, + "t5_yproj_": { + "name": "t5_yproj." + }, + "t_embedder_": { + "name": "t_embedder." + } + } + }, + "ModelMergeQwenImage": { + "display_name": "ModelMergeQwenImage", + "inputs": { + "img_in_": { + "name": "img_in." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "pos_embeds_": { + "name": "pos_embeds." + }, + "proj_out_": { + "name": "proj_out." + }, + "time_text_embed_": { + "name": "time_text_embed." + }, + "transformer_blocks_0_": { + "name": "transformer_blocks.۰." + }, + "transformer_blocks_10_": { + "name": "transformer_blocks.۱۰." + }, + "transformer_blocks_11_": { + "name": "transformer_blocks.۱۱." + }, + "transformer_blocks_12_": { + "name": "transformer_blocks.۱۲." + }, + "transformer_blocks_13_": { + "name": "transformer_blocks.۱۳." + }, + "transformer_blocks_14_": { + "name": "transformer_blocks.۱۴." + }, + "transformer_blocks_15_": { + "name": "transformer_blocks.۱۵." + }, + "transformer_blocks_16_": { + "name": "transformer_blocks.۱۶." + }, + "transformer_blocks_17_": { + "name": "transformer_blocks.۱۷." + }, + "transformer_blocks_18_": { + "name": "transformer_blocks.۱۸." + }, + "transformer_blocks_19_": { + "name": "transformer_blocks.۱۹." + }, + "transformer_blocks_1_": { + "name": "transformer_blocks.۱." + }, + "transformer_blocks_20_": { + "name": "transformer_blocks.۲۰." + }, + "transformer_blocks_21_": { + "name": "transformer_blocks.۲۱." + }, + "transformer_blocks_22_": { + "name": "transformer_blocks.۲۲." + }, + "transformer_blocks_23_": { + "name": "transformer_blocks.۲۳." + }, + "transformer_blocks_24_": { + "name": "transformer_blocks.۲۴." + }, + "transformer_blocks_25_": { + "name": "transformer_blocks.۲۵." + }, + "transformer_blocks_26_": { + "name": "transformer_blocks.۲۶." + }, + "transformer_blocks_27_": { + "name": "transformer_blocks.۲۷." + }, + "transformer_blocks_28_": { + "name": "transformer_blocks.۲۸." + }, + "transformer_blocks_29_": { + "name": "transformer_blocks.۲۹." + }, + "transformer_blocks_2_": { + "name": "transformer_blocks.۲." + }, + "transformer_blocks_30_": { + "name": "transformer_blocks.۳۰." + }, + "transformer_blocks_31_": { + "name": "transformer_blocks.۳۱." + }, + "transformer_blocks_32_": { + "name": "transformer_blocks.۳۲." + }, + "transformer_blocks_33_": { + "name": "transformer_blocks.۳۳." + }, + "transformer_blocks_34_": { + "name": "transformer_blocks.۳۴." + }, + "transformer_blocks_35_": { + "name": "transformer_blocks.۳۵." + }, + "transformer_blocks_36_": { + "name": "transformer_blocks.۳۶." + }, + "transformer_blocks_37_": { + "name": "transformer_blocks.۳۷." + }, + "transformer_blocks_38_": { + "name": "transformer_blocks.۳۸." + }, + "transformer_blocks_39_": { + "name": "transformer_blocks.۳۹." + }, + "transformer_blocks_3_": { + "name": "transformer_blocks.۳." + }, + "transformer_blocks_40_": { + "name": "transformer_blocks.۴۰." + }, + "transformer_blocks_41_": { + "name": "transformer_blocks.۴۱." + }, + "transformer_blocks_42_": { + "name": "transformer_blocks.۴۲." + }, + "transformer_blocks_43_": { + "name": "transformer_blocks.۴۳." + }, + "transformer_blocks_44_": { + "name": "transformer_blocks.۴۴." + }, + "transformer_blocks_45_": { + "name": "transformer_blocks.۴۵." + }, + "transformer_blocks_46_": { + "name": "transformer_blocks.۴۶." + }, + "transformer_blocks_47_": { + "name": "transformer_blocks.۴۷." + }, + "transformer_blocks_48_": { + "name": "transformer_blocks.۴۸." + }, + "transformer_blocks_49_": { + "name": "transformer_blocks.۴۹." + }, + "transformer_blocks_4_": { + "name": "transformer_blocks.۴." + }, + "transformer_blocks_50_": { + "name": "transformer_blocks.۵۰." + }, + "transformer_blocks_51_": { + "name": "transformer_blocks.۵۱." + }, + "transformer_blocks_52_": { + "name": "transformer_blocks.۵۲." + }, + "transformer_blocks_53_": { + "name": "transformer_blocks.۵۳." + }, + "transformer_blocks_54_": { + "name": "transformer_blocks.۵۴." + }, + "transformer_blocks_55_": { + "name": "transformer_blocks.۵۵." + }, + "transformer_blocks_56_": { + "name": "transformer_blocks.۵۶." + }, + "transformer_blocks_57_": { + "name": "transformer_blocks.۵۷." + }, + "transformer_blocks_58_": { + "name": "transformer_blocks.۵۸." + }, + "transformer_blocks_59_": { + "name": "transformer_blocks.۵۹." + }, + "transformer_blocks_5_": { + "name": "transformer_blocks.۵." + }, + "transformer_blocks_6_": { + "name": "transformer_blocks.۶." + }, + "transformer_blocks_7_": { + "name": "transformer_blocks.۷." + }, + "transformer_blocks_8_": { + "name": "transformer_blocks.۸." + }, + "transformer_blocks_9_": { + "name": "transformer_blocks.۹." + }, + "txt_in_": { + "name": "txt_in." + }, + "txt_norm_": { + "name": "txt_norm." + } + } + }, + "ModelMergeSD1": { + "display_name": "ModelMergeSD1", + "inputs": { + "input_blocks_0_": { + "name": "ورودی بلوک‌ها.۰." + }, + "input_blocks_10_": { + "name": "ورودی بلوک‌ها.۱۰." + }, + "input_blocks_11_": { + "name": "ورودی بلوک‌ها.۱۱." + }, + "input_blocks_1_": { + "name": "ورودی بلوک‌ها.۱." + }, + "input_blocks_2_": { + "name": "ورودی بلوک‌ها.۲." + }, + "input_blocks_3_": { + "name": "ورودی بلوک‌ها.۳." + }, + "input_blocks_4_": { + "name": "ورودی بلوک‌ها.۴." + }, + "input_blocks_5_": { + "name": "ورودی بلوک‌ها.۵." + }, + "input_blocks_6_": { + "name": "ورودی بلوک‌ها.۶." + }, + "input_blocks_7_": { + "name": "ورودی بلوک‌ها.۷." + }, + "input_blocks_8_": { + "name": "ورودی بلوک‌ها.۸." + }, + "input_blocks_9_": { + "name": "ورودی بلوک‌ها.۹." + }, + "label_emb_": { + "name": "label_emb." + }, + "middle_block_0_": { + "name": "بلوک میانی.۰." + }, + "middle_block_1_": { + "name": "بلوک میانی.۱." + }, + "middle_block_2_": { + "name": "بلوک میانی.۲." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "out_": { + "name": "خروجی." + }, + "output_blocks_0_": { + "name": "خروجی بلوک‌ها.۰." + }, + "output_blocks_10_": { + "name": "خروجی بلوک‌ها.۱۰." + }, + "output_blocks_11_": { + "name": "خروجی بلوک‌ها.۱۱." + }, + "output_blocks_1_": { + "name": "خروجی بلوک‌ها.۱." + }, + "output_blocks_2_": { + "name": "خروجی بلوک‌ها.۲." + }, + "output_blocks_3_": { + "name": "خروجی بلوک‌ها.۳." + }, + "output_blocks_4_": { + "name": "خروجی بلوک‌ها.۴." + }, + "output_blocks_5_": { + "name": "خروجی بلوک‌ها.۵." + }, + "output_blocks_6_": { + "name": "خروجی بلوک‌ها.۶." + }, + "output_blocks_7_": { + "name": "خروجی بلوک‌ها.۷." + }, + "output_blocks_8_": { + "name": "خروجی بلوک‌ها.۸." + }, + "output_blocks_9_": { + "name": "خروجی بلوک‌ها.۹." + }, + "time_embed_": { + "name": "time_embed." + } + } + }, + "ModelMergeSD2": { + "display_name": "ModelMergeSD2", + "inputs": { + "input_blocks_0_": { + "name": "بلوک‌های ورودی ۰." + }, + "input_blocks_10_": { + "name": "بلوک‌های ورودی ۱۰." + }, + "input_blocks_11_": { + "name": "بلوک‌های ورودی ۱۱." + }, + "input_blocks_1_": { + "name": "بلوک‌های ورودی ۱." + }, + "input_blocks_2_": { + "name": "بلوک‌های ورودی ۲." + }, + "input_blocks_3_": { + "name": "بلوک‌های ورودی ۳." + }, + "input_blocks_4_": { + "name": "بلوک‌های ورودی ۴." + }, + "input_blocks_5_": { + "name": "بلوک‌های ورودی ۵." + }, + "input_blocks_6_": { + "name": "بلوک‌های ورودی ۶." + }, + "input_blocks_7_": { + "name": "بلوک‌های ورودی ۷." + }, + "input_blocks_8_": { + "name": "بلوک‌های ورودی ۸." + }, + "input_blocks_9_": { + "name": "بلوک‌های ورودی ۹." + }, + "label_emb_": { + "name": "label_emb." + }, + "middle_block_0_": { + "name": "بلوک میانی ۰." + }, + "middle_block_1_": { + "name": "بلوک میانی ۱." + }, + "middle_block_2_": { + "name": "بلوک میانی ۲." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "out_": { + "name": "خروجی." + }, + "output_blocks_0_": { + "name": "بلوک‌های خروجی ۰." + }, + "output_blocks_10_": { + "name": "بلوک‌های خروجی ۱۰." + }, + "output_blocks_11_": { + "name": "بلوک‌های خروجی ۱۱." + }, + "output_blocks_1_": { + "name": "بلوک‌های خروجی ۱." + }, + "output_blocks_2_": { + "name": "بلوک‌های خروجی ۲." + }, + "output_blocks_3_": { + "name": "بلوک‌های خروجی ۳." + }, + "output_blocks_4_": { + "name": "بلوک‌های خروجی ۴." + }, + "output_blocks_5_": { + "name": "بلوک‌های خروجی ۵." + }, + "output_blocks_6_": { + "name": "بلوک‌های خروجی ۶." + }, + "output_blocks_7_": { + "name": "بلوک‌های خروجی ۷." + }, + "output_blocks_8_": { + "name": "بلوک‌های خروجی ۸." + }, + "output_blocks_9_": { + "name": "بلوک‌های خروجی ۹." + }, + "time_embed_": { + "name": "time_embed." + } + } + }, + "ModelMergeSD35_Large": { + "display_name": "ModelMergeSD35_Large", + "inputs": { + "context_embedder_": { + "name": "context_embedder." + }, + "final_layer_": { + "name": "final_layer." + }, + "joint_blocks_0_": { + "name": "joint_blocks.۰." + }, + "joint_blocks_10_": { + "name": "joint_blocks.۱۰." + }, + "joint_blocks_11_": { + "name": "joint_blocks.۱۱." + }, + "joint_blocks_12_": { + "name": "joint_blocks.۱۲." + }, + "joint_blocks_13_": { + "name": "joint_blocks.۱۳." + }, + "joint_blocks_14_": { + "name": "joint_blocks.۱۴." + }, + "joint_blocks_15_": { + "name": "joint_blocks.۱۵." + }, + "joint_blocks_16_": { + "name": "joint_blocks.۱۶." + }, + "joint_blocks_17_": { + "name": "joint_blocks.۱۷." + }, + "joint_blocks_18_": { + "name": "joint_blocks.۱۸." + }, + "joint_blocks_19_": { + "name": "joint_blocks.۱۹." + }, + "joint_blocks_1_": { + "name": "joint_blocks.۱." + }, + "joint_blocks_20_": { + "name": "joint_blocks.۲۰." + }, + "joint_blocks_21_": { + "name": "joint_blocks.۲۱." + }, + "joint_blocks_22_": { + "name": "joint_blocks.۲۲." + }, + "joint_blocks_23_": { + "name": "joint_blocks.۲۳." + }, + "joint_blocks_24_": { + "name": "joint_blocks.۲۴." + }, + "joint_blocks_25_": { + "name": "joint_blocks.۲۵." + }, + "joint_blocks_26_": { + "name": "joint_blocks.۲۶." + }, + "joint_blocks_27_": { + "name": "joint_blocks.۲۷." + }, + "joint_blocks_28_": { + "name": "joint_blocks.۲۸." + }, + "joint_blocks_29_": { + "name": "joint_blocks.۲۹." + }, + "joint_blocks_2_": { + "name": "joint_blocks.۲." + }, + "joint_blocks_30_": { + "name": "joint_blocks.۳۰." + }, + "joint_blocks_31_": { + "name": "joint_blocks.۳۱." + }, + "joint_blocks_32_": { + "name": "joint_blocks.۳۲." + }, + "joint_blocks_33_": { + "name": "joint_blocks.۳۳." + }, + "joint_blocks_34_": { + "name": "joint_blocks.۳۴." + }, + "joint_blocks_35_": { + "name": "joint_blocks.۳۵." + }, + "joint_blocks_36_": { + "name": "joint_blocks.۳۶." + }, + "joint_blocks_37_": { + "name": "joint_blocks.۳۷." + }, + "joint_blocks_3_": { + "name": "joint_blocks.۳." + }, + "joint_blocks_4_": { + "name": "joint_blocks.۴." + }, + "joint_blocks_5_": { + "name": "joint_blocks.۵." + }, + "joint_blocks_6_": { + "name": "joint_blocks.۶." + }, + "joint_blocks_7_": { + "name": "joint_blocks.۷." + }, + "joint_blocks_8_": { + "name": "joint_blocks.۸." + }, + "joint_blocks_9_": { + "name": "joint_blocks.۹." + }, + "model1": { + "name": "مدل۱" + }, + "model2": { + "name": "مدل۲" + }, + "pos_embed_": { + "name": "pos_embed." + }, + "t_embedder_": { + "name": "t_embedder." + }, + "x_embedder_": { + "name": "x_embedder." + }, + "y_embedder_": { + "name": "y_embedder." + } + } + }, + "ModelMergeSD3_2B": { + "display_name": "ModelMergeSD3_2B", + "inputs": { + "context_embedder_": { + "name": "context_embedder." + }, + "final_layer_": { + "name": "final_layer." + }, + "joint_blocks_0_": { + "name": "joint_blocks.۰." + }, + "joint_blocks_10_": { + "name": "joint_blocks.۱۰." + }, + "joint_blocks_11_": { + "name": "joint_blocks.۱۱." + }, + "joint_blocks_12_": { + "name": "joint_blocks.۱۲." + }, + "joint_blocks_13_": { + "name": "joint_blocks.۱۳." + }, + "joint_blocks_14_": { + "name": "joint_blocks.۱۴." + }, + "joint_blocks_15_": { + "name": "joint_blocks.۱۵." + }, + "joint_blocks_16_": { + "name": "joint_blocks.۱۶." + }, + "joint_blocks_17_": { + "name": "joint_blocks.۱۷." + }, + "joint_blocks_18_": { + "name": "joint_blocks.۱۸." + }, + "joint_blocks_19_": { + "name": "joint_blocks.۱۹." + }, + "joint_blocks_1_": { + "name": "joint_blocks.۱." + }, + "joint_blocks_20_": { + "name": "joint_blocks.۲۰." + }, + "joint_blocks_21_": { + "name": "joint_blocks.۲۱." + }, + "joint_blocks_22_": { + "name": "joint_blocks.۲۲." + }, + "joint_blocks_23_": { + "name": "joint_blocks.۲۳." + }, + "joint_blocks_2_": { + "name": "joint_blocks.۲." + }, + "joint_blocks_3_": { + "name": "joint_blocks.۳." + }, + "joint_blocks_4_": { + "name": "joint_blocks.۴." + }, + "joint_blocks_5_": { + "name": "joint_blocks.۵." + }, + "joint_blocks_6_": { + "name": "joint_blocks.۶." + }, + "joint_blocks_7_": { + "name": "joint_blocks.۷." + }, + "joint_blocks_8_": { + "name": "joint_blocks.۸." + }, + "joint_blocks_9_": { + "name": "joint_blocks.۹." + }, + "model1": { + "name": "مدل۱" + }, + "model2": { + "name": "مدل۲" + }, + "pos_embed_": { + "name": "pos_embed." + }, + "t_embedder_": { + "name": "t_embedder." + }, + "x_embedder_": { + "name": "x_embedder." + }, + "y_embedder_": { + "name": "y_embedder." + } + } + }, + "ModelMergeSDXL": { + "display_name": "ModelMergeSDXL", + "inputs": { + "input_blocks_0": { + "name": "input_blocks.۰" + }, + "input_blocks_1": { + "name": "input_blocks.۱" + }, + "input_blocks_2": { + "name": "input_blocks.۲" + }, + "input_blocks_3": { + "name": "input_blocks.۳" + }, + "input_blocks_4": { + "name": "input_blocks.۴" + }, + "input_blocks_5": { + "name": "input_blocks.۵" + }, + "input_blocks_6": { + "name": "input_blocks.۶" + }, + "input_blocks_7": { + "name": "input_blocks.۷" + }, + "input_blocks_8": { + "name": "input_blocks.۸" + }, + "label_emb_": { + "name": "label_emb." + }, + "middle_block_0": { + "name": "middle_block.۰" + }, + "middle_block_1": { + "name": "middle_block.۱" + }, + "middle_block_2": { + "name": "middle_block.۲" + }, + "model1": { + "name": "مدل۱" + }, + "model2": { + "name": "مدل۲" + }, + "out_": { + "name": "out." + }, + "output_blocks_0": { + "name": "output_blocks.۰" + }, + "output_blocks_1": { + "name": "output_blocks.۱" + }, + "output_blocks_2": { + "name": "output_blocks.۲" + }, + "output_blocks_3": { + "name": "output_blocks.۳" + }, + "output_blocks_4": { + "name": "output_blocks.۴" + }, + "output_blocks_5": { + "name": "output_blocks.۵" + }, + "output_blocks_6": { + "name": "output_blocks.۶" + }, + "output_blocks_7": { + "name": "output_blocks.۷" + }, + "output_blocks_8": { + "name": "output_blocks.۸" + }, + "time_embed_": { + "name": "time_embed." + } + } + }, + "ModelMergeSimple": { + "display_name": "ModelMergeSimple", + "inputs": { + "model1": { + "name": "مدل۱" + }, + "model2": { + "name": "مدل۲" + }, + "ratio": { + "name": "نسبت" + } + } + }, + "ModelMergeSubtract": { + "display_name": "ModelMergeSubtract", + "inputs": { + "model1": { + "name": "مدل۱" + }, + "model2": { + "name": "مدل۲" + }, + "multiplier": { + "name": "ضریب" + } + } + }, + "ModelMergeWAN2_1": { + "description": "مدل ۱.۳B دارای ۳۰ بلاک است، مدل ۱۴B دارای ۴۰ بلاک است. مدل تصویر به ویدیو دارای img_emb اضافی است.", + "display_name": "ModelMergeWAN2_1", + "inputs": { + "blocks_0_": { + "name": "blocks.0." + }, + "blocks_10_": { + "name": "blocks.10." + }, + "blocks_11_": { + "name": "blocks.11." + }, + "blocks_12_": { + "name": "blocks.12." + }, + "blocks_13_": { + "name": "blocks.13." + }, + "blocks_14_": { + "name": "blocks.14." + }, + "blocks_15_": { + "name": "blocks.15." + }, + "blocks_16_": { + "name": "blocks.16." + }, + "blocks_17_": { + "name": "blocks.17." + }, + "blocks_18_": { + "name": "blocks.18." + }, + "blocks_19_": { + "name": "blocks.19." + }, + "blocks_1_": { + "name": "blocks.1." + }, + "blocks_20_": { + "name": "blocks.20." + }, + "blocks_21_": { + "name": "blocks.21." + }, + "blocks_22_": { + "name": "blocks.22." + }, + "blocks_23_": { + "name": "blocks.23." + }, + "blocks_24_": { + "name": "blocks.24." + }, + "blocks_25_": { + "name": "blocks.25." + }, + "blocks_26_": { + "name": "blocks.26." + }, + "blocks_27_": { + "name": "blocks.27." + }, + "blocks_28_": { + "name": "blocks.28." + }, + "blocks_29_": { + "name": "blocks.29." + }, + "blocks_2_": { + "name": "blocks.2." + }, + "blocks_30_": { + "name": "blocks.30." + }, + "blocks_31_": { + "name": "blocks.31." + }, + "blocks_32_": { + "name": "blocks.32." + }, + "blocks_33_": { + "name": "blocks.33." + }, + "blocks_34_": { + "name": "blocks.34." + }, + "blocks_35_": { + "name": "blocks.35." + }, + "blocks_36_": { + "name": "blocks.36." + }, + "blocks_37_": { + "name": "blocks.37." + }, + "blocks_38_": { + "name": "blocks.38." + }, + "blocks_39_": { + "name": "blocks.39." + }, + "blocks_3_": { + "name": "blocks.3." + }, + "blocks_4_": { + "name": "blocks.4." + }, + "blocks_5_": { + "name": "blocks.5." + }, + "blocks_6_": { + "name": "blocks.6." + }, + "blocks_7_": { + "name": "blocks.7." + }, + "blocks_8_": { + "name": "blocks.8." + }, + "blocks_9_": { + "name": "blocks.9." + }, + "head_": { + "name": "head." + }, + "img_emb_": { + "name": "img_emb." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "patch_embedding_": { + "name": "patch_embedding." + }, + "text_embedding_": { + "name": "text_embedding." + }, + "time_embedding_": { + "name": "time_embedding." + }, + "time_projection_": { + "name": "time_projection." + } + } + }, + "ModelPatchLoader": { + "display_name": "ModelPatchLoader", + "inputs": { + "name": { + "name": "نام" + } + } + }, + "ModelSamplingAuraFlow": { + "display_name": "ModelSamplingAuraFlow", + "inputs": { + "model": { + "name": "مدل" + }, + "shift": { + "name": "شیفت" + } + } + }, + "ModelSamplingContinuousEDM": { + "display_name": "ModelSamplingContinuousEDM", + "inputs": { + "model": { + "name": "مدل" + }, + "sampling": { + "name": "نمونه‌برداری" + }, + "sigma_max": { + "name": "سیگما بیشینه" + }, + "sigma_min": { + "name": "سیگما کمینه" + } + } + }, + "ModelSamplingContinuousV": { + "display_name": "ModelSamplingContinuousV", + "inputs": { + "model": { + "name": "مدل" + }, + "sampling": { + "name": "نمونه‌برداری" + }, + "sigma_max": { + "name": "سیگما بیشینه" + }, + "sigma_min": { + "name": "سیگما کمینه" + } + } + }, + "ModelSamplingDiscrete": { + "display_name": "ModelSamplingDiscrete", + "inputs": { + "model": { + "name": "مدل" + }, + "sampling": { + "name": "نمونه‌برداری" + }, + "zsnr": { + "name": "zsnr" + } + } + }, + "ModelSamplingFlux": { + "display_name": "ModelSamplingFlux", + "inputs": { + "base_shift": { + "name": "شیفت پایه" + }, + "height": { + "name": "ارتفاع" + }, + "max_shift": { + "name": "بیشینه شیفت" + }, + "model": { + "name": "مدل" + }, + "width": { + "name": "عرض" + } + } + }, + "ModelSamplingLTXV": { + "display_name": "ModelSamplingLTXV", + "inputs": { + "base_shift": { + "name": "شیفت پایه" + }, + "latent": { + "name": "latent" + }, + "max_shift": { + "name": "بیشینه شیفت" + }, + "model": { + "name": "مدل" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ModelSamplingSD3": { + "display_name": "ModelSamplingSD3", + "inputs": { + "model": { + "name": "مدل" + }, + "shift": { + "name": "شیفت" + } + } + }, + "ModelSamplingStableCascade": { + "display_name": "ModelSamplingStableCascade", + "inputs": { + "model": { + "name": "مدل" + }, + "shift": { + "name": "شیفت" + } + } + }, + "ModelSave": { + "display_name": "ModelSave", + "inputs": { + "filename_prefix": { + "name": "پیشوند نام فایل" + }, + "model": { + "name": "مدل" + } + } + }, + "MoonvalleyImg2VideoNode": { + "description": "گره تبدیل تصویر به ویدیو Moonvalley Marey", + "display_name": "Moonvalley Marey Image to Video", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "image": { + "name": "تصویر", + "tooltip": "تصویر مرجع برای تولید ویدیو استفاده می‌شود" + }, + "negative_prompt": { + "name": "پرامپت منفی", + "tooltip": "متن پرامپت منفی" + }, + "prompt": { + "name": "پرامپت" + }, + "prompt_adherence": { + "name": "پایبندی به پرامپت", + "tooltip": "مقیاس راهنمایی برای کنترل تولید" + }, + "resolution": { + "name": "وضوح", + "tooltip": "وضوح ویدیوی خروجی" + }, + "seed": { + "name": "بذر", + "tooltip": "مقدار بذر تصادفی" + }, + "steps": { + "name": "گام‌ها", + "tooltip": "تعداد گام‌های حذف نویز" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MoonvalleyTxt2VideoNode": { + "display_name": "Moonvalley Marey تبدیل متن به ویدیو", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "متن پرامپت منفی" + }, + "prompt": { + "name": "prompt" + }, + "prompt_adherence": { + "name": "prompt_adherence", + "tooltip": "مقیاس راهنمایی برای کنترل تولید" + }, + "resolution": { + "name": "resolution", + "tooltip": "وضوح ویدیوی خروجی" + }, + "seed": { + "name": "seed", + "tooltip": "مقدار seed تصادفی" + }, + "steps": { + "name": "steps", + "tooltip": "گام‌های استنتاج" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MoonvalleyVideo2VideoNode": { + "display_name": "Moonvalley Marey تبدیل ویدیو به ویدیو", + "inputs": { + "control_type": { + "name": "control_type" + }, + "motion_intensity": { + "name": "motion_intensity", + "tooltip": "فقط در صورتی استفاده می‌شود که control_type برابر با 'Motion Transfer' باشد" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "متن پرامپت منفی" + }, + "prompt": { + "name": "prompt", + "tooltip": "توضیح ویدیوی مورد نظر برای تولید" + }, + "seed": { + "name": "seed", + "tooltip": "مقدار seed تصادفی" + }, + "steps": { + "name": "steps", + "tooltip": "تعداد گام‌های استنتاج" + }, + "video": { + "name": "video", + "tooltip": "ویدیوی مرجع برای تولید ویدیوی خروجی. باید حداقل ۵ ثانیه باشد. ویدیوهای طولانی‌تر از ۵ ثانیه به‌صورت خودکار کوتاه می‌شوند. فقط فرمت MP4 پشتیبانی می‌شود." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Morphology": { + "display_name": "ImageMorphology", + "inputs": { + "image": { + "name": "image" + }, + "kernel_size": { + "name": "kernel_size" + }, + "operation": { + "name": "operation" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "NormalizeImages": { + "display_name": "نرمال‌سازی تصاویر", + "inputs": { + "images": { + "name": "images", + "tooltip": "تصویر برای پردازش." + }, + "mean": { + "name": "mean", + "tooltip": "مقدار میانگین برای نرمال‌سازی." + }, + "std": { + "name": "std", + "tooltip": "انحراف معیار برای نرمال‌سازی." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "تصاویر پردازش‌شده" + } + } + }, + "NormalizeVideoLatentStart": { + "description": "فریم‌های ابتدایی یک latent ویدیویی را نرمال‌سازی می‌کند تا میانگین و انحراف معیار آن‌ها با فریم‌های مرجع بعدی مطابقت داشته باشد. به کاهش تفاوت بین فریم‌های ابتدایی و سایر فریم‌های ویدیو کمک می‌کند.", + "display_name": "نرمال‌سازی شروع نهفته ویدیو", + "inputs": { + "latent": { + "name": "latent" + }, + "reference_frame_count": { + "name": "reference_frame_count", + "tooltip": "تعداد فریم‌های نهفته پس از فریم‌های ابتدایی که به عنوان مرجع استفاده می‌شوند" + }, + "start_frame_count": { + "name": "start_frame_count", + "tooltip": "تعداد فریم‌های نهفته برای نرمال‌سازی (از ابتدا شمارش می‌شود)" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, + "OpenAIChatConfig": { + "description": "امکان تعیین گزینه‌های پیکربندی پیشرفته برای OpenAI Chat Nodeها را فراهم می‌کند.", + "display_name": "تنظیمات پیشرفته OpenAI ChatGPT", + "inputs": { + "instructions": { + "name": "instructions", + "tooltip": "دستورالعمل‌هایی برای مدل درباره نحوه تولید پاسخ" + }, + "max_output_tokens": { + "name": "max_output_tokens", + "tooltip": "حد بالایی برای تعداد توکن‌هایی که می‌توانند برای یک پاسخ تولید شوند، شامل توکن‌های خروجی قابل مشاهده" + }, + "truncation": { + "name": "truncation", + "tooltip": "استراتژی کوتاه‌سازی برای پاسخ مدل. auto: اگر زمینه این پاسخ و پاسخ‌های قبلی از اندازه پنجره زمینه مدل بیشتر شود، مدل پاسخ را با حذف آیتم‌های ورودی در میانه گفتگو کوتاه می‌کند تا در پنجره زمینه جا بگیرد. disabled: اگر پاسخ مدل از اندازه پنجره زمینه مدل بیشتر شود، درخواست با خطای ۴۰۰ شکست می‌خورد." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "OpenAIChatNode": { + "description": "تولید پاسخ متنی از یک مدل OpenAI.", + "display_name": "OpenAI ChatGPT", + "inputs": { + "advanced_options": { + "name": "advanced_options", + "tooltip": "پیکربندی اختیاری برای مدل. ورودی‌ها را از node OpenAI Chat Advanced Options می‌پذیرد." + }, + "files": { + "name": "files", + "tooltip": "فایل(ها)ی اختیاری برای استفاده به عنوان زمینه مدل. ورودی‌ها را از node OpenAI Chat Input Files می‌پذیرد." + }, + "images": { + "name": "images", + "tooltip": "تصویر(ها)ی اختیاری برای استفاده به عنوان زمینه مدل. برای افزودن چند تصویر می‌توانید از node Batch Images استفاده کنید." + }, + "model": { + "name": "model", + "tooltip": "مدلی که برای تولید پاسخ استفاده می‌شود" + }, + "persist_context": { + "name": "persist_context", + "tooltip": "این پارامتر منسوخ شده و اثری ندارد." + }, + "prompt": { + "name": "prompt", + "tooltip": "ورودی متنی به مدل، برای تولید پاسخ استفاده می‌شود." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "OpenAIDalle2": { + "description": "تولید تصویر به صورت همزمان از طریق نقطه پایانی DALL·E 2 شرکت OpenAI.", + "display_name": "OpenAI DALL·E 2", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "image": { + "name": "image", + "tooltip": "تصویر مرجع اختیاری برای ویرایش تصویر." + }, + "mask": { + "name": "mask", + "tooltip": "ماسک اختیاری برای inpainting (ناحیه‌های سفید جایگزین خواهند شد)" + }, + "n": { + "name": "n", + "tooltip": "تعداد تصاویری که باید تولید شود" + }, + "prompt": { + "name": "prompt", + "tooltip": "پرامپت متنی برای DALL·E" + }, + "seed": { + "name": "seed", + "tooltip": "هنوز در backend پیاده‌سازی نشده است" + }, + "size": { + "name": "size", + "tooltip": "اندازه تصویر" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "OpenAIDalle3": { + "description": "تولید تصویر به صورت همزمان از طریق نقطه پایانی DALL·E 3 شرکت OpenAI.", + "display_name": "OpenAI DALL·E 3", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "prompt": { + "name": "prompt", + "tooltip": "متن راهنما برای DALL·E" + }, + "quality": { + "name": "quality", + "tooltip": "کیفیت تصویر" + }, + "seed": { + "name": "seed", + "tooltip": "هنوز در backend پیاده‌سازی نشده است" + }, + "size": { + "name": "size", + "tooltip": "اندازه تصویر" + }, + "style": { + "name": "style", + "tooltip": "گزینه Vivid مدل را به سمت تولید تصاویر فوق‌واقعی و دراماتیک سوق می‌دهد. گزینه Natural باعث تولید تصاویر طبیعی‌تر و کمتر فوق‌واقعی می‌شود." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "OpenAIGPTImage1": { + "description": "تولید تصویر به صورت همزمان از طریق نقطه پایانی GPT Image 1 شرکت OpenAI.", + "display_name": "OpenAI GPT Image 1", + "inputs": { + "background": { + "name": "background", + "tooltip": "بازگرداندن تصویر با یا بدون پس‌زمینه" + }, + "control_after_generate": { + "name": "control after generate" + }, + "image": { + "name": "image", + "tooltip": "تصویر مرجع اختیاری برای ویرایش تصویر." + }, + "mask": { + "name": "mask", + "tooltip": "ماسک اختیاری برای inpainting (ناحیه‌های سفید جایگزین خواهند شد)" + }, + "model": { + "name": "model" + }, + "n": { + "name": "n", + "tooltip": "تعداد تصاویری که باید تولید شود" + }, + "prompt": { + "name": "prompt", + "tooltip": "متن راهنما برای تصویر GPT" + }, + "quality": { + "name": "quality", + "tooltip": "کیفیت تصویر، بر هزینه و زمان تولید تأثیر می‌گذارد." + }, + "seed": { + "name": "seed", + "tooltip": "هنوز در backend پیاده‌سازی نشده است" + }, + "size": { + "name": "size", + "tooltip": "اندازه تصویر" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "OpenAIInputFiles": { + "description": "بارگذاری و آماده‌سازی فایل‌های ورودی (متن، PDF و غیره) برای استفاده به عنوان ورودی در Node چت OpenAI. این فایل‌ها هنگام تولید پاسخ توسط مدل OpenAI خوانده می‌شوند. 🛈 نکته: می‌توان این node را با سایر nodeهای فایل ورودی OpenAI زنجیره کرد.", + "display_name": "فایل‌های ورودی OpenAI ChatGPT", + "inputs": { + "OPENAI_INPUT_FILES": { + "name": "OPENAI_INPUT_FILES", + "tooltip": "فایل(های) اضافی اختیاری برای دسته‌بندی با فایل بارگذاری‌شده از این node. امکان زنجیره‌سازی فایل‌های ورودی را فراهم می‌کند تا یک پیام بتواند شامل چندین فایل ورودی باشد." + }, + "file": { + "name": "file", + "tooltip": "فایل‌های ورودی برای استفاده به عنوان زمینه برای مدل. در حال حاضر فقط فایل‌های متنی (.txt) و PDF (.pdf) پذیرفته می‌شوند." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "OpenAIVideoSora2": { + "description": "تولید ویدیو و صوت توسط OpenAI.", + "display_name": "OpenAI Sora - ویدیو", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "duration": { + "name": "duration" + }, + "image": { + "name": "image" + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "متن راهنما؛ در صورت وجود تصویر ورودی می‌تواند خالی باشد." + }, + "seed": { + "name": "seed", + "tooltip": "Seed برای تعیین اینکه node باید مجدداً اجرا شود یا خیر؛ نتایج واقعی صرف‌نظر از seed غیرقطعی هستند." + }, + "size": { + "name": "size" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "OptimalStepsScheduler": { + "display_name": "OptimalStepsScheduler", + "inputs": { + "denoise": { + "name": "کاهش نویز" + }, + "model_type": { + "name": "model_type" + }, + "steps": { + "name": "گام‌ها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PairConditioningCombine": { + "display_name": "ترکیب جفت شرطی", + "inputs": { + "negative_A": { + "name": "منفی_A" + }, + "negative_B": { + "name": "منفی_B" + }, + "positive_A": { + "name": "مثبت_A" + }, + "positive_B": { + "name": "مثبت_B" + } + }, + "outputs": { + "0": { + "name": "مثبت" + }, + "1": { + "name": "منفی" + } + } + }, + "PairConditioningSetDefaultCombine": { + "display_name": "تنظیم پیش‌فرض ترکیب جفت شرطی", + "inputs": { + "hooks": { + "name": "hooks" + }, + "negative": { + "name": "منفی" + }, + "negative_DEFAULT": { + "name": "منفی_پیش‌فرض" + }, + "positive": { + "name": "مثبت" + }, + "positive_DEFAULT": { + "name": "مثبت_پیش‌فرض" + } + }, + "outputs": { + "0": { + "name": "مثبت" + }, + "1": { + "name": "منفی" + } + } + }, + "PairConditioningSetProperties": { + "display_name": "تنظیم ویژگی‌های جفت شرطی", + "inputs": { + "hooks": { + "name": "hooks" + }, + "mask": { + "name": "ماسک" + }, + "negative_NEW": { + "name": "منفی_جدید" + }, + "positive_NEW": { + "name": "مثبت_جدید" + }, + "set_cond_area": { + "name": "تعیین ناحیه شرطی" + }, + "strength": { + "name": "شدت" + }, + "timesteps": { + "name": "گام‌های زمانی" + } + }, + "outputs": { + "0": { + "name": "مثبت" + }, + "1": { + "name": "منفی" + } + } + }, + "PairConditioningSetPropertiesAndCombine": { + "display_name": "تنظیم ویژگی و ترکیب جفت شرطی", + "inputs": { + "hooks": { + "name": "hooks" + }, + "mask": { + "name": "ماسک" + }, + "negative": { + "name": "منفی" + }, + "negative_NEW": { + "name": "منفی_جدید" + }, + "positive": { + "name": "مثبت" + }, + "positive_NEW": { + "name": "مثبت_جدید" + }, + "set_cond_area": { + "name": "تعیین ناحیه شرطی" + }, + "strength": { + "name": "شدت" + }, + "timesteps": { + "name": "گام‌های زمانی" + } + }, + "outputs": { + "0": { + "name": "مثبت" + }, + "1": { + "name": "منفی" + } + } + }, + "PatchModelAddDownscale": { + "display_name": "PatchModelAddDownscale (Kohya Deep Shrink)", + "inputs": { + "block_number": { + "name": "شماره بلاک" + }, + "downscale_after_skip": { + "name": "کاهش ابعاد پس از پرش" + }, + "downscale_factor": { + "name": "ضریب کاهش ابعاد" + }, + "downscale_method": { + "name": "روش کاهش ابعاد" + }, + "end_percent": { + "name": "درصد پایان" + }, + "model": { + "name": "مدل" + }, + "start_percent": { + "name": "درصد شروع" + }, + "upscale_method": { + "name": "روش افزایش ابعاد" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PerpNeg": { + "display_name": "Perp-Neg (منسوخ شده توسط PerpNegGuider)", + "inputs": { + "empty_conditioning": { + "name": "شرط‌گذاری خالی" + }, + "model": { + "name": "مدل" + }, + "neg_scale": { + "name": "مقیاس منفی" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PerpNegGuider": { + "display_name": "PerpNegGuider", + "inputs": { + "cfg": { + "name": "cfg" + }, + "empty_conditioning": { + "name": "شرط‌گذاری خالی" + }, + "model": { + "name": "مدل" + }, + "neg_scale": { + "name": "مقیاس منفی" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PerturbedAttentionGuidance": { + "display_name": "PerturbedAttentionGuidance", + "inputs": { + "model": { + "name": "مدل" + }, + "scale": { + "name": "مقیاس" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PhotoMakerEncode": { + "display_name": "PhotoMakerEncode", + "inputs": { + "clip": { + "name": "clip" + }, + "image": { + "name": "تصویر" + }, + "photomaker": { + "name": "photomaker" + }, + "text": { + "name": "متن" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PhotoMakerLoader": { + "display_name": "PhotoMakerLoader", + "inputs": { + "photomaker_model_name": { + "name": "نام مدل photomaker" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PixverseImageToVideoNode": { + "description": "تولید ویدیو بر اساس پرامپت و اندازه خروجی.", + "display_name": "تبدیل تصویر به ویدیو PixVerse", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration_seconds": { + "name": "مدت (ثانیه)" + }, + "image": { + "name": "تصویر" + }, + "motion_mode": { + "name": "حالت حرکت" + }, + "negative_prompt": { + "name": "پرامپت منفی", + "tooltip": "توضیح متنی اختیاری برای عناصر نامطلوب در تصویر." + }, + "pixverse_template": { + "name": "قالب PixVerse", + "tooltip": "قالب اختیاری برای تأثیرگذاری بر سبک تولید، ایجاد شده توسط نود PixVerse Template." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت برای تولید ویدیو" + }, + "quality": { + "name": "کیفیت" + }, + "seed": { + "name": "بذر", + "tooltip": "بذر برای تولید ویدیو." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PixverseTemplateNode": { + "display_name": "قالب PixVerse", + "inputs": { + "template": { + "name": "قالب" + } + }, + "outputs": { + "0": { + "name": "قالب PixVerse", + "tooltip": null + } + } + }, + "PixverseTextToVideoNode": { + "description": "تولید ویدیو بر اساس پرامپت و اندازه خروجی.", + "display_name": "تبدیل متن به ویدیو PixVerse", + "inputs": { + "aspect_ratio": { + "name": "نسبت تصویر" + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration_seconds": { + "name": "مدت زمان (ثانیه)" + }, + "motion_mode": { + "name": "حالت حرکت" + }, + "negative_prompt": { + "name": "پرامپت منفی", + "tooltip": "توضیح متنی اختیاری برای عناصر ناخواسته در تصویر." + }, + "pixverse_template": { + "name": "قالب PixVerse", + "tooltip": "قالب اختیاری برای تأثیرگذاری بر سبک تولید، ایجاد شده توسط node قالب PixVerse." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت برای تولید ویدیو" + }, + "quality": { + "name": "کیفیت" + }, + "seed": { + "name": "seed", + "tooltip": "seed برای تولید ویدیو." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PixverseTransitionVideoNode": { + "description": "تولید ویدیو بر اساس پرامپت و اندازه خروجی.", + "display_name": "ویدیوی انتقالی PixVerse", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration_seconds": { + "name": "مدت زمان (ثانیه)" + }, + "first_frame": { + "name": "فریم اول" + }, + "last_frame": { + "name": "فریم آخر" + }, + "motion_mode": { + "name": "حالت حرکت" + }, + "negative_prompt": { + "name": "پرامپت منفی", + "tooltip": "توضیح متنی اختیاری برای عناصر ناخواسته در تصویر." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت برای تولید ویدیو" + }, + "quality": { + "name": "کیفیت" + }, + "seed": { + "name": "seed", + "tooltip": "seed برای تولید ویدیو." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PolyexponentialScheduler": { + "display_name": "PolyexponentialScheduler", + "inputs": { + "rho": { + "name": "رو" + }, + "sigma_max": { + "name": "سیگما ماکس" + }, + "sigma_min": { + "name": "سیگما مین" + }, + "steps": { + "name": "گام‌ها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PorterDuffImageComposite": { + "display_name": "ترکیب تصویر Porter-Duff", + "inputs": { + "destination": { + "name": "مقصد" + }, + "destination_alpha": { + "name": "آلفای مقصد" + }, + "mode": { + "name": "حالت" + }, + "source": { + "name": "سورس" + }, + "source_alpha": { + "name": "آلفای سورس" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "tooltip": null + } + } + }, + "Preview3D": { + "display_name": "پیش‌نمایش سه‌بعدی و انیمیشن", + "inputs": { + "bg_image": { + "name": "تصویر پس‌زمینه" + }, + "camera_info": { + "name": "اطلاعات دوربین" + }, + "image": { + "name": "تصویر" + }, + "model_file": { + "name": "فایل مدل" + } + } + }, + "PreviewAny": { + "display_name": "پیش‌نمایش به صورت متن", + "inputs": { + "preview": { + }, + "previewMode": { + }, + "source": { + "name": "سورس" + } + } + }, + "PreviewAudio": { + "display_name": "پیش‌نمایش صوت", + "inputs": { + "audio": { + "name": "صوت" + }, + "audioUI": { + "name": "رابط کاربری صوت" + } + } + }, + "PreviewImage": { + "description": "تصاویر ورودی را در پوشه خروجی ComfyUI شما ذخیره می‌کند.", + "display_name": "پیش‌نمایش تصویر", + "inputs": { + "images": { + "name": "تصاویر" + } + } + }, + "PrimitiveBoolean": { + "display_name": "بولین", + "inputs": { + "value": { + "name": "مقدار" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PrimitiveFloat": { + "display_name": "عدد اعشاری", + "inputs": { + "value": { + "name": "مقدار" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PrimitiveInt": { + "display_name": "عدد صحیح", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "value": { + "name": "مقدار" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PrimitiveString": { + "display_name": "رشته", + "inputs": { + "value": { + "name": "مقدار" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PrimitiveStringMultiline": { + "display_name": "رشته (چند خطی)", + "inputs": { + "value": { + "name": "مقدار" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "QuadrupleCLIPLoader": { + "description": "[دستورالعمل‌ها]\n\nhidream: long clip-l, long clip-g, t5xxl, llama_8b_3.1_instruct", + "display_name": "QuadrupleCLIPLoader", + "inputs": { + "clip_name1": { + "name": "clip_name1" + }, + "clip_name2": { + "name": "clip_name2" + }, + "clip_name3": { + "name": "clip_name3" + }, + "clip_name4": { + "name": "clip_name4" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "QwenImageDiffsynthControlnet": { + "display_name": "QwenImageDiffsynthControlnet", + "inputs": { + "image": { + "name": "تصویر" + }, + "mask": { + "name": "ماسک" + }, + "model": { + "name": "مدل" + }, + "model_patch": { + "name": "patch مدل" + }, + "strength": { + "name": "شدت" + }, + "vae": { + "name": "vae" + } + } + }, + "RandomCropImages": { + "display_name": "برش تصادفی تصاویر", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "height": { + "name": "ارتفاع", + "tooltip": "ارتفاع برش." + }, + "images": { + "name": "تصاویر", + "tooltip": "تصویر برای پردازش." + }, + "seed": { + "name": "بذر تصادفی", + "tooltip": "بذر تصادفی." + }, + "width": { + "name": "عرض", + "tooltip": "عرض برش." + } + }, + "outputs": { + "0": { + "name": "تصاویر", + "tooltip": "تصاویر پردازش‌شده" + } + } + }, + "RandomNoise": { + "display_name": "نویز تصادفی", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "noise_seed": { + "name": "بذر نویز" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RebatchImages": { + "display_name": "دسته‌بندی مجدد تصاویر", + "inputs": { + "batch_size": { + "name": "اندازه دسته" + }, + "images": { + "name": "تصاویر" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RebatchLatents": { + "display_name": "تغییر دسته‌بندی latentها", + "inputs": { + "batch_size": { + "name": "اندازه دسته" + }, + "latents": { + "name": "latentها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RecordAudio": { + "display_name": "ضبط صدا", + "inputs": { + "audio": { + "name": "صدا" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RecraftColorRGB": { + "description": "ایجاد رنگ Recraft با انتخاب مقادیر مشخص RGB.", + "display_name": "ایجاد رنگ Recraft با RGB", + "inputs": { + "b": { + "name": "b", + "tooltip": "مقدار آبی رنگ." + }, + "g": { + "name": "g", + "tooltip": "مقدار سبز رنگ." + }, + "r": { + "name": "r", + "tooltip": "مقدار قرمز رنگ." + }, + "recraft_color": { + "name": "رنگ Recraft" + } + }, + "outputs": { + "0": { + "name": "رنگ Recraft", + "tooltip": null + } + } + }, + "RecraftControls": { + "description": "ایجاد کنترل‌های Recraft برای سفارشی‌سازی تولید Recraft.", + "display_name": "کنترل‌های Recraft", + "inputs": { + "background_color": { + "name": "رنگ پس‌زمینه" + }, + "colors": { + "name": "رنگ‌ها" + } + }, + "outputs": { + "0": { + "name": "کنترل‌های Recraft", + "tooltip": null + } + } + }, + "RecraftCreativeUpscaleNode": { + "description": "بزرگ‌نمایی تصویر به صورت همزمان.\nیک تصویر شطرنجی را با ابزار «بزرگ‌نمایی خلاقانه» بهبود می‌بخشد و وضوح را با تمرکز بر جزئیات کوچک و چهره‌ها افزایش می‌دهد.", + "display_name": "بزرگ‌نمایی خلاقانه تصویر Recraft", + "inputs": { + "image": { + "name": "تصویر" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RecraftCrispUpscaleNode": { + "description": "بزرگ‌نمایی تصویر به صورت همزمان.\nیک تصویر شطرنجی را با ابزار «بزرگ‌نمایی شفاف» بهبود می‌بخشد، وضوح تصویر را افزایش داده و تصویر را شفاف‌تر و تمیزتر می‌کند.", + "display_name": "بزرگ‌نمایی شفاف تصویر Recraft", + "inputs": { + "image": { + "name": "تصویر" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RecraftImageInpaintingNode": { + "description": "تغییر تصویر بر اساس پرامپت و ماسک.", + "display_name": "بازسازی تصویر Recraft", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "image": { + "name": "تصویر" + }, + "mask": { + "name": "ماسک" + }, + "n": { + "name": "تعداد", + "tooltip": "تعداد تصاویر برای تولید." + }, + "negative_prompt": { + "name": "پرامپت منفی", + "tooltip": "توضیح متنی اختیاری از عناصر نامطلوب در تصویر." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت برای تولید تصویر." + }, + "recraft_style": { + "name": "سبک Recraft" + }, + "seed": { + "name": "seed", + "tooltip": "seed برای تعیین اینکه node باید دوباره اجرا شود؛ نتایج واقعی صرف‌نظر از seed غیرقطعی هستند." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RecraftImageToImageNode": { + "description": "تغییر تصویر بر اساس پرامپت و میزان قدرت.", + "display_name": "تبدیل تصویر به تصویر Recraft", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "image": { + "name": "تصویر" + }, + "n": { + "name": "تعداد", + "tooltip": "تعداد تصاویری که باید تولید شوند." + }, + "negative_prompt": { + "name": "پرامپت منفی", + "tooltip": "توضیح متنی اختیاری برای عناصر نامطلوب در تصویر." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت برای تولید تصویر." + }, + "recraft_controls": { + "name": "کنترل‌های Recraft", + "tooltip": "کنترل‌های اضافی اختیاری برای تولید از طریق node کنترل‌های Recraft." + }, + "recraft_style": { + "name": "سبک Recraft" + }, + "seed": { + "name": "seed", + "tooltip": "seed برای تعیین اینکه آیا node باید دوباره اجرا شود؛ نتایج واقعی صرف‌نظر از seed غیرقطعی هستند." + }, + "strength": { + "name": "قدرت", + "tooltip": "تفاوت با تصویر اصلی را مشخص می‌کند و باید در بازه [۰، ۱] باشد؛ ۰ به معنای تقریباً یکسان و ۱ به معنای شباهت بسیار کم است." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RecraftRemoveBackgroundNode": { + "description": "حذف پس‌زمینه از تصویر و بازگرداندن تصویر پردازش‌شده و ماسک.", + "display_name": "حذف پس‌زمینه Recraft", + "inputs": { + "image": { + "name": "تصویر" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "tooltip": null + } + } + }, + "RecraftReplaceBackgroundNode": { + "description": "جایگزینی پس‌زمینه تصویر بر اساس پرامپت ارائه‌شده.", + "display_name": "جایگزینی پس‌زمینه Recraft", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "image": { + "name": "تصویر" + }, + "n": { + "name": "تعداد", + "tooltip": "تعداد تصاویری که باید تولید شوند." + }, + "negative_prompt": { + "name": "پرامپت منفی", + "tooltip": "توضیح متنی اختیاری برای عناصر نامطلوب در تصویر." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت برای تولید تصویر." + }, + "recraft_style": { + "name": "سبک Recraft" + }, + "seed": { + "name": "seed", + "tooltip": "seed برای تعیین اینکه آیا node باید دوباره اجرا شود؛ نتایج واقعی صرف‌نظر از seed غیرقطعی هستند." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RecraftStyleV3DigitalIllustration": { + "description": "انتخاب سبک realistic_image و زیرسبک اختیاری.", + "display_name": "سبک Recraft - تصویرسازی دیجیتال", + "inputs": { + "substyle": { + "name": "زیرسبک" + } + }, + "outputs": { + "0": { + "name": "سبک Recraft", + "tooltip": null + } + } + }, + "RecraftStyleV3InfiniteStyleLibrary": { + "description": "انتخاب سبک بر اساس UUID موجود از کتابخانه بی‌نهایت سبک Recraft.", + "display_name": "سبک Recraft - کتابخانه بی‌نهایت سبک", + "inputs": { + "style_id": { + "name": "شناسه سبک", + "tooltip": "UUID سبک از کتابخانه بی‌نهایت سبک." + } + }, + "outputs": { + "0": { + "name": "سبک Recraft", + "tooltip": null + } + } + }, + "RecraftStyleV3LogoRaster": { + "description": "انتخاب سبک realistic_image و زیرسبک اختیاری.", + "display_name": "سبک Recraft - رستر لوگو", + "inputs": { + "substyle": { + "name": "زیرسبک" + } + }, + "outputs": { + "0": { + "name": "سبک Recraft", + "tooltip": null + } + } + }, + "RecraftStyleV3RealisticImage": { + "description": "انتخاب سبک realistic_image و زیرسبک اختیاری.", + "display_name": "سبک Recraft - تصویر واقع‌گرایانه", + "inputs": { + "substyle": { + "name": "زیرسبک" + } + }, + "outputs": { + "0": { + "name": "recraft_style", + "tooltip": null + } + } + }, + "RecraftTextToImageNode": { + "description": "تولید تصاویر به صورت همزمان بر اساس پرامپت و وضوح تصویر.", + "display_name": "تبدیل متن به تصویر Recraft", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "n": { + "name": "تعداد", + "tooltip": "تعداد تصاویر برای تولید." + }, + "negative_prompt": { + "name": "پرامپت منفی", + "tooltip": "توضیح متنی اختیاری از عناصر نامطلوب در تصویر." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت برای تولید تصویر." + }, + "recraft_controls": { + "name": "recraft_controls", + "tooltip": "کنترل‌های اختیاری بیشتر بر تولید از طریق node کنترل Recraft." + }, + "recraft_style": { + "name": "recraft_style" + }, + "seed": { + "name": "seed", + "tooltip": "seed برای تعیین اینکه node باید دوباره اجرا شود؛ نتایج واقعی صرف‌نظر از seed غیرقطعی هستند." + }, + "size": { + "name": "اندازه", + "tooltip": "اندازه تصویر تولید شده." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RecraftTextToVectorNode": { + "description": "تولید SVG به صورت همزمان بر اساس پرامپت و وضوح تصویر.", + "display_name": "تبدیل متن به وکتور Recraft", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "n": { + "name": "تعداد", + "tooltip": "تعداد تصاویر برای تولید." + }, + "negative_prompt": { + "name": "پرامپت منفی", + "tooltip": "توضیح متنی اختیاری از عناصر نامطلوب در تصویر." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت برای تولید تصویر." + }, + "recraft_controls": { + "name": "recraft_controls", + "tooltip": "کنترل‌های اختیاری بیشتر بر تولید از طریق node کنترل Recraft." + }, + "seed": { + "name": "seed", + "tooltip": "seed برای تعیین اینکه node باید دوباره اجرا شود؛ نتایج واقعی صرف‌نظر از seed غیرقطعی هستند." + }, + "size": { + "name": "اندازه", + "tooltip": "اندازه تصویر تولید شده." + }, + "substyle": { + "name": "زیرسبک" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RecraftVectorizeImageNode": { + "description": "تولید SVG به صورت همزمان از یک تصویر ورودی.", + "display_name": "وکتورسازی تصویر Recraft", + "inputs": { + "image": { + "name": "تصویر" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ReferenceLatent": { + "description": "این node فضای نهفته راهنما را برای مدل ویرایش تنظیم می‌کند. اگر مدل پشتیبانی کند، می‌توانید چندین مورد را برای تنظیم چند تصویر مرجع زنجیره کنید.", + "display_name": "ReferenceLatent", + "inputs": { + "conditioning": { + "name": "شرط‌گذاری" + }, + "latent": { + "name": "latent" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RegexExtract": { + "display_name": "استخراج با Regex", + "inputs": { + "case_insensitive": { + "name": "بدون حساسیت به حروف" + }, + "dotall": { + "name": "dotall" + }, + "group_index": { + "name": "شماره گروه" + }, + "mode": { + "name": "حالت" + }, + "multiline": { + "name": "چندخطی" + }, + "regex_pattern": { + "name": "الگوی regex" + }, + "string": { + "name": "رشته" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RegexMatch": { + "display_name": "تطبیق با Regex", + "inputs": { + "case_insensitive": { + "name": "بدون حساسیت به حروف" + }, + "dotall": { + "name": "dotall" + }, + "multiline": { + "name": "چندخطی" + }, + "regex_pattern": { + "name": "الگوی regex" + }, + "string": { + "name": "رشته" + } + }, + "outputs": { + "0": { + "name": "تطبیق‌ها", + "tooltip": null + } + } + }, + "RegexReplace": { + "description": "یافتن و جایگزینی متن با استفاده از الگوهای regex.", + "display_name": "جایگزینی با Regex", + "inputs": { + "case_insensitive": { + "name": "بدون حساسیت به حروف" + }, + "count": { + "name": "تعداد", + "tooltip": "حداکثر تعداد جایگزینی. مقدار ۰ برای جایگزینی همه موارد (پیش‌فرض). مقدار ۱ فقط اولین تطبیق، ۲ برای دو تطبیق اول و غیره." + }, + "dotall": { + "name": "dotall", + "tooltip": "در صورت فعال بودن، کاراکتر نقطه (.) هر کاراکتری از جمله کاراکترهای خط جدید را تطبیق می‌دهد. در غیر این صورت، نقطه فقط کاراکترهای غیر از خط جدید را تطبیق می‌دهد." + }, + "multiline": { + "name": "چندخطی" + }, + "regex_pattern": { + "name": "الگوی regex" + }, + "replace": { + "name": "جایگزین" + }, + "string": { + "name": "رشته" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RenormCFG": { + "display_name": "RenormCFG", + "inputs": { + "cfg_trunc": { + "name": "cfg_trunc" + }, + "model": { + "name": "مدل" + }, + "renorm_cfg": { + "name": "renorm_cfg" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RepeatImageBatch": { + "display_name": "تکرار دسته تصویر", + "inputs": { + "amount": { + "name": "تعداد" + }, + "image": { + "name": "تصویر" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RepeatLatentBatch": { + "display_name": "تکرار دسته latent", + "inputs": { + "amount": { + "name": "تعداد" + }, + "samples": { + "name": "نمونه‌ها" + } + } + }, + "ReplaceText": { + "display_name": "جایگزینی متن", + "inputs": { + "find": { + "name": "یافتن", + "tooltip": "متنی که باید پیدا شود." + }, + "replace": { + "name": "جایگزین", + "tooltip": "متنی که باید جایگزین شود." + }, + "texts": { + "name": "متن‌ها", + "tooltip": "متنی که باید پردازش شود." + } + }, + "outputs": { + "0": { + "name": "متن‌های پردازش‌شده", + "tooltip": "متن‌های پردازش‌شده" + } + } + }, + "ReplaceVideoLatentFrames": { + "display_name": "جایگزینی فریم‌های نهفته ویدیو", + "inputs": { + "destination": { + "name": "مقصد", + "tooltip": "فضای نهفته مقصد که فریم‌ها در آن جایگزین خواهند شد." + }, + "index": { + "name": "اندیس", + "tooltip": "اندیس فریم نهفته شروع در فضای نهفته مقصد که فریم‌های فضای نهفته منبع در آن قرار می‌گیرند. مقادیر منفی از انتها شمارش می‌شوند." + }, + "source": { + "name": "منبع", + "tooltip": "فضای نهفته منبع که فریم‌ها را برای درج در فضای نهفته مقصد فراهم می‌کند. اگر ارائه نشود، فضای نهفته مقصد بدون تغییر بازگردانده می‌شود." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RescaleCFG": { + "display_name": "مقیاس‌دهی CFG", + "inputs": { + "model": { + "name": "مدل" + }, + "multiplier": { + "name": "ضریب" + } + } + }, + "ResizeAndPadImage": { + "display_name": "تغییر اندازه و افزودن پد به تصویر", + "inputs": { + "image": { + "name": "تصویر" + }, + "interpolation": { + "name": "درون‌یابی" + }, + "padding_color": { + "name": "رنگ پد" + }, + "target_height": { + "name": "ارتفاع هدف" + }, + "target_width": { + "name": "عرض هدف" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ResizeImageMaskNode": { + "description": "تغییر اندازه تصویر یا mask با استفاده از روش‌های مختلف مقیاس‌گذاری.", + "display_name": "تغییر اندازه تصویر/ماسک", + "inputs": { + "input": { + "name": "ورودی" + }, + "resize_type": { + "name": "نوع تغییر اندازه", + "tooltip": "نحوه تغییر اندازه را انتخاب کنید: بر اساس ابعاد دقیق، ضریب مقیاس، تطبیق با تصویر دیگر و غیره." + }, + "resize_type_crop": { + "name": "برش" + }, + "resize_type_height": { + "name": "ارتفاع" + }, + "resize_type_width": { + "name": "عرض" + }, + "scale_method": { + "name": "روش مقیاس‌دهی", + "tooltip": "الگوریتم درون‌یابی. 'area' برای کاهش اندازه بهترین است، 'lanczos' برای افزایش اندازه، و 'nearest-exact' برای هنر پیکسلی مناسب است." + } + }, + "outputs": { + "0": { + "name": "تغییر اندازه داده شده", + "tooltip": null + } + } + }, + "ResizeImagesByLongerEdge": { + "display_name": "تغییر اندازه تصاویر بر اساس ضلع بلندتر", + "inputs": { + "images": { + "name": "تصاویر", + "tooltip": "تصویر برای پردازش." + }, + "longer_edge": { + "name": "ضلع بلندتر", + "tooltip": "طول هدف برای ضلع بلندتر." + } + }, + "outputs": { + "0": { + "name": "تصاویر", + "tooltip": "تصاویر پردازش شده" + } + } + }, + "ResizeImagesByShorterEdge": { + "display_name": "تغییر اندازه تصاویر بر اساس ضلع کوتاه‌تر", + "inputs": { + "images": { + "name": "تصاویر", + "tooltip": "تصویر برای پردازش." + }, + "shorter_edge": { + "name": "ضلع کوتاه‌تر", + "tooltip": "طول هدف برای ضلع کوتاه‌تر." + } + }, + "outputs": { + "0": { + "name": "تصاویر", + "tooltip": "تصاویر پردازش شده" + } + } + }, + "ResolutionBucket": { + "display_name": "دسته‌بندی بر اساس وضوح", + "inputs": { + "conditioning": { + "name": "شرایط‌دهی", + "tooltip": "لیست لیست‌های شرایط‌دهی (باید با طول فضاهای نهفته برابر باشد)." + }, + "latents": { + "name": "فضاهای نهفته", + "tooltip": "لیست دیکشنری‌های نهفته برای دسته‌بندی بر اساس وضوح." + } + }, + "outputs": { + "0": { + "name": "فضاهای نهفته", + "tooltip": "لیست دیکشنری‌های نهفته دسته‌بندی شده، یکی برای هر دسته وضوح." + }, + "1": { + "name": "شرایط‌دهی", + "tooltip": "لیست لیست‌های شرایط‌دهی، یکی برای هر دسته وضوح." + } + } + }, + "Rodin3D_Detail": { + "description": "تولید دارایی‌های سه‌بعدی با استفاده از Rodin API", + "display_name": "Rodin 3D Generate - تولید جزئیات", + "inputs": { + "Images": { + "name": "تصاویر" + }, + "Material_Type": { + "name": "نوع متریال" + }, + "Polygon_count": { + "name": "تعداد پلی‌گان" + }, + "Seed": { + "name": "Seed" + } + }, + "outputs": { + "0": { + "name": "مسیر مدل سه‌بعدی", + "tooltip": null + } + } + }, + "Rodin3D_Gen2": { + "description": "تولید دارایی‌های سه‌بعدی با استفاده از Rodin API", + "display_name": "Rodin 3D Generate - تولید Gen-2", + "inputs": { + "Images": { + "name": "تصاویر" + }, + "Material_Type": { + "name": "نوع متریال" + }, + "Polygon_count": { + "name": "تعداد پلی‌گان" + }, + "Seed": { + "name": "Seed" + }, + "TAPose": { + "name": "TAPose" + } + }, + "outputs": { + "0": { + "name": "مسیر مدل سه‌بعدی", + "tooltip": null + } + } + }, + "Rodin3D_Regular": { + "description": "تولید دارایی‌های سه‌بعدی با استفاده از Rodin API", + "display_name": "Rodin 3D Generate - تولید معمولی", + "inputs": { + "Images": { + "name": "تصاویر" + }, + "Material_Type": { + "name": "نوع متریال" + }, + "Polygon_count": { + "name": "تعداد پلی‌گان" + }, + "Seed": { + "name": "Seed" + } + }, + "outputs": { + "0": { + "name": "مسیر مدل سه‌بعدی", + "tooltip": null + } + } + }, + "Rodin3D_Sketch": { + "description": "تولید دارایی‌های سه‌بعدی با استفاده از Rodin API", + "display_name": "Rodin 3D Generate - تولید اسکچ", + "inputs": { + "Images": { + "name": "تصاویر" + }, + "Seed": { + "name": "Seed" + } + }, + "outputs": { + "0": { + "name": "مسیر مدل سه‌بعدی", + "tooltip": null + } + } + }, + "Rodin3D_Smooth": { + "description": "تولید دارایی‌های سه‌بعدی با استفاده از Rodin API", + "display_name": "Rodin 3D Generate - تولید صاف", + "inputs": { + "Images": { + "name": "تصاویر" + }, + "Material_Type": { + "name": "نوع متریال" + }, + "Polygon_count": { + "name": "تعداد پلی‌گان" + }, + "Seed": { + "name": "Seed" + } + }, + "outputs": { + "0": { + "name": "مسیر مدل سه‌بعدی", + "tooltip": null + } + } + }, + "RunwayFirstLastFrameNode": { + "description": "اولین و آخرین فریم کلیدی را بارگذاری کنید، یک پرامپت بنویسید و ویدیو تولید کنید. انتقال‌های پیچیده‌تر، مانند زمانی که فریم آخر کاملاً با فریم اول متفاوت است، ممکن است از مدت زمان طولانی‌تر ۱۰ ثانیه‌ای بهره‌مند شوند. این کار به تولید اجازه می‌دهد تا زمان بیشتری برای انتقال روان بین دو ورودی داشته باشد. پیش از شروع، این نکات کلیدی را مرور کنید تا مطمئن شوید انتخاب‌های ورودی شما باعث موفقیت تولید خواهد شد: https://help.runwayml.com/hc/en-us/articles/34170748696595-Creating-with-Keyframes-on-Gen-3.", + "display_name": "Runway تبدیل اولین و آخرین فریم به ویدیو", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان" + }, + "end_frame": { + "name": "فریم پایان", + "tooltip": "فریم پایان برای استفاده در ویدیو. فقط برای gen3a_turbo پشتیبانی می‌شود." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت متنی برای تولید" + }, + "ratio": { + "name": "نسبت تصویر" + }, + "seed": { + "name": "seed", + "tooltip": "seed تصادفی برای تولید" + }, + "start_frame": { + "name": "فریم شروع", + "tooltip": "فریم شروع برای استفاده در ویدیو" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RunwayImageToVideoNodeGen3a": { + "description": "تولید ویدیو از یک فریم شروع با استفاده از مدل Gen3a Turbo. پیش از شروع، این نکات کلیدی را مرور کنید تا مطمئن شوید انتخاب‌های ورودی شما باعث موفقیت تولید خواهد شد: https://help.runwayml.com/hc/en-us/articles/33927968552339-Creating-with-Act-One-on-Gen-3-Alpha-and-Turbo.", + "display_name": "Runway تبدیل تصویر به ویدیو (Gen3a Turbo)", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت متنی برای تولید" + }, + "ratio": { + "name": "نسبت تصویر" + }, + "seed": { + "name": "seed", + "tooltip": "seed تصادفی برای تولید" + }, + "start_frame": { + "name": "فریم شروع", + "tooltip": "فریم شروع برای استفاده در ویدیو" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RunwayImageToVideoNodeGen4": { + "description": "تولید ویدیو از یک فریم شروع با استفاده از مدل Gen4 Turbo. پیش از شروع، این نکات کلیدی را مرور کنید تا مطمئن شوید انتخاب‌های ورودی شما باعث موفقیت تولید خواهد شد: https://help.runwayml.com/hc/en-us/articles/37327109429011-Creating-with-Gen-4-Video.", + "display_name": "Runway تبدیل تصویر به ویدیو (Gen4 Turbo)", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت متنی برای تولید" + }, + "ratio": { + "name": "نسبت تصویر" + }, + "seed": { + "name": "seed", + "tooltip": "seed تصادفی برای تولید" + }, + "start_frame": { + "name": "فریم شروع", + "tooltip": "فریم شروع برای استفاده در ویدیو" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RunwayTextToImageNode": { + "description": "تولید تصویر از یک متن ورودی با استفاده از مدل Gen 4 سرویس Runway. همچنین می‌توانید یک تصویر مرجع برای راهنمایی تولید وارد کنید.", + "display_name": "تبدیل متن به تصویر Runway", + "inputs": { + "prompt": { + "name": "متن ورودی", + "tooltip": "متن راهنما برای تولید تصویر" + }, + "ratio": { + "name": "نسبت تصویر" + }, + "reference_image": { + "name": "تصویر مرجع", + "tooltip": "تصویر مرجع اختیاری برای راهنمایی تولید" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SDTurboScheduler": { + "display_name": "زمان‌بندی SDTurbo", + "inputs": { + "denoise": { + "name": "کاهش نویز" + }, + "model": { + "name": "مدل" + }, + "steps": { + "name": "گام‌ها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SD_4XUpscale_Conditioning": { + "display_name": "شرایط بزرگ‌نمایی ۴ برابر SD", + "inputs": { + "images": { + "name": "تصاویر" + }, + "negative": { + "name": "منفی" + }, + "noise_augmentation": { + "name": "افزایش نویز" + }, + "positive": { + "name": "مثبت" + }, + "scale_ratio": { + "name": "نسبت مقیاس" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "SV3D_Conditioning": { + "display_name": "شرط‌گذاری SV3D", + "inputs": { + "clip_vision": { + "name": "clip vision" + }, + "elevation": { + "name": "ارتفاع زاویه" + }, + "height": { + "name": "ارتفاع" + }, + "init_image": { + "name": "تصویر اولیه" + }, + "vae": { + "name": "vae" + }, + "video_frames": { + "name": "فریم‌های ویدیو" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "SVD_img2vid_Conditioning": { + "display_name": "شرط‌گذاری SVD_img2vid", + "inputs": { + "augmentation_level": { + "name": "سطح افزایش داده" + }, + "clip_vision": { + "name": "clip vision" + }, + "fps": { + "name": "فریم بر ثانیه" + }, + "height": { + "name": "ارتفاع" + }, + "init_image": { + "name": "تصویر اولیه" + }, + "motion_bucket_id": { + "name": "شناسه motion bucket" + }, + "vae": { + "name": "vae" + }, + "video_frames": { + "name": "فریم‌های ویدیو" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "مثبت" + }, + "1": { + "name": "منفی" + }, + "2": { + "name": "latent" + } + } + }, + "SamplerCustom": { + "display_name": "SamplerCustom", + "inputs": { + "add_noise": { + "name": "افزودن نویز" + }, + "cfg": { + "name": "cfg" + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "latent_image": { + "name": "تصویر latent" + }, + "model": { + "name": "مدل" + }, + "negative": { + "name": "منفی" + }, + "noise_seed": { + "name": "بذر نویز" + }, + "positive": { + "name": "مثبت" + }, + "sampler": { + "name": "نمونه‌گیر" + }, + "sigmas": { + "name": "سیگماها" + } + }, + "outputs": { + "0": { + "name": "خروجی", + "tooltip": null + }, + "1": { + "name": "خروجی بدون نویز", + "tooltip": null + } + } + }, + "SamplerCustomAdvanced": { + "display_name": "SamplerCustomAdvanced", + "inputs": { + "guider": { + "name": "راهنما" + }, + "latent_image": { + "name": "تصویر latent" + }, + "noise": { + "name": "نویز" + }, + "sampler": { + "name": "نمونه‌گیر" + }, + "sigmas": { + "name": "سیگماها" + } + }, + "outputs": { + "0": { + "name": "خروجی", + "tooltip": null + }, + "1": { + "name": "خروجی بدون نویز", + "tooltip": null + } + } + }, + "SamplerDPMAdaptative": { + "display_name": "SamplerDPMAdaptative", + "inputs": { + "accept_safety": { + "name": "تأیید ایمنی" + }, + "atol": { + "name": "atol" + }, + "dcoeff": { + "name": "dcoeff" + }, + "eta": { + "name": "eta" + }, + "h_init": { + "name": "h_init" + }, + "icoeff": { + "name": "icoeff" + }, + "order": { + "name": "ترتیب" + }, + "pcoeff": { + "name": "pcoeff" + }, + "rtol": { + "name": "rtol" + }, + "s_noise": { + "name": "s_noise" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerDPMPP_2M_SDE": { + "display_name": "SamplerDPMPP_2M_SDE", + "inputs": { + "eta": { + "name": "eta" + }, + "noise_device": { + "name": "دستگاه نویز" + }, + "s_noise": { + "name": "s_noise" + }, + "solver_type": { + "name": "نوع حل‌گر" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerDPMPP_2S_Ancestral": { + "display_name": "SamplerDPMPP_2S_Ancestral", + "inputs": { + "eta": { + "name": "eta" + }, + "s_noise": { + "name": "s_noise" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerDPMPP_3M_SDE": { + "display_name": "SamplerDPMPP_3M_SDE", + "inputs": { + "eta": { + "name": "اتا" + }, + "noise_device": { + "name": "noise_device" + }, + "s_noise": { + "name": "s_noise" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerDPMPP_SDE": { + "display_name": "SamplerDPMPP_SDE", + "inputs": { + "eta": { + "name": "اتا" + }, + "noise_device": { + "name": "noise_device" + }, + "r": { + "name": "r" + }, + "s_noise": { + "name": "s_noise" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerER_SDE": { + "display_name": "SamplerER_SDE", + "inputs": { + "eta": { + "name": "اتا", + "tooltip": "قدرت تصادفی معادله دیفرانسیل تصادفی معکوس زمان.\nوقتی اتا=۰ باشد، به معادله دیفرانسیل معمولی قطعی تبدیل می‌شود. این تنظیم برای نوع حل‌کننده ER-SDE اعمال نمی‌شود." + }, + "max_stage": { + "name": "بیشترین مرحله" + }, + "s_noise": { + "name": "s_noise" + }, + "solver_type": { + "name": "نوع حل‌کننده" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerEulerAncestral": { + "display_name": "SamplerEulerAncestral", + "inputs": { + "eta": { + "name": "اتا" + }, + "s_noise": { + "name": "s_noise" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerEulerAncestralCFGPP": { + "display_name": "SamplerEulerAncestralCFG++", + "inputs": { + "eta": { + "name": "اتا" + }, + "s_noise": { + "name": "s_noise" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerEulerCFGpp": { + "display_name": "SamplerEulerCFG++", + "inputs": { + "version": { + "name": "نسخه" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerLCMUpscale": { + "display_name": "SamplerLCMUpscale", + "inputs": { + "scale_ratio": { + "name": "نسبت بزرگ‌نمایی" + }, + "scale_steps": { + "name": "گام‌های بزرگ‌نمایی" + }, + "upscale_method": { + "name": "روش بزرگ‌نمایی" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerLMS": { + "display_name": "SamplerLMS", + "inputs": { + "order": { + "name": "ترتیب" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerSASolver": { + "display_name": "SamplerSASolver", + "inputs": { + "corrector_order": { + "name": "ترتیب تصحیح‌کننده" + }, + "eta": { + "name": "اتا" + }, + "model": { + "name": "مدل" + }, + "predictor_order": { + "name": "ترتیب پیش‌بینی‌کننده" + }, + "s_noise": { + "name": "s_noise" + }, + "sde_end_percent": { + "name": "درصد پایان SDE" + }, + "sde_start_percent": { + "name": "درصد شروع SDE" + }, + "simple_order_2": { + "name": "ترتیب ساده ۲" + }, + "use_pece": { + "name": "استفاده از PECE" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerSEEDS2": { + "description": "این نود نمونه‌بردار می‌تواند چندین نمونه‌بردار را نمایش دهد:\n\nseeds_2\n- تنظیمات پیش‌فرض\n\nexp_heun_2_x0\n- solver_type=phi_2، r=۱.۰، eta=۰.۰\n\nexp_heun_2_x0_sde\n- solver_type=phi_2، r=۱.۰، eta=۱.۰، s_noise=۱.۰", + "display_name": "SamplerSEEDS2", + "inputs": { + "eta": { + "name": "eta", + "tooltip": "قدرت تصادفی" + }, + "r": { + "name": "r", + "tooltip": "اندازه گام نسبی برای مرحله میانی (گره c2)" + }, + "s_noise": { + "name": "s_noise", + "tooltip": "ضریب نویز SDE" + }, + "solver_type": { + "name": "solver_type" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplingPercentToSigma": { + "display_name": "SamplingPercentToSigma", + "inputs": { + "model": { + "name": "model" + }, + "return_actual_sigma": { + "name": "return_actual_sigma", + "tooltip": "مقدار واقعی سیگما را به جای مقداری که برای بررسی بازه استفاده می‌شود بازگرداند.\nاین فقط بر نتایج در ۰.۰ و ۱.۰ تأثیر می‌گذارد." + }, + "sampling_percent": { + "name": "sampling_percent" + } + }, + "outputs": { + "0": { + "name": "sigma_value", + "tooltip": null + } + } + }, + "SaveAnimatedPNG": { + "display_name": "ذخیره PNG متحرک", + "inputs": { + "compress_level": { + "name": "compress_level" + }, + "filename_prefix": { + "name": "filename_prefix" + }, + "fps": { + "name": "fps" + }, + "images": { + "name": "images" + } + } + }, + "SaveAnimatedWEBP": { + "display_name": "ذخیره WEBP متحرک", + "inputs": { + "filename_prefix": { + "name": "filename_prefix" + }, + "fps": { + "name": "fps" + }, + "images": { + "name": "images" + }, + "lossless": { + "name": "lossless" + }, + "method": { + "name": "method" + }, + "quality": { + "name": "quality" + } + } + }, + "SaveAudio": { + "display_name": "ذخیره صدا (FLAC)", + "inputs": { + "audio": { + "name": "audio" + }, + "audioUI": { + "name": "audioUI" + }, + "filename_prefix": { + "name": "filename_prefix" + } + } + }, + "SaveAudioMP3": { + "display_name": "ذخیره صدا (MP3)", + "inputs": { + "audio": { + "name": "audio" + }, + "audioUI": { + "name": "audioUI" + }, + "filename_prefix": { + "name": "filename_prefix" + }, + "quality": { + "name": "quality" + } + } + }, + "SaveAudioOpus": { + "display_name": "ذخیره صدا (Opus)", + "inputs": { + "audio": { + "name": "audio" + }, + "audioUI": { + "name": "audioUI" + }, + "filename_prefix": { + "name": "filename_prefix" + }, + "quality": { + "name": "quality" + } + } + }, + "SaveGLB": { + "display_name": "ذخیره GLB", + "inputs": { + "filename_prefix": { + "name": "filename_prefix" + }, + "image": { + "name": "image" + }, + "mesh": { + "name": "mesh" + } + } + }, + "SaveImage": { + "description": "تصاویر ورودی را در پوشه خروجی ComfyUI شما ذخیره می‌کند.", + "display_name": "ذخیره تصویر", + "inputs": { + "filename_prefix": { + "name": "پیشوند نام فایل", + "tooltip": "پیشوند فایل ذخیره‌شونده. این می‌تواند شامل اطلاعات قالب‌بندی مانند ‎%date:yyyy-MM-dd%‎ یا ‎%Empty Latent Image.width%‎ برای درج مقادیر از nodeها باشد." + }, + "images": { + "name": "تصاویر", + "tooltip": "تصاویری که باید ذخیره شوند." + } + } + }, + "SaveImageDataSetToFolder": { + "display_name": "ذخیره مجموعه تصاویر در پوشه", + "inputs": { + "filename_prefix": { + "name": "پیشوند نام فایل تصویر", + "tooltip": "پیشوند برای نام فایل‌های ذخیره‌شده تصویر." + }, + "folder_name": { + "name": "نام پوشه", + "tooltip": "نام پوشه‌ای که تصاویر در آن ذخیره می‌شوند (درون پوشه خروجی)." + }, + "images": { + "name": "تصاویر", + "tooltip": "فهرست تصاویر برای ذخیره." + } + } + }, + "SaveImageTextDataSetToFolder": { + "display_name": "ذخیره مجموعه تصویر و متن در پوشه", + "inputs": { + "filename_prefix": { + "name": "پیشوند نام فایل تصویر", + "tooltip": "پیشوند برای نام فایل‌های ذخیره‌شده تصویر." + }, + "folder_name": { + "name": "نام پوشه", + "tooltip": "نام پوشه‌ای که تصاویر در آن ذخیره می‌شوند (درون پوشه خروجی)." + }, + "images": { + "name": "تصاویر", + "tooltip": "فهرست تصاویر برای ذخیره." + }, + "texts": { + "name": "متون", + "tooltip": "فهرست کپشن‌های متنی برای ذخیره." + } + } + }, + "SaveImageWebsocket": { + "display_name": "SaveImageWebsocket", + "inputs": { + "images": { + "name": "تصاویر" + } + } + }, + "SaveLatent": { + "display_name": "ذخیره Latent", + "inputs": { + "filename_prefix": { + "name": "پیشوند نام فایل" + }, + "samples": { + "name": "نمونه‌ها" + } + } + }, + "SaveLoRA": { + "display_name": "ذخیره وزن‌های LoRA", + "inputs": { + "lora": { + "name": "LoRA", + "tooltip": "مدل LoRA برای ذخیره. از مدل با لایه‌های LoRA استفاده نکنید." + }, + "prefix": { + "name": "پیشوند", + "tooltip": "پیشوند مورد استفاده برای فایل ذخیره‌شده LoRA." + }, + "steps": { + "name": "گام‌ها", + "tooltip": "اختیاری: تعداد گام‌هایی که LoRA آموزش دیده است، برای نام‌گذاری فایل ذخیره‌شده استفاده می‌شود." + } + } + }, + "SaveSVGNode": { + "description": "فایل‌های SVG را روی دیسک ذخیره کنید.", + "display_name": "ذخیره SVG Node", + "inputs": { + "filename_prefix": { + "name": "پیشوند نام فایل", + "tooltip": "پیشوند فایل ذخیره‌شونده. این می‌تواند شامل اطلاعات قالب‌بندی مانند ‎%date:yyyy-MM-dd%‎ یا ‎%Empty Latent Image.width%‎ برای درج مقادیر از nodeها باشد." + }, + "svg": { + "name": "SVG" + } + } + }, + "SaveTrainingDataset": { + "display_name": "ذخیره مجموعه داده آموزشی", + "inputs": { + "conditioning": { + "name": "شرایط‌دهی", + "tooltip": "فهرست لیست‌های conditioning از MakeTrainingDataset." + }, + "folder_name": { + "name": "نام پوشه", + "tooltip": "نام پوشه برای ذخیره مجموعه داده (درون پوشه خروجی)." + }, + "latents": { + "name": "latents", + "tooltip": "فهرست دیکشنری‌های latent از MakeTrainingDataset." + }, + "shard_size": { + "name": "اندازه shard", + "tooltip": "تعداد نمونه‌ها در هر فایل shard." + } + } + }, + "SaveVideo": { + "description": "تصاویر ورودی را در پوشه خروجی ComfyUI شما ذخیره می‌کند.", + "display_name": "ذخیره ویدیو", + "inputs": { + "codec": { + "name": "کدک", + "tooltip": "کدک مورد استفاده برای ویدیو." + }, + "filename_prefix": { + "name": "پیشوند نام فایل", + "tooltip": "پیشوند فایل برای ذخیره. این می‌تواند شامل اطلاعات قالب‌بندی مانند ‎%date:yyyy-MM-dd%‎ یا ‎%Empty Latent Image.width%‎ برای درج مقادیر از nodeها باشد." + }, + "format": { + "name": "فرمت", + "tooltip": "فرمت ذخیره‌سازی ویدیو." + }, + "video": { + "name": "ویدیو", + "tooltip": "ویدیویی که باید ذخیره شود." + } + } + }, + "SaveWEBM": { + "display_name": "ذخیره WEBM", + "inputs": { + "codec": { + "name": "کدک" + }, + "crf": { + "name": "CRF", + "tooltip": "CRF بالاتر به معنای کیفیت پایین‌تر و حجم فایل کمتر است، CRF پایین‌تر به معنای کیفیت بالاتر و حجم فایل بیشتر است." + }, + "filename_prefix": { + "name": "پیشوند نام فایل" + }, + "fps": { + "name": "فریم بر ثانیه" + }, + "images": { + "name": "تصاویر" + } + } + }, + "ScaleROPE": { + "description": "مقیاس و جابجایی ROPE مدل را تنظیم می‌کند.", + "display_name": "مقیاس‌بندی ROPE", + "inputs": { + "model": { + "name": "مدل" + }, + "scale_t": { + "name": "مقیاس T" + }, + "scale_x": { + "name": "مقیاس X" + }, + "scale_y": { + "name": "مقیاس Y" + }, + "shift_t": { + "name": "جابجایی T" + }, + "shift_x": { + "name": "جابجایی X" + }, + "shift_y": { + "name": "جابجایی Y" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SelfAttentionGuidance": { + "display_name": "راهنمایی Self-Attention", + "inputs": { + "blur_sigma": { + "name": "سیگمای تاری" + }, + "model": { + "name": "مدل" + }, + "scale": { + "name": "مقیاس" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SetClipHooks": { + "display_name": "تنظیم Hookهای CLIP", + "inputs": { + "apply_to_conds": { + "name": "اعمال به شرایط" + }, + "clip": { + "name": "clip" + }, + "hooks": { + "name": "hookها" + }, + "schedule_clip": { + "name": "زمان‌بندی clip" + } + } + }, + "SetFirstSigma": { + "display_name": "تنظیم سیگما اول", + "inputs": { + "sigma": { + "name": "سیگما" + }, + "sigmas": { + "name": "سیگماها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SetHookKeyframes": { + "display_name": "تنظیم کلیدفریم‌های Hook", + "inputs": { + "hook_kf": { + "name": "کلیدفریم هوک" + }, + "hooks": { + "name": "هوک‌ها" + } + } + }, + "SetLatentNoiseMask": { + "display_name": "تنظیم ماسک نویز latent", + "inputs": { + "mask": { + "name": "ماسک" + }, + "samples": { + "name": "نمونه‌ها" + } + } + }, + "SetUnionControlNetType": { + "display_name": "تنظیم نوع Union ControlNet", + "inputs": { + "control_net": { + "name": "control_net" + }, + "type": { + "name": "نوع" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ShuffleDataset": { + "display_name": "درهم‌ریختن دیتاست تصویر", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "images": { + "name": "تصاویر", + "tooltip": "فهرست تصاویر برای پردازش." + }, + "seed": { + "name": "بذر", + "tooltip": "بذر تصادفی." + } + }, + "outputs": { + "0": { + "name": "تصاویر", + "tooltip": "تصاویر پردازش‌شده" + } + } + }, + "ShuffleImageTextDataset": { + "display_name": "درهم‌ریختن دیتاست تصویر-متن", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "images": { + "name": "تصاویر", + "tooltip": "فهرست تصاویر برای درهم‌ریختن." + }, + "seed": { + "name": "بذر", + "tooltip": "بذر تصادفی." + }, + "texts": { + "name": "متن‌ها", + "tooltip": "فهرست متن‌ها برای درهم‌ریختن." + } + }, + "outputs": { + "0": { + "name": "تصاویر", + "tooltip": "تصاویر درهم‌ریخته" + }, + "1": { + "name": "متن‌ها", + "tooltip": "متن‌های درهم‌ریخته" + } + } + }, + "SkipLayerGuidanceDiT": { + "description": "نسخه عمومی نود SkipLayerGuidance که می‌تواند روی هر مدل DiT استفاده شود.", + "display_name": "SkipLayerGuidanceDiT", + "inputs": { + "double_layers": { + "name": "لایه‌های دوگانه" + }, + "end_percent": { + "name": "درصد پایان" + }, + "model": { + "name": "مدل" + }, + "rescaling_scale": { + "name": "مقیاس بازتنظیم" + }, + "scale": { + "name": "مقیاس" + }, + "single_layers": { + "name": "لایه‌های تکی" + }, + "start_percent": { + "name": "درصد شروع" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SkipLayerGuidanceDiTSimple": { + "description": "نسخه ساده نود SkipLayerGuidanceDiT که فقط عبور uncond را تغییر می‌دهد.", + "display_name": "SkipLayerGuidanceDiTSimple", + "inputs": { + "double_layers": { + "name": "لایه‌های دوگانه" + }, + "end_percent": { + "name": "درصد پایان" + }, + "model": { + "name": "مدل" + }, + "single_layers": { + "name": "لایه‌های تکی" + }, + "start_percent": { + "name": "درصد شروع" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SkipLayerGuidanceSD3": { + "description": "نسخه عمومی گره SkipLayerGuidance که می‌تواند برای هر مدل DiT استفاده شود.", + "display_name": "SkipLayerGuidanceSD3", + "inputs": { + "end_percent": { + "name": "درصد پایان" + }, + "layers": { + "name": "لایه‌ها" + }, + "model": { + "name": "مدل" + }, + "scale": { + "name": "مقیاس" + }, + "start_percent": { + "name": "درصد شروع" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SolidMask": { + "display_name": "SolidMask", + "inputs": { + "height": { + "name": "ارتفاع" + }, + "value": { + "name": "مقدار" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SplitAudioChannels": { + "description": "صدا را به کانال‌های چپ و راست جدا می‌کند.", + "display_name": "تقسیم کانال‌های صوتی", + "inputs": { + "audio": { + "name": "صدا" + } + }, + "outputs": { + "0": { + "name": "چپ", + "tooltip": null + }, + "1": { + "name": "راست", + "tooltip": null + } + } + }, + "SplitImageWithAlpha": { + "display_name": "تقسیم تصویر با آلفا", + "inputs": { + "image": { + "name": "تصویر" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "tooltip": null + } + } + }, + "SplitSigmas": { + "display_name": "SplitSigmas", + "inputs": { + "sigmas": { + "name": "سیگماها" + }, + "step": { + "name": "گام" + } + }, + "outputs": { + "0": { + "name": "سیگماهای بالا", + "tooltip": null + }, + "1": { + "name": "سیگماهای پایین", + "tooltip": null + } + } + }, + "SplitSigmasDenoise": { + "display_name": "SplitSigmasDenoise", + "inputs": { + "denoise": { + "name": "کاهش نویز" + }, + "sigmas": { + "name": "سیگماها" + } + }, + "outputs": { + "0": { + "name": "سیگماهای بالا", + "tooltip": null + }, + "1": { + "name": "سیگماهای پایین", + "tooltip": null + } + } + }, + "StabilityAudioInpaint": { + "description": "بخشی از نمونه صوتی موجود را با استفاده از دستور متنی تغییر می‌دهد.", + "display_name": "تکمیل صوتی Stability AI", + "inputs": { + "audio": { + "name": "صدا", + "tooltip": "صدا باید بین ۶ تا ۱۹۰ ثانیه باشد." + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان", + "tooltip": "مدت زمان تولید شده به ثانیه را کنترل می‌کند." + }, + "mask_end": { + "name": "پایان ماسک" + }, + "mask_start": { + "name": "شروع ماسک" + }, + "model": { + "name": "مدل" + }, + "prompt": { + "name": "دستور" + }, + "seed": { + "name": "بذر", + "tooltip": "بذر تصادفی مورد استفاده برای تولید." + }, + "steps": { + "name": "گام‌ها", + "tooltip": "تعداد مراحل نمونه‌برداری را کنترل می‌کند." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StabilityAudioToAudio": { + "description": "نمونه‌های صوتی موجود را با استفاده از دستورهای متنی به ترکیب‌های جدید و باکیفیت تبدیل می‌کند.", + "display_name": "Stability AI Audio To Audio", + "inputs": { + "audio": { + "name": "صوت", + "tooltip": "فایل صوتی باید بین ۶ تا ۱۹۰ ثانیه باشد." + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان", + "tooltip": "مدت زمان (بر حسب ثانیه) صوت تولید شده را کنترل می‌کند." + }, + "model": { + "name": "مدل" + }, + "prompt": { + "name": "پرامپت" + }, + "seed": { + "name": "بذر", + "tooltip": "بذر تصادفی مورد استفاده برای تولید." + }, + "steps": { + "name": "گام‌ها", + "tooltip": "تعداد گام‌های نمونه‌برداری را کنترل می‌کند." + }, + "strength": { + "name": "شدت", + "tooltip": "این پارامتر میزان تأثیر پارامتر صوت را بر صوت تولید شده کنترل می‌کند." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StabilityStableImageSD_3_5Node": { + "description": "تصاویر را به صورت همزمان بر اساس پرامپت و وضوح تصویر تولید می‌کند.", + "display_name": "Stability AI Stable Diffusion 3.5 Image", + "inputs": { + "aspect_ratio": { + "name": "نسبت تصویر", + "tooltip": "نسبت تصویر تولید شده." + }, + "cfg_scale": { + "name": "مقیاس cfg", + "tooltip": "میزان پایبندی فرآیند diffusion به متن پرامپت (مقادیر بالاتر تصویر را به پرامپت نزدیک‌تر نگه می‌دارد)" + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "image": { + "name": "تصویر" + }, + "image_denoise": { + "name": "کاهش نویز تصویر", + "tooltip": "کاهش نویز تصویر ورودی؛ مقدار ۰.۰ تصویری مشابه ورودی ایجاد می‌کند و مقدار ۱.۰ به منزله عدم وجود تصویر ورودی است." + }, + "model": { + "name": "مدل" + }, + "negative_prompt": { + "name": "پرامپت منفی", + "tooltip": "کلمات کلیدی برای مواردی که نمی‌خواهید در تصویر خروجی مشاهده شوند. این یک ویژگی پیشرفته است." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "آنچه می‌خواهید در تصویر خروجی مشاهده کنید. یک پرامپت قوی و توصیفی که عناصر، رنگ‌ها و موضوعات را به وضوح تعریف کند، نتایج بهتری به همراه خواهد داشت." + }, + "seed": { + "name": "بذر", + "tooltip": "بذر تصادفی مورد استفاده برای ایجاد نویز." + }, + "style_preset": { + "name": "پیش‌تنظیم سبک", + "tooltip": "سبک دلخواه (اختیاری) برای تصویر تولید شده." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StabilityStableImageUltraNode": { + "description": "تولید تصویر به صورت همزمان بر اساس پرامپت و وضوح تصویر.", + "display_name": "Stability AI Stable Image Ultra", + "inputs": { + "aspect_ratio": { + "name": "نسبت تصویر", + "tooltip": "نسبت تصویر تولید شده." + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "image": { + "name": "تصویر" + }, + "image_denoise": { + "name": "کاهش نویز تصویر", + "tooltip": "کاهش نویز تصویر ورودی؛ مقدار ۰.۰ تصویری مشابه ورودی ایجاد می‌کند و مقدار ۱.۰ به منزله عدم وجود تصویر ورودی است." + }, + "negative_prompt": { + "name": "پرامپت منفی", + "tooltip": "توضیحی کوتاه درباره آنچه نمی‌خواهید در تصویر خروجی مشاهده کنید. این یک ویژگی پیشرفته است." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "آنچه می‌خواهید در تصویر خروجی مشاهده کنید. یک پرامپت قوی و توصیفی که عناصر، رنگ‌ها و موضوعات را به وضوح مشخص کند، نتایج بهتری به همراه خواهد داشت. برای کنترل وزن یک واژه از فرمت `(واژه:وزن)` استفاده کنید، که در آن `واژه` همان واژه مورد نظر و `وزن` مقداری بین ۰ تا ۱ است. برای مثال: `آسمان (آبی:۰.۳) و (سبز:۰.۸)` نشان‌دهنده آسمانی است که هم آبی و هم سبز است، اما سبز بیشتر از آبی است." + }, + "seed": { + "name": "seed", + "tooltip": "seed تصادفی مورد استفاده برای ایجاد نویز." + }, + "style_preset": { + "name": "پیش‌تنظیم سبک", + "tooltip": "سبک دلخواه (اختیاری) برای تصویر تولید شده." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StabilityTextToAudio": { + "description": "تولید موسیقی و افکت صوتی با کیفیت بالا از توضیحات متنی.", + "display_name": "Stability AI Text To Audio", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان", + "tooltip": "مدت زمان (بر حسب ثانیه) صدای تولید شده را کنترل می‌کند." + }, + "model": { + "name": "مدل" + }, + "prompt": { + "name": "پرامپت" + }, + "seed": { + "name": "seed", + "tooltip": "seed تصادفی مورد استفاده برای تولید." + }, + "steps": { + "name": "گام‌ها", + "tooltip": "تعداد گام‌های نمونه‌برداری را کنترل می‌کند." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StabilityUpscaleConservativeNode": { + "description": "بزرگ‌نمایی تصویر با کمترین تغییرات تا وضوح 4K.", + "display_name": "Stability AI Upscale Conservative", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "creativity": { + "name": "خلاقیت", + "tooltip": "احتمال ایجاد جزئیات اضافی که به شدت توسط تصویر اولیه تعیین نشده‌اند را کنترل می‌کند." + }, + "image": { + "name": "تصویر" + }, + "negative_prompt": { + "name": "پرامپت منفی", + "tooltip": "کلمات کلیدی برای آنچه نمی‌خواهید در تصویر خروجی مشاهده کنید. این یک ویژگی پیشرفته است." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "آنچه می‌خواهید در تصویر خروجی مشاهده کنید. یک پرامپت قوی و توصیفی که عناصر، رنگ‌ها و موضوعات را به وضوح مشخص کند، نتایج بهتری به همراه خواهد داشت." + }, + "seed": { + "name": "seed", + "tooltip": "seed تصادفی مورد استفاده برای ایجاد نویز." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StabilityUpscaleCreativeNode": { + "description": "بزرگ‌نمایی تصویر با کمترین تغییرات تا وضوح ۴K.", + "display_name": "Stability AI Upscale Creative", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "creativity": { + "name": "خلاقیت", + "tooltip": "احتمال ایجاد جزئیات اضافی که به تصویر اولیه وابسته نیستند را کنترل می‌کند." + }, + "image": { + "name": "تصویر" + }, + "negative_prompt": { + "name": "پرامپت منفی", + "tooltip": "کلمات کلیدی برای مواردی که نمی‌خواهید در تصویر خروجی مشاهده شوند. این یک ویژگی پیشرفته است." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "آنچه می‌خواهید در تصویر خروجی مشاهده کنید. یک پرامپت قوی و توصیفی که عناصر، رنگ‌ها و موضوعات را به‌وضوح تعریف کند، نتایج بهتری به همراه خواهد داشت." + }, + "seed": { + "name": "بذر", + "tooltip": "بذر تصادفی مورد استفاده برای ایجاد نویز." + }, + "style_preset": { + "name": "پیش‌تنظیم سبک", + "tooltip": "سبک دلخواه (اختیاری) برای تصویر تولیدشده." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StabilityUpscaleFastNode": { + "description": "بزرگ‌نمایی سریع تصویر از طریق API استبیلیتی تا ۴ برابر اندازه اصلی؛ مناسب برای بزرگ‌نمایی تصاویر کم‌کیفیت یا فشرده‌شده.", + "display_name": "Stability AI Upscale Fast", + "inputs": { + "image": { + "name": "تصویر" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StableCascade_EmptyLatentImage": { + "display_name": "StableCascade_EmptyLatentImage", + "inputs": { + "batch_size": { + "name": "اندازه بچ" + }, + "compression": { + "name": "فشرده‌سازی" + }, + "height": { + "name": "ارتفاع" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "stage_c", + "tooltip": null + }, + "1": { + "name": "stage_b", + "tooltip": null + } + } + }, + "StableCascade_StageB_Conditioning": { + "display_name": "StableCascade_StageB_Conditioning", + "inputs": { + "conditioning": { + "name": "شرط‌گذاری" + }, + "stage_c": { + "name": "stage_c" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StableCascade_StageC_VAEEncode": { + "display_name": "StableCascade_StageC_VAEEncode", + "inputs": { + "compression": { + "name": "فشرده‌سازی" + }, + "image": { + "name": "تصویر" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "stage_c", + "tooltip": null + }, + "1": { + "name": "stage_b", + "tooltip": null + } + } + }, + "StableCascade_SuperResolutionControlnet": { + "display_name": "StableCascade_SuperResolutionControlnet", + "inputs": { + "image": { + "name": "تصویر" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "controlnet_input", + "tooltip": null + }, + "1": { + "name": "stage_c", + "tooltip": null + }, + "2": { + "name": "stage_b", + "tooltip": null + } + } + }, + "StableZero123_Conditioning": { + "display_name": "StableZero123_Conditioning", + "inputs": { + "azimuth": { + "name": "زاویه سمت" + }, + "batch_size": { + "name": "اندازه دسته" + }, + "clip_vision": { + "name": "clip_vision" + }, + "elevation": { + "name": "زاویه ارتفاع" + }, + "height": { + "name": "ارتفاع" + }, + "init_image": { + "name": "تصویر اولیه" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "StableZero123_Conditioning_Batched": { + "display_name": "StableZero123_Conditioning_Batched", + "inputs": { + "azimuth": { + "name": "زاویه سمت" + }, + "azimuth_batch_increment": { + "name": "افزایش دسته‌ای سمت" + }, + "batch_size": { + "name": "اندازه دسته" + }, + "clip_vision": { + "name": "clip_vision" + }, + "elevation": { + "name": "زاویه ارتفاع" + }, + "elevation_batch_increment": { + "name": "افزایش دسته‌ای ارتفاع" + }, + "height": { + "name": "ارتفاع" + }, + "init_image": { + "name": "تصویر اولیه" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "StringCompare": { + "display_name": "مقایسه", + "inputs": { + "case_sensitive": { + "name": "حساس به حروف بزرگ و کوچک" + }, + "mode": { + "name": "حالت" + }, + "string_a": { + "name": "رشته الف" + }, + "string_b": { + "name": "رشته ب" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StringConcatenate": { + "display_name": "ادغام", + "inputs": { + "delimiter": { + "name": "جداکننده" + }, + "string_a": { + "name": "رشته الف" + }, + "string_b": { + "name": "رشته ب" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StringContains": { + "display_name": "شامل بودن", + "inputs": { + "case_sensitive": { + "name": "حساس به حروف بزرگ و کوچک" + }, + "string": { + "name": "رشته" + }, + "substring": { + "name": "زیررشته" + } + }, + "outputs": { + "0": { + "name": "شامل", + "tooltip": null + } + } + }, + "StringLength": { + "display_name": "طول", + "inputs": { + "string": { + "name": "رشته" + } + }, + "outputs": { + "0": { + "name": "طول", + "tooltip": null + } + } + }, + "StringReplace": { + "display_name": "جایگزینی", + "inputs": { + "find": { + "name": "یافتن" + }, + "replace": { + "name": "جایگزین" + }, + "string": { + "name": "رشته" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StringSubstring": { + "display_name": "زیررشته", + "inputs": { + "end": { + "name": "پایان" + }, + "start": { + "name": "شروع" + }, + "string": { + "name": "رشته" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StringTrim": { + "display_name": "حذف فاصله", + "inputs": { + "mode": { + "name": "حالت" + }, + "string": { + "name": "رشته" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StripWhitespace": { + "display_name": "حذف فاصله‌های اضافی", + "inputs": { + "texts": { + "name": "متن‌ها", + "tooltip": "متنی که باید پردازش شود." + } + }, + "outputs": { + "0": { + "name": "متن‌های پردازش‌شده", + "tooltip": "متن‌های پردازش‌شده" + } + } + }, + "StyleModelApply": { + "display_name": "اعمال مدل سبک", + "inputs": { + "clip_vision_output": { + "name": "خروجی clip vision" + }, + "conditioning": { + "name": "شرط‌گذاری" + }, + "strength": { + "name": "شدت" + }, + "strength_type": { + "name": "نوع شدت" + }, + "style_model": { + "name": "مدل سبک" + } + } + }, + "StyleModelLoader": { + "display_name": "بارگذاری مدل سبک", + "inputs": { + "style_model_name": { + "name": "نام مدل سبک" + } + } + }, + "T5TokenizerOptions": { + "display_name": "گزینه‌های T5Tokenizer", + "inputs": { + "clip": { + "name": "clip" + }, + "min_length": { + "name": "حداقل طول" + }, + "min_padding": { + "name": "حداقل padding" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TCFG": { + "description": "TCFG – کاهنده مماسی CFG (۲۵۰۳.۱۸۱۳۷)\n\nبهبود کیفیت با هم‌تراز کردن uncond (منفی) با cond (مثبت).", + "display_name": "کاهنده مماسی CFG", + "inputs": { + "model": { + "name": "مدل" + } + }, + "outputs": { + "0": { + "name": "مدل اصلاح‌شده", + "tooltip": null + } + } + }, + "TemporalScoreRescaling": { + "description": "[عملکرد پس از CFG]\nTSR - بازمقیاس‌دهی امتیاز زمانی (۲۵۱۰.۰۱۱۸۴)\n\nبازمقیاس‌دهی امتیاز یا نویز مدل برای هدایت تنوع نمونه‌گیری.\n", + "display_name": "TSR - بازمقیاس‌دهی امتیاز زمانی", + "inputs": { + "model": { + "name": "مدل" + }, + "tsr_k": { + "name": "tsr_k", + "tooltip": "قدرت بازمقیاس‌دهی را کنترل می‌کند.\nمقدار کمتر k نتایج دقیق‌تری تولید می‌کند؛ مقدار بالاتر k نتایج نرم‌تری در تولید تصویر ایجاد می‌کند. مقدار k = ۱ بازمقیاس‌دهی را غیرفعال می‌کند." + }, + "tsr_sigma": { + "name": "tsr_sigma", + "tooltip": "کنترل می‌کند که بازمقیاس‌دهی از چه زمانی اعمال شود.\nمقادیر بزرگ‌تر، زودتر اثر می‌گذارند." + } + }, + "outputs": { + "0": { + "name": "مدل اصلاح‌شده", + "tooltip": null + } + } + }, + "TencentImageToModelNode": { + "display_name": "Hunyuan3D: تبدیل تصویر(ها) به مدل (پیشرفته)", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "face_count": { + "name": "تعداد وجه‌ها" + }, + "generate_type": { + "name": "نوع تولید" + }, + "generate_type_pbr": { + "name": "pbr" + }, + "image": { + "name": "تصویر" + }, + "image_back": { + "name": "تصویر پشت" + }, + "image_left": { + "name": "تصویر چپ" + }, + "image_right": { + "name": "تصویر راست" + }, + "model": { + "name": "مدل", + "tooltip": "گزینه LowPoly برای مدل `۳.۱` در دسترس نیست." + }, + "seed": { + "name": "seed", + "tooltip": "seed تعیین می‌کند که node باید دوباره اجرا شود یا خیر؛ نتایج صرف‌نظر از seed غیرقطعی هستند." + } + }, + "outputs": { + "0": { + "name": "فایل مدل", + "tooltip": null + } + } + }, + "TencentTextToModelNode": { + "display_name": "Hunyuan3D: تبدیل متن به مدل (پیشرفته)", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "face_count": { + "name": "تعداد وجه‌ها" + }, + "generate_type": { + "name": "نوع تولید" + }, + "generate_type_pbr": { + "name": "pbr" + }, + "model": { + "name": "مدل", + "tooltip": "گزینه LowPoly برای مدل `۳.۱` در دسترس نیست." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "حداکثر تا ۱۰۲۴ کاراکتر پشتیبانی می‌شود." + }, + "seed": { + "name": "seed", + "tooltip": "seed تعیین می‌کند که node باید دوباره اجرا شود یا خیر؛ نتایج صرف‌نظر از seed غیرقطعی هستند." + } + }, + "outputs": { + "0": { + "name": "فایل مدل", + "tooltip": null + } + } + }, + "TextEncodeAceStepAudio": { + "display_name": "TextEncodeAceStepAudio", + "inputs": { + "clip": { + "name": "clip" + }, + "lyrics": { + "name": "متن ترانه" + }, + "lyrics_strength": { + "name": "قدرت متن ترانه" + }, + "tags": { + "name": "برچسب‌ها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TextEncodeHunyuanVideo_ImageToVideo": { + "display_name": "TextEncodeHunyuanVideo_ImageToVideo", + "inputs": { + "clip": { + "name": "clip" + }, + "clip_vision_output": { + "name": "خروجی بینایی clip" + }, + "image_interleave": { + "name": "درهم‌تنیدگی تصویر", + "tooltip": "میزان تأثیر تصویر در مقابل پرامپت متنی. عدد بالاتر یعنی تأثیر بیشتر پرامپت متنی." + }, + "prompt": { + "name": "پرامپت" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TextEncodeQwenImageEdit": { + "display_name": "TextEncodeQwenImageEdit", + "inputs": { + "clip": { + "name": "clip" + }, + "image": { + "name": "تصویر" + }, + "prompt": { + "name": "پرامپت" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TextEncodeQwenImageEditPlus": { + "display_name": "TextEncodeQwenImageEditPlus", + "inputs": { + "clip": { + "name": "clip" + }, + "image1": { + "name": "تصویر ۱" + }, + "image2": { + "name": "تصویر ۲" + }, + "image3": { + "name": "تصویر ۳" + }, + "prompt": { + "name": "پرامپت" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TextEncodeZImageOmni": { + "display_name": "TextEncodeZImageOmni", + "inputs": { + "auto_resize_images": { + "name": "تغییر اندازه خودکار تصاویر" + }, + "clip": { + "name": "clip" + }, + "image1": { + "name": "تصویر ۱" + }, + "image2": { + "name": "تصویر ۲" + }, + "image3": { + "name": "تصویر ۳" + }, + "image_encoder": { + "name": "رمزگذار تصویر" + }, + "prompt": { + "name": "پرامپت" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TextToLowercase": { + "display_name": "تبدیل متن به حروف کوچک", + "inputs": { + "texts": { + "name": "متن‌ها", + "tooltip": "متنی که باید پردازش شود." + } + }, + "outputs": { + "0": { + "name": "متن‌های پردازش‌شده", + "tooltip": "متن‌های پردازش‌شده" + } + } + }, + "TextToUppercase": { + "display_name": "تبدیل متن به حروف بزرگ", + "inputs": { + "texts": { + "name": "متن‌ها", + "tooltip": "متنی که باید پردازش شود." + } + }, + "outputs": { + "0": { + "name": "متن‌های پردازش‌شده", + "tooltip": "متن‌های پردازش‌شده" + } + } + }, + "ThresholdMask": { + "display_name": "ThresholdMask", + "inputs": { + "mask": { + "name": "ماسک" + }, + "value": { + "name": "مقدار" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TomePatchModel": { + "display_name": "TomePatchModel", + "inputs": { + "model": { + "name": "مدل" + }, + "ratio": { + "name": "نسبت" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TopazImageEnhance": { + "description": "استاندارد صنعتی برای بزرگ‌نمایی و بهبود تصویر.", + "display_name": "افزایش کیفیت تصویر Topaz", + "inputs": { + "color_preservation": { + "name": "حفظ رنگ", + "tooltip": "رنگ‌های اصلی را حفظ می‌کند." + }, + "creativity": { + "name": "خلاقیت" + }, + "crop_to_fill": { + "name": "برش برای پر کردن", + "tooltip": "به طور پیش‌فرض، اگر نسبت تصویر خروجی متفاوت باشد، تصویر letterbox می‌شود. برای برش تصویر و پر کردن ابعاد خروجی فعال کنید." + }, + "face_enhancement": { + "name": "بهبود چهره", + "tooltip": "در صورت وجود، چهره‌ها را هنگام پردازش بهبود می‌بخشد." + }, + "face_enhancement_creativity": { + "name": "خلاقیت در بهبود چهره", + "tooltip": "سطح خلاقیت برای بهبود چهره را تعیین کنید." + }, + "face_enhancement_strength": { + "name": "قدرت بهبود چهره", + "tooltip": "کنترل میزان وضوح چهره‌های بهبود یافته نسبت به پس‌زمینه." + }, + "face_preservation": { + "name": "حفظ چهره", + "tooltip": "هویت چهره سوژه‌ها را حفظ می‌کند." + }, + "image": { + "name": "تصویر" + }, + "model": { + "name": "مدل" + }, + "output_height": { + "name": "ارتفاع خروجی", + "tooltip": "مقدار صفر به معنی خروجی با همان ارتفاع اصلی یا عرض خروجی است." + }, + "output_width": { + "name": "عرض خروجی", + "tooltip": "مقدار صفر به معنی محاسبه خودکار است (معمولاً اندازه اصلی یا ارتفاع خروجی در صورت تعیین)." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپت متنی اختیاری برای راهنمایی خلاقانه در بزرگ‌نمایی." + }, + "subject_detection": { + "name": "شناسایی سوژه" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TopazVideoEnhance": { + "description": "با استفاده از فناوری قدرتمند بزرگ‌نمایی و بازیابی، به ویدیو جان تازه‌ای ببخشید.", + "display_name": "Topaz Video Enhance", + "inputs": { + "dynamic_compression_level": { + "name": "سطح فشرده‌سازی پویا", + "tooltip": "سطح CQP." + }, + "interpolation_duplicate": { + "name": "حذف فریم‌های تکراری", + "tooltip": "ورودی را برای فریم‌های تکراری تحلیل و آن‌ها را حذف می‌کند." + }, + "interpolation_duplicate_threshold": { + "name": "حساسیت تشخیص فریم تکراری", + "tooltip": "حساسیت تشخیص برای فریم‌های تکراری." + }, + "interpolation_enabled": { + "name": "فعال‌سازی درون‌یابی" + }, + "interpolation_frame_rate": { + "name": "نرخ فریم خروجی", + "tooltip": "نرخ فریم خروجی." + }, + "interpolation_model": { + "name": "مدل درون‌یابی" + }, + "interpolation_slowmo": { + "name": "ضریب حرکت آهسته", + "tooltip": "ضریب حرکت آهسته که به ویدیوی ورودی اعمال می‌شود. برای مثال، ۲ باعث می‌شود خروجی دو برابر آهسته‌تر و مدت زمان دو برابر شود." + }, + "upscaler_creativity": { + "name": "سطح خلاقیت", + "tooltip": "سطح خلاقیت (فقط برای Starlight (Astra) Creative اعمال می‌شود)." + }, + "upscaler_enabled": { + "name": "فعال‌سازی بزرگ‌نمایی" + }, + "upscaler_model": { + "name": "مدل بزرگ‌نمایی" + }, + "upscaler_resolution": { + "name": "وضوح بزرگ‌نمایی" + }, + "video": { + "name": "ویدیو" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TorchCompileModel": { + "display_name": "TorchCompileModel", + "inputs": { + "backend": { + "name": "بک‌اند" + }, + "model": { + "name": "مدل" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TrainLoraNode": { + "display_name": "آموزش LoRA", + "inputs": { + "algorithm": { + "name": "الگوریتم", + "tooltip": "الگوریتم مورد استفاده برای آموزش." + }, + "batch_size": { + "name": "اندازه بچ", + "tooltip": "اندازه بچ مورد استفاده برای آموزش." + }, + "bucket_mode": { + "name": "حالت سطل‌بندی رزولوشن", + "tooltip": "فعال‌سازی حالت سطل‌بندی رزولوشن. در صورت فعال بودن، انتظار می‌رود لاتنت‌های پیش‌سطل‌بندی شده از node ResolutionBucket دریافت شود." + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "existing_lora": { + "name": "LoRA موجود", + "tooltip": "LoRA موجود برای افزودن. برای ایجاد LoRA جدید مقدار None را قرار دهید." + }, + "grad_accumulation_steps": { + "name": "تعداد مراحل انباشت گرادیان", + "tooltip": "تعداد مراحل انباشت گرادیان برای آموزش." + }, + "gradient_checkpointing": { + "name": "ذخیره‌سازی گرادیان", + "tooltip": "استفاده از ذخیره‌سازی گرادیان برای آموزش." + }, + "latents": { + "name": "لاتنت‌ها", + "tooltip": "لاتنت‌هایی که برای آموزش استفاده می‌شوند و به عنوان دیتاست/ورودی مدل عمل می‌کنند." + }, + "learning_rate": { + "name": "نرخ یادگیری", + "tooltip": "نرخ یادگیری مورد استفاده برای آموزش." + }, + "lora_dtype": { + "name": "نوع داده LoRA", + "tooltip": "نوع داده مورد استفاده برای LoRA." + }, + "loss_function": { + "name": "تابع خطا", + "tooltip": "تابع خطا مورد استفاده برای آموزش." + }, + "model": { + "name": "مدل", + "tooltip": "مدلی که LoRA روی آن آموزش داده می‌شود." + }, + "optimizer": { + "name": "بهینه‌ساز", + "tooltip": "بهینه‌سازی که برای آموزش استفاده می‌شود." + }, + "positive": { + "name": "شرط مثبت", + "tooltip": "شرط مثبت مورد استفاده برای آموزش." + }, + "rank": { + "name": "رتبه", + "tooltip": "رتبه لایه‌های LoRA." + }, + "seed": { + "name": "بذر", + "tooltip": "بذر مورد استفاده برای آموزش (در تولیدکننده برای مقداردهی اولیه وزن‌های LoRA و نمونه‌گیری نویز استفاده می‌شود)" + }, + "steps": { + "name": "تعداد مراحل", + "tooltip": "تعداد مراحل آموزش LoRA." + }, + "training_dtype": { + "name": "نوع داده آموزش", + "tooltip": "نوع داده مورد استفاده برای آموزش." + } + }, + "outputs": { + "0": { + "name": "مدل", + "tooltip": "مدل با LoRA اعمال شده" + }, + "1": { + "name": "وزن‌های LoRA", + "tooltip": "وزن‌های LoRA" + }, + "2": { + "name": "نقشه خطا", + "tooltip": "تاریخچه خطا" + }, + "3": { + "name": "مراحل", + "tooltip": "کل مراحل آموزش" + } + } + }, + "TrimAudioDuration": { + "description": "برش تانسور صوت به بازه زمانی انتخاب شده.", + "display_name": "برش مدت زمان صوت", + "inputs": { + "audio": { + "name": "صوت" + }, + "duration": { + "name": "مدت زمان", + "tooltip": "مدت زمان بر حسب ثانیه" + }, + "start_index": { + "name": "زمان شروع", + "tooltip": "زمان شروع بر حسب ثانیه، می‌تواند منفی باشد تا از انتها شمارش شود (پشتیبانی از زیرثانیه)." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TrimVideoLatent": { + "display_name": "برش لاتنت ویدیو", + "inputs": { + "samples": { + "name": "نمونه‌ها" + }, + "trim_amount": { + "name": "مقدار برش" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TripleCLIPLoader": { + "description": "[دستورالعمل‌ها]\n\nsd3: clip-l، clip-g، t5", + "display_name": "TripleCLIPLoader", + "inputs": { + "clip_name1": { + "name": "clip_name1" + }, + "clip_name2": { + "name": "clip_name2" + }, + "clip_name3": { + "name": "clip_name3" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TripoConversionNode": { + "display_name": "Tripo: تبدیل مدل", + "inputs": { + "animate_in_place": { + "name": "انیمیشن در محل" + }, + "bake": { + "name": "بِیک" + }, + "export_orientation": { + "name": "خروجی جهت" + }, + "export_vertex_colors": { + "name": "خروجی رنگ رأس‌ها" + }, + "face_limit": { + "name": "محدودیت چهره" + }, + "fbx_preset": { + "name": "پیش‌تنظیم FBX" + }, + "flatten_bottom": { + "name": "تخت کردن پایین" + }, + "flatten_bottom_threshold": { + "name": "آستانه تخت کردن پایین" + }, + "force_symmetry": { + "name": "اجبار تقارن" + }, + "format": { + "name": "فرمت" + }, + "original_model_task_id": { + "name": "original_model_task_id" + }, + "pack_uv": { + "name": "بسته‌بندی UV" + }, + "part_names": { + "name": "نام بخش‌ها" + }, + "pivot_to_center_bottom": { + "name": "محور به مرکز پایین" + }, + "quad": { + "name": "quad" + }, + "scale_factor": { + "name": "ضریب مقیاس" + }, + "texture_format": { + "name": "فرمت تکسچر" + }, + "texture_size": { + "name": "اندازه تکسچر" + }, + "with_animation": { + "name": "با انیمیشن" + } + } + }, + "TripoImageToModelNode": { + "display_name": "Tripo: تصویر به مدل", + "inputs": { + "face_limit": { + "name": "محدودیت چهره" + }, + "geometry_quality": { + "name": "کیفیت هندسه" + }, + "image": { + "name": "تصویر" + }, + "model_seed": { + "name": "بذر مدل" + }, + "model_version": { + "name": "نسخه مدل", + "tooltip": "نسخه مدل مورد استفاده برای تولید" + }, + "orientation": { + "name": "جهت" + }, + "pbr": { + "name": "PBR" + }, + "quad": { + "name": "quad" + }, + "style": { + "name": "سبک" + }, + "texture": { + "name": "تکسچر" + }, + "texture_alignment": { + "name": "تراز تکسچر" + }, + "texture_quality": { + "name": "کیفیت تکسچر" + }, + "texture_seed": { + "name": "بذر تکسچر" + } + }, + "outputs": { + "0": { + "name": "فایل مدل", + "tooltip": null + }, + "1": { + "name": "شناسه وظیفه مدل", + "tooltip": null + } + } + }, + "TripoMultiviewToModelNode": { + "display_name": "Tripo: چندنما به مدل", + "inputs": { + "face_limit": { + "name": "محدودیت وجه" + }, + "geometry_quality": { + "name": "کیفیت هندسه" + }, + "image": { + "name": "تصویر" + }, + "image_back": { + "name": "تصویر پشت" + }, + "image_left": { + "name": "تصویر چپ" + }, + "image_right": { + "name": "تصویر راست" + }, + "model_seed": { + "name": "بذر مدل" + }, + "model_version": { + "name": "نسخه مدل", + "tooltip": "نسخه مدل مورد استفاده برای تولید" + }, + "orientation": { + "name": "جهت" + }, + "pbr": { + "name": "PBR" + }, + "quad": { + "name": "چهارضلعی" + }, + "texture": { + "name": "تکسچر" + }, + "texture_alignment": { + "name": "تراز تکسچر" + }, + "texture_quality": { + "name": "کیفیت تکسچر" + }, + "texture_seed": { + "name": "بذر تکسچر" + } + }, + "outputs": { + "0": { + "name": "فایل مدل", + "tooltip": null + }, + "1": { + "name": "شناسه وظیفه مدل", + "tooltip": null + } + } + }, + "TripoRefineNode": { + "description": "بهبود یک مدل پیش‌نویس که فقط توسط مدل‌های Tripo نسخه ۱.۴ ایجاد شده است.", + "display_name": "Tripo: بهبود مدل پیش‌نویس", + "inputs": { + "model_task_id": { + "name": "شناسه وظیفه مدل", + "tooltip": "باید یک مدل Tripo نسخه ۱.۴ باشد" + } + }, + "outputs": { + "0": { + "name": "فایل مدل", + "tooltip": null + }, + "1": { + "name": "شناسه وظیفه مدل", + "tooltip": null + } + } + }, + "TripoRetargetNode": { + "display_name": "Tripo: هدف‌گذاری مجدد مدل ریگ‌شده", + "inputs": { + "animation": { + "name": "انیمیشن" + }, + "original_model_task_id": { + "name": "شناسه وظیفه مدل اصلی" + } + }, + "outputs": { + "0": { + "name": "فایل مدل", + "tooltip": null + }, + "1": { + "name": "شناسه وظیفه هدف‌گذاری مجدد", + "tooltip": null + } + } + }, + "TripoRigNode": { + "display_name": "Tripo: ریگ مدل", + "inputs": { + "original_model_task_id": { + "name": "شناسه وظیفه مدل اصلی" + } + }, + "outputs": { + "0": { + "name": "فایل مدل", + "tooltip": null + }, + "1": { + "name": "شناسه وظیفه ریگ", + "tooltip": null + } + } + }, + "TripoTextToModelNode": { + "display_name": "Tripo: متن به مدل", + "inputs": { + "face_limit": { + "name": "محدودیت وجه" + }, + "geometry_quality": { + "name": "کیفیت هندسه" + }, + "image_seed": { + "name": "بذر تصویر" + }, + "model_seed": { + "name": "بذر مدل" + }, + "model_version": { + "name": "نسخه مدل" + }, + "negative_prompt": { + "name": "پرامپت منفی" + }, + "pbr": { + "name": "PBR" + }, + "prompt": { + "name": "پرامپت" + }, + "quad": { + "name": "چهارضلعی" + }, + "style": { + "name": "استایل" + }, + "texture": { + "name": "تکسچر" + }, + "texture_quality": { + "name": "کیفیت تکسچر" + }, + "texture_seed": { + "name": "بذر تکسچر" + } + }, + "outputs": { + "0": { + "name": "فایل مدل", + "tooltip": null + }, + "1": { + "name": "شناسه وظیفه مدل", + "tooltip": null + } + } + }, + "TripoTextureNode": { + "display_name": "Tripo: مدل بافت", + "inputs": { + "model_task_id": { + "name": "شناسه وظیفه مدل" + }, + "pbr": { + "name": "PBR" + }, + "texture": { + "name": "بافت" + }, + "texture_alignment": { + "name": "تراز بافت" + }, + "texture_quality": { + "name": "کیفیت بافت" + }, + "texture_seed": { + "name": "بذر بافت" + } + }, + "outputs": { + "0": { + "name": "فایل مدل", + "tooltip": null + }, + "1": { + "name": "شناسه وظیفه مدل", + "tooltip": null + } + } + }, + "TruncateText": { + "display_name": "کوتاه‌سازی متن", + "inputs": { + "max_length": { + "name": "حداکثر طول", + "tooltip": "حداکثر طول متن." + }, + "texts": { + "name": "متن‌ها", + "tooltip": "متنی که باید پردازش شود." + } + }, + "outputs": { + "0": { + "name": "متن‌های پردازش‌شده", + "tooltip": "متن‌های پردازش‌شده" + } + } + }, + "UNETLoader": { + "display_name": "بارگذاری مدل Diffusion", + "inputs": { + "unet_name": { + "name": "نام UNet" + }, + "weight_dtype": { + "name": "نوع داده وزن" + } + } + }, + "UNetCrossAttentionMultiply": { + "display_name": "ضرب توجه متقاطع UNet", + "inputs": { + "k": { + "name": "k" + }, + "model": { + "name": "مدل" + }, + "out": { + "name": "خروجی" + }, + "q": { + "name": "q" + }, + "v": { + "name": "v" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "UNetSelfAttentionMultiply": { + "display_name": "ضرب توجه به خود UNet", + "inputs": { + "k": { + "name": "k" + }, + "model": { + "name": "مدل" + }, + "out": { + "name": "خروجی" + }, + "q": { + "name": "q" + }, + "v": { + "name": "v" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "UNetTemporalAttentionMultiply": { + "display_name": "ضرب توجه زمانی UNet", + "inputs": { + "cross_structural": { + "name": "ساختاری متقاطع" + }, + "cross_temporal": { + "name": "زمانی متقاطع" + }, + "model": { + "name": "مدل" + }, + "self_structural": { + "name": "ساختاری خود" + }, + "self_temporal": { + "name": "زمانی خود" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "USOStyleReference": { + "display_name": "USOStyleReference", + "inputs": { + "clip_vision_output": { + "name": "خروجی clip vision" + }, + "model": { + "name": "مدل" + }, + "model_patch": { + "name": "patch مدل" + } + } + }, + "UpscaleModelLoader": { + "display_name": "بارگذاری مدل بزرگ‌نمایی", + "inputs": { + "model_name": { + "name": "نام مدل" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "VAEDecode": { + "description": "تصاویر latent را به تصاویر پیکسلی بازمی‌گرداند.", + "display_name": "رمزگشایی VAE", + "inputs": { + "samples": { + "name": "نمونه‌ها", + "tooltip": "latent برای رمزگشایی." + }, + "vae": { + "name": "vae", + "tooltip": "مدل VAE مورد استفاده برای رمزگشایی latent." + } + }, + "outputs": { + "0": { + "tooltip": "تصویر رمزگشایی‌شده." + } + } + }, + "VAEDecodeAudio": { + "display_name": "رمزگشایی VAE صوتی", + "inputs": { + "samples": { + "name": "نمونه‌ها" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "VAEDecodeHunyuan3D": { + "display_name": "VAEDecodeHunyuan3D", + "inputs": { + "num_chunks": { + "name": "تعداد بخش‌ها" + }, + "octree_resolution": { + "name": "وضوح octree" + }, + "samples": { + "name": "نمونه‌ها" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "VAEDecodeTiled": { + "display_name": "رمزگشایی VAE (کاشی‌بندی‌شده)", + "inputs": { + "overlap": { + "name": "همپوشانی" + }, + "samples": { + "name": "نمونه‌ها" + }, + "temporal_overlap": { + "name": "همپوشانی زمانی", + "tooltip": "فقط برای VAE ویدیویی: تعداد فریم‌هایی که همپوشانی دارند." + }, + "temporal_size": { + "name": "اندازه زمانی", + "tooltip": "فقط برای VAE ویدیویی: تعداد فریم‌هایی که همزمان رمزگشایی می‌شوند." + }, + "tile_size": { + "name": "اندازه کاشی" + }, + "vae": { + "name": "vae" + } + } + }, + "VAEEncode": { + "display_name": "کدگذاری VAE", + "inputs": { + "pixels": { + "name": "پیکسل‌ها" + }, + "vae": { + "name": "vae" + } + } + }, + "VAEEncodeAudio": { + "display_name": "کدگذاری VAE صوتی", + "inputs": { + "audio": { + "name": "صوت" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "VAEEncodeForInpaint": { + "display_name": "کدگذاری VAE (برای inpainting)", + "inputs": { + "grow_mask_by": { + "name": "افزایش ماسک به اندازه" + }, + "mask": { + "name": "ماسک" + }, + "pixels": { + "name": "پیکسل‌ها" + }, + "vae": { + "name": "vae" + } + } + }, + "VAEEncodeTiled": { + "display_name": "کدگذاری VAE (کاشی‌بندی‌شده)", + "inputs": { + "overlap": { + "name": "همپوشانی" + }, + "pixels": { + "name": "پیکسل‌ها" + }, + "temporal_overlap": { + "name": "همپوشانی زمانی", + "tooltip": "فقط برای VAE ویدیویی: تعداد فریم‌هایی که همپوشانی دارند." + }, + "temporal_size": { + "name": "اندازه زمانی", + "tooltip": "فقط برای VAE ویدیویی: تعداد فریم‌هایی که همزمان کدگذاری می‌شوند." + }, + "tile_size": { + "name": "اندازه کاشی" + }, + "vae": { + "name": "vae" + } + } + }, + "VAELoader": { + "display_name": "بارگذاری VAE", + "inputs": { + "vae_name": { + "name": "vae_name" + } + } + }, + "VAESave": { + "display_name": "ذخیره VAE", + "inputs": { + "filename_prefix": { + "name": "پیشوند نام فایل" + }, + "vae": { + "name": "vae" + } + } + }, + "VPScheduler": { + "display_name": "VPScheduler", + "inputs": { + "beta_d": { + "name": "بتا d" + }, + "beta_min": { + "name": "حداقل بتا" + }, + "eps_s": { + "name": "اپسیلون s" + }, + "steps": { + "name": "گام‌ها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Veo3FirstLastFrameNode": { + "description": "تولید ویدیو با استفاده از پرامپت و اولین و آخرین فریم.", + "display_name": "Google Veo 3 تبدیل اولین و آخرین فریم به ویدیو", + "inputs": { + "aspect_ratio": { + "name": "نسبت تصویر", + "tooltip": "نسبت تصویر ویدیوی خروجی" + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان", + "tooltip": "مدت زمان ویدیوی خروجی به ثانیه" + }, + "first_frame": { + "name": "اولین فریم", + "tooltip": "فریم شروع" + }, + "generate_audio": { + "name": "تولید صدا", + "tooltip": "تولید صدا برای ویدیو." + }, + "last_frame": { + "name": "آخرین فریم", + "tooltip": "فریم پایان" + }, + "model": { + "name": "مدل" + }, + "negative_prompt": { + "name": "پرامپت منفی", + "tooltip": "پرامپت متنی منفی برای راهنمایی جهت اجتناب از موارد خاص در ویدیو" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "توضیح متنی ویدیو" + }, + "resolution": { + "name": "رزولوشن" + }, + "seed": { + "name": "seed", + "tooltip": "seed برای تولید ویدیو" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Veo3VideoGenerationNode": { + "description": "تولید ویدیو از پرامپت متنی با استفاده از API گوگل Veo 3", + "display_name": "تولید ویدیو Google Veo 3", + "inputs": { + "aspect_ratio": { + "name": "نسبت تصویر", + "tooltip": "نسبت تصویر ویدیوی خروجی" + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration_seconds": { + "name": "مدت زمان (ثانیه)", + "tooltip": "مدت زمان ویدیوی خروجی به ثانیه (Veo 3 فقط از ۸ ثانیه پشتیبانی می‌کند)" + }, + "enhance_prompt": { + "name": "enhance_prompt", + "tooltip": "این پارامتر منسوخ شده و نادیده گرفته می‌شود." + }, + "generate_audio": { + "name": "تولید صدا", + "tooltip": "تولید صدا برای ویدیو. توسط تمام مدل‌های Veo 3 پشتیبانی می‌شود." + }, + "image": { + "name": "تصویر", + "tooltip": "تصویر مرجع اختیاری برای راهنمایی تولید ویدیو" + }, + "model": { + "name": "مدل", + "tooltip": "مدل Veo 3 برای تولید ویدیو" + }, + "negative_prompt": { + "name": "پرامپت منفی", + "tooltip": "پرامپت متنی منفی برای راهنمایی جهت اجتناب از موارد خاص در ویدیو" + }, + "person_generation": { + "name": "تولید افراد", + "tooltip": "آیا تولید افراد در ویدیو مجاز باشد" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "توضیح متنی ویدیو" + }, + "seed": { + "name": "seed", + "tooltip": "seed برای تولید ویدیو (۰ برای تصادفی)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "VeoVideoGenerationNode": { + "description": "تولید ویدیو از طریق متن با استفاده از API گوگل Veo ۲", + "display_name": "تولید ویدیو با Google Veo 2", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "نسبت تصویر ویدیوی خروجی" + }, + "control_after_generate": { + "name": "control after generate" + }, + "duration_seconds": { + "name": "duration_seconds", + "tooltip": "مدت زمان ویدیوی خروجی بر حسب ثانیه" + }, + "enhance_prompt": { + "name": "enhance_prompt", + "tooltip": "آیا توضیح متنی با کمک هوش مصنوعی بهبود یابد" + }, + "image": { + "name": "image", + "tooltip": "تصویر مرجع اختیاری برای راهنمایی تولید ویدیو" + }, + "model": { + "name": "model", + "tooltip": "مدل Veo ۲ برای تولید ویدیو" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "راهنمای متنی منفی برای تعیین مواردی که باید در ویدیو اجتناب شود" + }, + "person_generation": { + "name": "person_generation", + "tooltip": "آیا تولید افراد در ویدیو مجاز باشد" + }, + "prompt": { + "name": "prompt", + "tooltip": "توضیح متنی برای ویدیو" + }, + "seed": { + "name": "seed", + "tooltip": "Seed برای تولید ویدیو (۰ برای تصادفی)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "VideoLinearCFGGuidance": { + "display_name": "VideoLinearCFGGuidance", + "inputs": { + "min_cfg": { + "name": "min_cfg" + }, + "model": { + "name": "model" + } + } + }, + "VideoTriangleCFGGuidance": { + "display_name": "VideoTriangleCFGGuidance", + "inputs": { + "min_cfg": { + "name": "min_cfg" + }, + "model": { + "name": "model" + } + } + }, + "Vidu2ImageToVideoNode": { + "description": "تولید ویدیو از یک تصویر و توضیح متنی اختیاری.", + "display_name": "تولید ویدیو از تصویر با Vidu2", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "duration": { + "name": "duration" + }, + "image": { + "name": "image", + "tooltip": "تصویری که به عنوان فریم ابتدایی ویدیوی تولیدی استفاده می‌شود." + }, + "model": { + "name": "model" + }, + "movement_amplitude": { + "name": "movement_amplitude", + "tooltip": "دامنه حرکت اشیاء در فریم." + }, + "prompt": { + "name": "prompt", + "tooltip": "توضیح متنی اختیاری برای تولید ویدیو (حداکثر ۲۰۰۰ کاراکتر)." + }, + "resolution": { + "name": "resolution" + }, + "seed": { + "name": "seed" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2ReferenceVideoNode": { + "description": "تولید ویدیو از چند تصویر مرجع و یک پرامپت.", + "display_name": "تولید ویدیو از تصاویر مرجع Vidu2", + "inputs": { + "aspect_ratio": { + "name": "نسبت تصویر" + }, + "audio": { + "name": "صدا", + "tooltip": "در صورت فعال بودن، ویدیو شامل گفتار تولیدشده و موسیقی پس‌زمینه بر اساس پرامپت خواهد بود." + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان" + }, + "model": { + "name": "مدل" + }, + "movement_amplitude": { + "name": "دامنه حرکت", + "tooltip": "دامنه حرکت اشیاء در قاب." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "در صورت فعال بودن، ویدیو شامل گفتار تولیدشده و موسیقی پس‌زمینه بر اساس پرامپت خواهد بود." + }, + "resolution": { + "name": "رزولوشن" + }, + "seed": { + "name": "seed" + }, + "subjects": { + "name": "subjects", + "tooltip": "برای هر subject، تا ۳ تصویر مرجع ارائه دهید (در مجموع ۷ تصویر برای همه subjects). در پرامپت‌ها با @subject{subject_id} به آن‌ها ارجاع دهید." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2StartEndToVideoNode": { + "description": "تولید ویدیو از یک فریم شروع، یک فریم پایان و یک پرامپت.", + "display_name": "تولید ویدیو از فریم شروع/پایان Vidu2", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان" + }, + "end_frame": { + "name": "فریم پایان" + }, + "first_frame": { + "name": "فریم شروع" + }, + "model": { + "name": "مدل" + }, + "movement_amplitude": { + "name": "دامنه حرکت", + "tooltip": "دامنه حرکت اشیاء در قاب." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "توضیح پرامپت (حداکثر ۲۰۰۰ کاراکتر)." + }, + "resolution": { + "name": "رزولوشن" + }, + "seed": { + "name": "seed" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2TextToVideoNode": { + "description": "تولید ویدیو از یک پرامپت متنی", + "display_name": "تولید ویدیو از متن Vidu2", + "inputs": { + "aspect_ratio": { + "name": "نسبت تصویر" + }, + "background_music": { + "name": "موسیقی پس‌زمینه", + "tooltip": "آیا موسیقی پس‌زمینه به ویدیوی تولیدشده اضافه شود یا خیر." + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان" + }, + "model": { + "name": "مدل" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "توضیح متنی برای تولید ویدیو، با حداکثر طول ۲۰۰۰ کاراکتر." + }, + "resolution": { + "name": "رزولوشن" + }, + "seed": { + "name": "seed" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ViduImageToVideoNode": { + "description": "تولید ویدیو از یک تصویر و متن اختیاری", + "display_name": "تولید ویدیو از تصویر با Vidu", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان", + "tooltip": "مدت زمان ویدیوی خروجی بر حسب ثانیه" + }, + "image": { + "name": "تصویر", + "tooltip": "تصویری که به عنوان فریم ابتدایی ویدیو تولیدی استفاده می‌شود" + }, + "model": { + "name": "مدل", + "tooltip": "نام مدل" + }, + "movement_amplitude": { + "name": "دامنه حرکت", + "tooltip": "دامنه حرکت اشیاء در فریم" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "توضیح متنی برای تولید ویدیو" + }, + "resolution": { + "name": "رزولوشن", + "tooltip": "مقادیر پشتیبانی‌شده ممکن است بسته به مدل و مدت زمان متفاوت باشد" + }, + "seed": { + "name": "بذر", + "tooltip": "بذر برای تولید ویدیو (۰ برای تصادفی)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ViduReferenceVideoNode": { + "description": "تولید ویدیو از چند تصویر و یک پرامپت", + "display_name": "تولید ویدیو از تصاویر مرجع با Vidu", + "inputs": { + "aspect_ratio": { + "name": "نسبت تصویر", + "tooltip": "نسبت تصویر ویدیوی خروجی" + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان", + "tooltip": "مدت زمان ویدیوی خروجی بر حسب ثانیه" + }, + "images": { + "name": "تصاویر", + "tooltip": "تصاویر مرجع برای تولید ویدیویی با سوژه‌های یکسان (حداکثر ۷ تصویر)." + }, + "model": { + "name": "مدل", + "tooltip": "نام مدل" + }, + "movement_amplitude": { + "name": "دامنه حرکت", + "tooltip": "دامنه حرکت اشیاء در فریم" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "توضیح متنی برای تولید ویدیو" + }, + "resolution": { + "name": "رزولوشن", + "tooltip": "مقادیر پشتیبانی‌شده ممکن است بسته به مدل و مدت زمان متفاوت باشد" + }, + "seed": { + "name": "بذر", + "tooltip": "بذر برای تولید ویدیو (۰ برای تصادفی)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ViduStartEndToVideoNode": { + "description": "تولید ویدیو از فریم شروع و پایان و یک پرامپت", + "display_name": "تولید ویدیو از فریم ابتدایی و انتهایی با Vidu", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان", + "tooltip": "مدت زمان ویدیوی خروجی بر حسب ثانیه" + }, + "end_frame": { + "name": "فریم پایان", + "tooltip": "فریم انتهایی" + }, + "first_frame": { + "name": "فریم شروع", + "tooltip": "فریم ابتدایی" + }, + "model": { + "name": "مدل", + "tooltip": "نام مدل" + }, + "movement_amplitude": { + "name": "دامنه حرکت", + "tooltip": "دامنه حرکت اشیاء در فریم" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "توضیح متنی برای تولید ویدیو" + }, + "resolution": { + "name": "رزولوشن", + "tooltip": "مقادیر پشتیبانی‌شده ممکن است بسته به مدل و مدت زمان متفاوت باشد" + }, + "seed": { + "name": "بذر", + "tooltip": "بذر برای تولید ویدیو (۰ برای تصادفی)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ViduTextToVideoNode": { + "description": "تولید ویدیو از یک متن ورودی", + "display_name": "تولید ویدیو از متن Vidu", + "inputs": { + "aspect_ratio": { + "name": "نسبت تصویر", + "tooltip": "نسبت تصویر ویدیوی خروجی" + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان", + "tooltip": "مدت زمان ویدیوی خروجی به ثانیه" + }, + "model": { + "name": "مدل", + "tooltip": "نام مدل" + }, + "movement_amplitude": { + "name": "دامنه حرکت", + "tooltip": "دامنه حرکت اشیاء در قاب" + }, + "prompt": { + "name": "پرامپت", + "tooltip": "توضیح متنی برای تولید ویدیو" + }, + "resolution": { + "name": "رزولوشن", + "tooltip": "مقادیر پشتیبانی شده ممکن است بسته به مدل و مدت زمان متفاوت باشد" + }, + "seed": { + "name": "بذر", + "tooltip": "بذر برای تولید ویدیو (۰ برای تصادفی)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "VoxelToMesh": { + "display_name": "VoxelToMesh", + "inputs": { + "algorithm": { + "name": "الگوریتم" + }, + "threshold": { + "name": "آستانه" + }, + "voxel": { + "name": "voxel" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "VoxelToMeshBasic": { + "display_name": "VoxelToMeshBasic", + "inputs": { + "threshold": { + "name": "آستانه" + }, + "voxel": { + "name": "voxel" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Wan22FunControlToVideo": { + "display_name": "Wan22FunControlToVideo", + "inputs": { + "batch_size": { + "name": "اندازه بچ" + }, + "control_video": { + "name": "ویدیوی کنترل" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + }, + "ref_image": { + "name": "تصویر مرجع" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "Wan22ImageToVideoLatent": { + "display_name": "Wan22ImageToVideoLatent", + "inputs": { + "batch_size": { + "name": "اندازه بچ" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "start_image": { + "name": "تصویر شروع" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanAnimateToVideo": { + "display_name": "WanAnimateToVideo", + "inputs": { + "background_video": { + "name": "ویدیوی پس‌زمینه" + }, + "batch_size": { + "name": "اندازه دسته" + }, + "character_mask": { + "name": "ماسک کاراکتر" + }, + "clip_vision_output": { + "name": "خروجی clip vision" + }, + "continue_motion": { + "name": "ادامه حرکت" + }, + "continue_motion_max_frames": { + "name": "حداکثر فریم‌های ادامه حرکت" + }, + "face_video": { + "name": "ویدیوی چهره" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "negative": { + "name": "منفی" + }, + "pose_video": { + "name": "ویدیوی ژست" + }, + "positive": { + "name": "مثبت" + }, + "reference_image": { + "name": "تصویر مرجع" + }, + "vae": { + "name": "vae" + }, + "video_frame_offset": { + "name": "افست فریم ویدیو", + "tooltip": "مقدار فریم‌هایی که باید در تمام ویدیوهای ورودی جست‌وجو شود. برای تولید ویدیوهای طولانی‌تر به صورت قطعه‌ای استفاده می‌شود. برای گسترش یک ویدیو، به خروجی video_frame_offset گره قبلی متصل کنید." + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + }, + "3": { + "name": "trim_latent", + "tooltip": null + }, + "4": { + "name": "برش تصویر", + "tooltip": null + }, + "5": { + "name": "افست فریم ویدیو", + "tooltip": null + } + } + }, + "WanCameraEmbedding": { + "display_name": "WanCameraEmbedding", + "inputs": { + "camera_pose": { + "name": "وضعیت دوربین" + }, + "cx": { + "name": "cx" + }, + "cy": { + "name": "cy" + }, + "fx": { + "name": "fx" + }, + "fy": { + "name": "fy" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "speed": { + "name": "سرعت" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "بردار دوربین", + "tooltip": null + }, + "1": { + "name": "عرض", + "tooltip": null + }, + "2": { + "name": "ارتفاع", + "tooltip": null + }, + "3": { + "name": "طول", + "tooltip": null + } + } + }, + "WanCameraImageToVideo": { + "display_name": "WanCameraImageToVideo", + "inputs": { + "batch_size": { + "name": "اندازه دسته" + }, + "camera_conditions": { + "name": "شرایط دوربین" + }, + "clip_vision_output": { + "name": "خروجی clip vision" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + }, + "start_image": { + "name": "تصویر شروع" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "WanContextWindowsManual": { + "description": "تنظیم دستی پنجره‌های زمینه برای مدل‌های مشابه WAN (بعد=۲).", + "display_name": "پنجره‌های زمینه WAN (دستی)", + "inputs": { + "closed_loop": { + "name": "حلقه بسته", + "tooltip": "آیا حلقه پنجره زمینه بسته شود؛ فقط برای زمان‌بندی حلقه‌ای قابل استفاده است." + }, + "context_length": { + "name": "طول پنجره زمینه", + "tooltip": "طول پنجره زمینه." + }, + "context_overlap": { + "name": "همپوشانی پنجره زمینه", + "tooltip": "میزان همپوشانی پنجره زمینه." + }, + "context_schedule": { + "name": "زمان‌بندی پنجره زمینه", + "tooltip": "گام پنجره زمینه." + }, + "context_stride": { + "name": "گام پنجره زمینه", + "tooltip": "گام پنجره زمینه؛ فقط برای زمان‌بندی یکنواخت قابل استفاده است." + }, + "freenoise": { + "name": "freenoise", + "tooltip": "آیا از شافل نویز FreeNoise استفاده شود؛ باعث بهبود ترکیب پنجره‌ها می‌شود." + }, + "fuse_method": { + "name": "روش ادغام", + "tooltip": "روشی که برای ادغام پنجره‌های زمینه استفاده می‌شود." + }, + "model": { + "name": "مدل", + "tooltip": "مدلی که پنجره‌های زمینه روی آن در هنگام نمونه‌گیری اعمال می‌شود." + } + }, + "outputs": { + "0": { + "tooltip": "مدل با پنجره‌های زمینه اعمال‌شده در هنگام نمونه‌گیری." + } + } + }, + "WanFirstLastFrameToVideo": { + "display_name": "WanFirstLastFrameToVideo", + "inputs": { + "batch_size": { + "name": "اندازه دسته" + }, + "clip_vision_end_image": { + "name": "تصویر پایان clip vision" + }, + "clip_vision_start_image": { + "name": "تصویر شروع clip vision" + }, + "end_image": { + "name": "تصویر پایان" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + }, + "start_image": { + "name": "تصویر شروع" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "WanFunControlToVideo": { + "display_name": "WanFunControlToVideo", + "inputs": { + "batch_size": { + "name": "اندازه دسته" + }, + "clip_vision_output": { + "name": "خروجی clip vision" + }, + "control_video": { + "name": "ویدئوی کنترل" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + }, + "start_image": { + "name": "تصویر شروع" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "WanFunInpaintToVideo": { + "display_name": "WanFunInpaintToVideo", + "inputs": { + "batch_size": { + "name": "اندازه دسته" + }, + "clip_vision_output": { + "name": "خروجی clip vision" + }, + "end_image": { + "name": "تصویر پایان" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + }, + "start_image": { + "name": "تصویر شروع" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "WanHuMoImageToVideo": { + "display_name": "WanHuMoImageToVideo", + "inputs": { + "audio_encoder_output": { + "name": "خروجی رمزگذار صوتی" + }, + "batch_size": { + "name": "اندازه دسته" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + }, + "ref_image": { + "name": "تصویر مرجع" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "WanImageToImageApi": { + "description": "یک تصویر را از یک یا دو تصویر ورودی و یک پرامپت متنی تولید می‌کند. تصویر خروجی در حال حاضر با وضوح ثابت ۱.۶ مگاپیکسل است و نسبت ابعاد آن با تصویر(های) ورودی مطابقت دارد.", + "display_name": "وان تصویر به تصویر", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "image": { + "name": "تصویر", + "tooltip": "ویرایش تک‌تصویر یا ترکیب چند تصویر. حداکثر ۲ تصویر." + }, + "model": { + "name": "مدل", + "tooltip": "مدلی که باید استفاده شود." + }, + "negative_prompt": { + "name": "پرامپت منفی", + "tooltip": "پرامپت منفی که مواردی را که باید اجتناب شود توصیف می‌کند." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپتی که عناصر و ویژگی‌های بصری را توصیف می‌کند. از زبان انگلیسی و چینی پشتیبانی می‌کند." + }, + "seed": { + "name": "بذر", + "tooltip": "بذری که برای تولید استفاده می‌شود." + }, + "watermark": { + "name": "واترمارک", + "tooltip": "آیا واترمارک تولیدشده توسط هوش مصنوعی به نتیجه اضافه شود یا خیر." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanImageToVideo": { + "display_name": "وان تصویر به ویدیو", + "inputs": { + "batch_size": { + "name": "اندازه بچ" + }, + "clip_vision_output": { + "name": "خروجی clip vision" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + }, + "start_image": { + "name": "تصویر شروع" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "WanImageToVideoApi": { + "description": "یک ویدیو را از اولین فریم و یک پرامپت متنی تولید می‌کند.", + "display_name": "وان تصویر به ویدیو", + "inputs": { + "audio": { + "name": "صدا", + "tooltip": "صدا باید دارای صدای واضح و بلند باشد و نویز اضافی یا موسیقی پس‌زمینه نداشته باشد." + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان", + "tooltip": "مدت زمان ۱۵ فقط برای مدل WAN2.6 در دسترس است." + }, + "generate_audio": { + "name": "تولید صدا", + "tooltip": "اگر ورودی صوتی ارائه نشود، صدا به صورت خودکار تولید می‌شود." + }, + "image": { + "name": "تصویر" + }, + "model": { + "name": "مدل", + "tooltip": "مدلی که باید استفاده شود." + }, + "negative_prompt": { + "name": "پرامپت منفی", + "tooltip": "پرامپت منفی که مواردی را که باید اجتناب شود توصیف می‌کند." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپتی که عناصر و ویژگی‌های بصری را توصیف می‌کند. از زبان انگلیسی و چینی پشتیبانی می‌کند." + }, + "prompt_extend": { + "name": "گسترش پرامپت", + "tooltip": "آیا پرامپت با کمک هوش مصنوعی تقویت شود یا خیر." + }, + "resolution": { + "name": "وضوح" + }, + "seed": { + "name": "بذر", + "tooltip": "بذری که برای تولید استفاده می‌شود." + }, + "shot_type": { + "name": "نوع برداشت", + "tooltip": "نوع برداشت برای ویدیوی تولیدشده را مشخص می‌کند؛ یعنی ویدیو یک برداشت پیوسته باشد یا چند برداشت با برش. این پارامتر فقط زمانی اعمال می‌شود که گسترش پرامپت فعال باشد." + }, + "watermark": { + "name": "واترمارک", + "tooltip": "آیا واترمارک تولیدشده توسط هوش مصنوعی به نتیجه اضافه شود یا خیر." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanInfiniteTalkToVideo": { + "display_name": "WanInfiniteTalkToVideo", + "inputs": { + "audio_encoder_output_1": { + "name": "خروجی رمزگذار صوتی ۱" + }, + "audio_scale": { + "name": "مقیاس صوتی" + }, + "clip_vision_output": { + "name": "خروجی بینایی clip" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "mode": { + "name": "حالت" + }, + "model": { + "name": "مدل" + }, + "model_patch": { + "name": "patch مدل" + }, + "motion_frame_count": { + "name": "تعداد فریم‌های حرکتی", + "tooltip": "تعداد فریم‌های قبلی که به عنوان زمینه حرکت استفاده می‌شود." + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + }, + "previous_frames": { + "name": "فریم‌های قبلی" + }, + "start_image": { + "name": "تصویر شروع" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "مدل", + "tooltip": null + }, + "1": { + "name": "مثبت", + "tooltip": null + }, + "2": { + "name": "منفی", + "tooltip": null + }, + "3": { + "name": "latent", + "tooltip": null + }, + "4": { + "name": "تصویر برش‌خورده", + "tooltip": null + } + } + }, + "WanMoveConcatTrack": { + "display_name": "WanMoveConcatTrack", + "inputs": { + "tracks_1": { + "name": "tracks_1" + }, + "tracks_2": { + "name": "tracks_2" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanMoveTrackToVideo": { + "display_name": "WanMoveTrackToVideo", + "inputs": { + "batch_size": { + "name": "اندازه بچ" + }, + "clip_vision_output": { + "name": "خروجی clip vision" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + }, + "start_image": { + "name": "تصویر شروع" + }, + "strength": { + "name": "شدت", + "tooltip": "شدت شرط‌گذاری ترک." + }, + "tracks": { + "name": "ترک‌ها" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "WanMoveTracksFromCoords": { + "display_name": "WanMoveTracksFromCoords", + "inputs": { + "track_coords": { + "name": "track_coords" + }, + "track_mask": { + "name": "track_mask" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "track_length", + "tooltip": null + } + } + }, + "WanMoveVisualizeTracks": { + "display_name": "WanMoveVisualizeTracks", + "inputs": { + "circle_size": { + "name": "اندازه دایره" + }, + "images": { + "name": "تصاویر" + }, + "line_resolution": { + "name": "وضوح خط" + }, + "line_width": { + "name": "ضخامت خط" + }, + "opacity": { + "name": "شفافیت" + }, + "tracks": { + "name": "ترک‌ها" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanPhantomSubjectToVideo": { + "display_name": "WanPhantomSubjectToVideo", + "inputs": { + "batch_size": { + "name": "اندازه بچ" + }, + "height": { + "name": "ارتفاع" + }, + "images": { + "name": "تصاویر" + }, + "length": { + "name": "طول" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "متن منفی", + "tooltip": null + }, + "2": { + "name": "متن تصویر منفی", + "tooltip": null + }, + "3": { + "name": "latent", + "tooltip": null + } + } + }, + "WanReferenceVideoApi": { + "description": "از شخصیت و صدای ویدیوهای ورودی به همراه یک پرامپت برای تولید ویدیوی جدیدی استفاده کنید که ثبات شخصیت را حفظ می‌کند.", + "display_name": "وان رفرنس به ویدیو", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان" + }, + "model": { + "name": "مدل" + }, + "negative_prompt": { + "name": "پرامپت منفی", + "tooltip": "پرامپت منفی برای توصیف مواردی که باید از آن‌ها اجتناب شود." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپتی که عناصر و ویژگی‌های بصری را توصیف می‌کند. از زبان انگلیسی و چینی پشتیبانی می‌شود. برای اشاره به شخصیت‌های مرجع از شناسه‌هایی مانند `character1` و `character2` استفاده کنید." + }, + "reference_videos": { + "name": "ویدیوهای مرجع" + }, + "seed": { + "name": "بذر" + }, + "shot_type": { + "name": "نوع برداشت", + "tooltip": "نوع برداشت برای ویدیوی تولید شده را مشخص می‌کند، یعنی اینکه ویدیو یک برداشت پیوسته باشد یا شامل چند برداشت با برش." + }, + "size": { + "name": "اندازه" + }, + "watermark": { + "name": "واترمارک", + "tooltip": "آیا یک واترمارک تولیدشده توسط هوش مصنوعی به نتیجه اضافه شود یا خیر." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanSoundImageToVideo": { + "display_name": "وان‌ساند ایمیج تو ویدیو", + "inputs": { + "audio_encoder_output": { + "name": "خروجی انکودر صوتی" + }, + "batch_size": { + "name": "اندازه بچ" + }, + "control_video": { + "name": "کنترل ویدیو" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + }, + "ref_image": { + "name": "تصویر مرجع" + }, + "ref_motion": { + "name": "حرکت مرجع" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "WanSoundImageToVideoExtend": { + "display_name": "وان‌ساند ایمیج تو ویدیو اکستند", + "inputs": { + "audio_encoder_output": { + "name": "خروجی انکودر صوتی" + }, + "control_video": { + "name": "کنترل ویدیو" + }, + "length": { + "name": "طول" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + }, + "ref_image": { + "name": "تصویر مرجع" + }, + "vae": { + "name": "vae" + }, + "video_latent": { + "name": "ویدیو latent" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "WanTextToImageApi": { + "description": "تولید تصویر بر اساس یک پرامپت متنی.", + "display_name": "وان تبدیل متن به تصویر", + "inputs": { + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "height": { + "name": "ارتفاع" + }, + "model": { + "name": "مدل", + "tooltip": "مدلی که باید استفاده شود." + }, + "negative_prompt": { + "name": "پرامپت منفی", + "tooltip": "پرامپت منفی که مواردی را که باید اجتناب شود توصیف می‌کند." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپتی که عناصر و ویژگی‌های بصری را توصیف می‌کند. از زبان انگلیسی و چینی پشتیبانی می‌کند." + }, + "prompt_extend": { + "name": "افزایش پرامپت", + "tooltip": "آیا پرامپت با کمک هوش مصنوعی بهبود یابد یا خیر." + }, + "seed": { + "name": "بذر", + "tooltip": "بذری که برای تولید استفاده می‌شود." + }, + "watermark": { + "name": "واترمارک", + "tooltip": "آیا واترمارک تولیدشده توسط هوش مصنوعی به نتیجه اضافه شود یا خیر." + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanTextToVideoApi": { + "description": "تولید ویدیو بر اساس یک پرامپت متنی.", + "display_name": "وان تبدیل متن به ویدیو", + "inputs": { + "audio": { + "name": "صدا", + "tooltip": "صدا باید دارای گفتار واضح و بلند باشد و نویز اضافی یا موسیقی پس‌زمینه نداشته باشد." + }, + "control_after_generate": { + "name": "کنترل پس از تولید" + }, + "duration": { + "name": "مدت زمان", + "tooltip": "مدت زمان ۱۵ ثانیه فقط برای مدل Wan 2.6 در دسترس است." + }, + "generate_audio": { + "name": "تولید صدا", + "tooltip": "در صورت عدم ارائه ورودی صوتی، صدا به صورت خودکار تولید می‌شود." + }, + "model": { + "name": "مدل", + "tooltip": "مدلی که باید استفاده شود." + }, + "negative_prompt": { + "name": "پرامپت منفی", + "tooltip": "پرامپت منفی که مواردی را که باید اجتناب شود توصیف می‌کند." + }, + "prompt": { + "name": "پرامپت", + "tooltip": "پرامپتی که عناصر و ویژگی‌های بصری را توصیف می‌کند. از زبان انگلیسی و چینی پشتیبانی می‌کند." + }, + "prompt_extend": { + "name": "افزایش پرامپت", + "tooltip": "آیا پرامپت با کمک هوش مصنوعی بهبود یابد یا خیر." + }, + "seed": { + "name": "بذر", + "tooltip": "بذری که برای تولید استفاده می‌شود." + }, + "shot_type": { + "name": "نوع برداشت", + "tooltip": "نوع برداشت برای ویدیوی تولیدشده را مشخص می‌کند؛ یعنی ویدیو یک برداشت پیوسته باشد یا چند برداشت با برش. این پارامتر فقط زمانی اعمال می‌شود که افزایش پرامپت فعال باشد." + }, + "size": { + "name": "اندازه" + }, + "watermark": { + "name": "واترمارک", + "tooltip": "آیا واترمارک تولیدشده توسط هوش مصنوعی به نتیجه اضافه شود یا خیر." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanTrackToVideo": { + "display_name": "WanTrackToVideo", + "inputs": { + "batch_size": { + "name": "اندازه بچ" + }, + "clip_vision_output": { + "name": "خروجی clip vision" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + }, + "start_image": { + "name": "تصویر شروع" + }, + "temperature": { + "name": "دمای تصادفی‌سازی" + }, + "topk": { + "name": "topk" + }, + "tracks": { + "name": "ترک‌ها" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "WanVaceToVideo": { + "display_name": "WanVaceToVideo", + "inputs": { + "batch_size": { + "name": "اندازه بچ" + }, + "control_masks": { + "name": "ماسک‌های کنترل" + }, + "control_video": { + "name": "ویدئوی کنترل" + }, + "height": { + "name": "ارتفاع" + }, + "length": { + "name": "طول" + }, + "negative": { + "name": "منفی" + }, + "positive": { + "name": "مثبت" + }, + "reference_image": { + "name": "تصویر مرجع" + }, + "strength": { + "name": "شدت" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "عرض" + } + }, + "outputs": { + "0": { + "name": "مثبت", + "tooltip": null + }, + "1": { + "name": "منفی", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + }, + "3": { + "name": "trim_latent", + "tooltip": null + } + } + }, + "WavespeedFlashVSRNode": { + "description": "افزایش‌دهنده سریع و با کیفیت ویدیو که وضوح را افزایش داده و شفافیت را برای ویدیوهای کم‌کیفیت یا تار بازمی‌گرداند.", + "display_name": "افزایش کیفیت ویدیو FlashVSR", + "inputs": { + "target_resolution": { + "name": "وضوح هدف" + }, + "video": { + "name": "ویدیو" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WavespeedImageUpscaleNode": { + "description": "افزایش وضوح و کیفیت تصویر، ارتقاء عکس‌ها به ۴K یا ۸K برای نتایج شفاف و با جزئیات.", + "display_name": "افزایش کیفیت تصویر WaveSpeed", + "inputs": { + "image": { + "name": "تصویر" + }, + "model": { + "name": "مدل" + }, + "target_resolution": { + "name": "وضوح هدف" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WebcamCapture": { + "display_name": "دریافت از وب‌کم", + "inputs": { + "capture_on_queue": { + "name": "دریافت هنگام صف" + }, + "height": { + "name": "ارتفاع" + }, + "image": { + "name": "تصویر" + }, + "waiting for camera___": { + }, + "width": { + "name": "عرض" + } + } + }, + "ZImageFunControlnet": { + "display_name": "ZImageFunControlnet", + "inputs": { + "image": { + "name": "تصویر" + }, + "inpaint_image": { + "name": "تصویر inpaint" + }, + "mask": { + "name": "ماسک" + }, + "model": { + "name": "مدل" + }, + "model_patch": { + "name": "patch مدل" + }, + "strength": { + "name": "شدت" + }, + "vae": { + "name": "vae" + } + } + }, + "unCLIPCheckpointLoader": { + "display_name": "unCLIPCheckpointLoader", + "inputs": { + "ckpt_name": { + "name": "نام Checkpoint" + } + } + }, + "unCLIPConditioning": { + "display_name": "unCLIPConditioning", + "inputs": { + "clip_vision_output": { + "name": "خروجی بینایی clip" + }, + "conditioning": { + "name": "شرط‌گذاری" + }, + "noise_augmentation": { + "name": "افزایش نویز" + }, + "strength": { + "name": "شدت" + } + } + }, + "wanBlockSwap": { + "description": "NOP", + "display_name": "wanBlockSwap", + "inputs": { + "model": { + "name": "مدل" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + } +} diff --git a/src/locales/fa/settings.json b/src/locales/fa/settings.json new file mode 100644 index 000000000..74beef831 --- /dev/null +++ b/src/locales/fa/settings.json @@ -0,0 +1,479 @@ +{ + "Comfy-Desktop_AutoUpdate": { + "name": "بررسی خودکار برای به‌روزرسانی‌ها" + }, + "Comfy-Desktop_SendStatistics": { + "name": "ارسال آمار استفاده ناشناس" + }, + "Comfy-Desktop_UV_PypiInstallMirror": { + "name": "آینه نصب Pypi", + "tooltip": "آینه پیش‌فرض برای نصب pip" + }, + "Comfy-Desktop_UV_PythonInstallMirror": { + "name": "آینه نصب Python", + "tooltip": "نصب‌های مدیریت‌شده Python از پروژه Astral python-build-standalone دانلود می‌شوند. این متغیر می‌تواند به یک آدرس آینه تنظیم شود تا منبع متفاوتی برای نصب‌های Python استفاده شود. آدرس ارائه‌شده جایگزین https://github.com/astral-sh/python-build-standalone/releases/download در مثال زیر خواهد شد: https://github.com/astral-sh/python-build-standalone/releases/download/20240713/cpython-3.12.4%2B20240713-aarch64-apple-darwin-install_only.tar.gz. توزیع‌ها را می‌توان با استفاده از طرح آدرس file:// از یک پوشه محلی خواند." + }, + "Comfy-Desktop_UV_TorchInstallMirror": { + "name": "آینه نصب Torch", + "tooltip": "آینه pip برای نصب pytorch" + }, + "Comfy-Desktop_WindowStyle": { + "name": "سبک پنجره", + "options": { + "custom": "سفارشی", + "default": "پیش‌فرض" + }, + "tooltip": "سفارشی: نوار عنوان سیستم با منوی بالای ComfyUI جایگزین می‌شود" + }, + "Comfy_Canvas_BackgroundImage": { + "name": "تصویر پس‌زمینه بوم", + "tooltip": "آدرس تصویر برای پس‌زمینه بوم. می‌توانید روی یک تصویر در پانل خروجی راست‌کلیک کرده و «تنظیم به عنوان پس‌زمینه» را انتخاب کنید یا تصویر دلخواه خود را با دکمه بارگذاری، بارگذاری نمایید." + }, + "Comfy_Canvas_LeftMouseClickBehavior": { + "name": "رفتار کلیک چپ ماوس", + "options": { + "Panning": "جابجایی", + "Select": "انتخاب" + } + }, + "Comfy_Canvas_MouseWheelScroll": { + "name": "اسکرول چرخ ماوس", + "options": { + "Panning": "جابجایی", + "Zoom in/out": "بزرگ‌نمایی/کوچک‌نمایی" + } + }, + "Comfy_Canvas_NavigationMode": { + "name": "حالت ناوبری", + "options": { + "Custom": "سفارشی", + "Drag Navigation": "ناوبری با کشیدن", + "Standard (New)": "استاندارد (جدید)" + } + }, + "Comfy_Canvas_SelectionToolbox": { + "name": "نمایش جعبه ابزار انتخاب" + }, + "Comfy_ConfirmClear": { + "name": "نیاز به تأیید هنگام پاک‌سازی workflow" + }, + "Comfy_DOMClippingEnabled": { + "name": "فعال‌سازی برش عناصر DOM (فعال‌سازی ممکن است عملکرد را کاهش دهد)" + }, + "Comfy_DevMode": { + "name": "فعال‌سازی گزینه‌های حالت توسعه (ذخیره API و غیره)" + }, + "Comfy_DisableFloatRounding": { + "name": "غیرفعال‌سازی گرد کردن پیش‌فرض اعداد اعشاری.", + "tooltip": "(نیاز به بارگذاری مجدد صفحه) امکان غیرفعال‌سازی گرد کردن زمانی که توسط node در backend تنظیم شده باشد وجود ندارد." + }, + "Comfy_DisableSliders": { + "name": "غیرفعال‌سازی اسلایدرهای ابزارک node" + }, + "Comfy_EditAttention_Delta": { + "name": "دقت Ctrl+بالا/پایین" + }, + "Comfy_EnableTooltips": { + "name": "فعال‌سازی راهنماها" + }, + "Comfy_EnableWorkflowViewRestore": { + "name": "ذخیره و بازیابی موقعیت و سطح بزرگ‌نمایی بوم در workflowها" + }, + "Comfy_Execution_PreviewMethod": { + "name": "روش پیش‌نمایش زنده", + "options": { + "auto": "خودکار", + "default": "پیش‌فرض", + "latent2rgb": "latent2rgb", + "none": "هیچ‌کدام", + "taesd": "taesd" + }, + "tooltip": "روش پیش‌نمایش زنده هنگام تولید تصویر. «پیش‌فرض» از تنظیم CLI سرور استفاده می‌کند." + }, + "Comfy_FloatRoundingPrecision": { + "name": "تعداد ارقام اعشاری گرد کردن ابزارک اعشاری [۰ = خودکار].", + "tooltip": "(نیاز به بارگذاری مجدد صفحه)" + }, + "Comfy_Graph_CanvasInfo": { + "name": "نمایش اطلاعات بوم در گوشه پایین سمت چپ (fps و غیره)" + }, + "Comfy_Graph_CanvasMenu": { + "name": "نمایش منوی بوم گراف" + }, + "Comfy_Graph_CtrlShiftZoom": { + "name": "فعال‌سازی میانبر بزرگ‌نمایی سریع (Ctrl + Shift + کشیدن)" + }, + "Comfy_Graph_LinkMarkers": { + "name": "نشانگرهای میانه‌ی پیوند", + "options": { + "Arrow": "پیکان", + "Circle": "دایره", + "None": "هیچ‌کدام" + } + }, + "Comfy_Graph_LiveSelection": { + "name": "انتخاب زنده", + "tooltip": "در صورت فعال بودن، nodeها به صورت آنی هنگام کشیدن مستطیل انتخاب، انتخاب یا لغو انتخاب می‌شوند؛ مشابه سایر ابزارهای طراحی." + }, + "Comfy_Graph_ZoomSpeed": { + "name": "سرعت بزرگ‌نمایی بوم" + }, + "Comfy_GroupSelectedNodes_Padding": { + "name": "فاصله داخلی nodeهای انتخاب‌شده در گروه" + }, + "Comfy_Group_DoubleClickTitleToEdit": { + "name": "دوبار کلیک روی عنوان گروه برای ویرایش" + }, + "Comfy_LinkRelease_Action": { + "name": "عملکرد هنگام رها کردن پیوند (بدون کلید ترکیبی)", + "options": { + "context menu": "منوی زمینه", + "no action": "بدون عمل", + "search box": "جعبه جستجو" + } + }, + "Comfy_LinkRelease_ActionShift": { + "name": "عملکرد هنگام رها کردن پیوند (کلید Shift)", + "options": { + "context menu": "منوی زمینه", + "no action": "بدون عمل", + "search box": "جعبه جستجو" + } + }, + "Comfy_LinkRenderMode": { + "name": "حالت نمایش پیوند", + "options": { + "Hidden": "مخفی", + "Linear": "خطی", + "Spline": "اسپلاین", + "Straight": "مستقیم" + } + }, + "Comfy_Load3D_3DViewerEnable": { + "name": "فعال‌سازی نمایشگر سه‌بعدی (بتا)", + "tooltip": "نمایشگر سه‌بعدی (بتا) را برای nodeهای انتخاب‌شده فعال می‌کند. این قابلیت به شما امکان می‌دهد مدل‌های سه‌بعدی را مستقیماً در نمایشگر سه‌بعدی با اندازه کامل مشاهده و تعامل کنید." + }, + "Comfy_Load3D_BackgroundColor": { + "name": "رنگ پس‌زمینه اولیه", + "tooltip": "رنگ پیش‌فرض پس‌زمینه صحنه سه‌بعدی را کنترل می‌کند. این تنظیم ظاهر پس‌زمینه را هنگام ایجاد یک ابزارک سه‌بعدی جدید تعیین می‌کند، اما پس از ایجاد برای هر ابزارک به صورت جداگانه قابل تغییر است." + }, + "Comfy_Load3D_CameraType": { + "name": "نوع دوربین اولیه", + "options": { + "orthographic": "اورتوگرافیک", + "perspective": "پرسپکتیو" + }, + "tooltip": "تعیین می‌کند که دوربین به طور پیش‌فرض پرسپکتیو باشد یا اورتوگرافیک هنگام ایجاد یک ابزارک سه‌بعدی جدید. این پیش‌فرض برای هر ابزارک به صورت جداگانه قابل تغییر است." + }, + "Comfy_Load3D_LightAdjustmentIncrement": { + "name": "گام تنظیم شدت نور", + "tooltip": "اندازه گام هنگام تنظیم شدت نور در صحنه‌های سه‌بعدی را کنترل می‌کند. مقدار گام کوچکتر امکان کنترل دقیق‌تر نور را فراهم می‌کند، در حالی که مقدار بزرگ‌تر تغییرات محسوس‌تری ایجاد می‌کند." + }, + "Comfy_Load3D_LightIntensity": { + "name": "شدت نور اولیه", + "tooltip": "سطح روشنایی پیش‌فرض نور در صحنه سه‌بعدی را تعیین می‌کند. این مقدار مشخص می‌کند که نورها هنگام ایجاد یک ابزارک سه‌بعدی جدید با چه شدتی به اشیاء تابیده شوند، اما برای هر ابزارک به صورت جداگانه قابل تغییر است." + }, + "Comfy_Load3D_LightIntensityMaximum": { + "name": "حداکثر شدت نور", + "tooltip": "حداکثر مقدار مجاز شدت نور برای صحنه‌های سه‌بعدی را تعیین می‌کند. این مقدار، حد بالای روشنایی قابل تنظیم در هر ابزارک سه‌بعدی را مشخص می‌کند." + }, + "Comfy_Load3D_LightIntensityMinimum": { + "name": "حداقل شدت نور", + "tooltip": "حداقل مقدار مجاز شدت نور برای صحنه‌های سه‌بعدی را تعیین می‌کند. این مقدار، حد پایین روشنایی قابل تنظیم در هر ابزارک سه‌بعدی را مشخص می‌کند." + }, + "Comfy_Load3D_PLYEngine": { + "name": "موتور PLY", + "options": { + "fastply": "fastply", + "sparkjs": "sparkjs", + "threejs": "threejs" + }, + "tooltip": "موتور بارگذاری فایل‌های PLY را انتخاب کنید. \"threejs\" از PLYLoader بومی Three.js (مناسب برای فایل‌های مش PLY) استفاده می‌کند. \"fastply\" برای فایل‌های point cloud PLY به صورت ASCII بهینه شده است. \"sparkjs\" از Spark.js برای فایل‌های ۳بعدی Gaussian Splatting PLY استفاده می‌کند." + }, + "Comfy_Load3D_ShowGrid": { + "name": "نمایش اولیه شبکه", + "tooltip": "تعیین می‌کند که شبکه (Grid) به طور پیش‌فرض هنگام ایجاد یک ابزارک سه‌بعدی جدید قابل مشاهده باشد یا خیر. این پیش‌فرض برای هر ابزارک به صورت جداگانه قابل تغییر است." + }, + "Comfy_Locale": { + "name": "زبان" + }, + "Comfy_MaskEditor_BrushAdjustmentSpeed": { + "name": "ضریب سرعت تنظیم قلم‌مو", + "tooltip": "کنترل می‌کند که اندازه و سختی قلم‌مو هنگام تنظیم با چه سرعتی تغییر کند. مقادیر بالاتر به معنای تغییرات سریع‌تر است." + }, + "Comfy_MaskEditor_UseDominantAxis": { + "name": "قفل تنظیم قلم‌مو به محور غالب", + "tooltip": "در صورت فعال بودن، تنظیمات قلم‌مو فقط اندازه یا سختی را بر اساس جهتی که بیشتر حرکت می‌کنید تغییر می‌دهد." + }, + "Comfy_ModelLibrary_AutoLoadAll": { + "name": "بارگذاری خودکار همه پوشه‌های مدل", + "tooltip": "اگر فعال باشد، همه پوشه‌ها به محض باز کردن کتابخانه مدل بارگذاری می‌شوند (این کار ممکن است باعث تأخیر در بارگذاری شود). اگر غیرفعال باشد، پوشه‌های مدل در سطح ریشه فقط پس از کلیک شما بارگذاری می‌شوند." + }, + "Comfy_ModelLibrary_NameFormat": { + "name": "نمایش نام در نمای درختی کتابخانه مدل", + "options": { + "filename": "filename", + "title": "title" + }, + "tooltip": "گزینه «filename» را انتخاب کنید تا نمای ساده‌ای از نام فایل خام (بدون مسیر یا پسوند \".safetensors\") در لیست مدل نمایش داده شود. گزینه «title» عنوان متادیتای قابل تنظیم مدل را نمایش می‌دهد." + }, + "Comfy_NodeBadge_NodeIdBadgeMode": { + "name": "حالت نشان شناسه نود", + "options": { + "None": "هیچ‌کدام", + "Show all": "نمایش همه" + } + }, + "Comfy_NodeBadge_NodeLifeCycleBadgeMode": { + "name": "حالت نشان چرخه عمر نود", + "options": { + "None": "هیچ‌کدام", + "Show all": "نمایش همه" + } + }, + "Comfy_NodeBadge_NodeSourceBadgeMode": { + "name": "حالت نشان منبع نود", + "options": { + "Hide built-in": "مخفی کردن داخلی‌ها", + "None": "هیچ‌کدام", + "Show all": "نمایش همه" + } + }, + "Comfy_NodeBadge_ShowApiPricing": { + "name": "نمایش نشان قیمت‌گذاری API نود" + }, + "Comfy_NodeSearchBoxImpl": { + "name": "پیاده‌سازی جعبه جستجوی نود", + "options": { + "default": "پیش‌فرض", + "litegraph (legacy)": "litegraph (قدیمی)" + } + }, + "Comfy_NodeSearchBoxImpl_NodePreview": { + "name": "پیش‌نمایش نود", + "tooltip": "فقط برای پیاده‌سازی پیش‌فرض اعمال می‌شود" + }, + "Comfy_NodeSearchBoxImpl_ShowCategory": { + "name": "نمایش دسته‌بندی نود در نتایج جستجو", + "tooltip": "فقط برای پیاده‌سازی پیش‌فرض اعمال می‌شود" + }, + "Comfy_NodeSearchBoxImpl_ShowIdName": { + "name": "نمایش نام شناسه نود در نتایج جستجو", + "tooltip": "فقط برای پیاده‌سازی پیش‌فرض اعمال می‌شود" + }, + "Comfy_NodeSearchBoxImpl_ShowNodeFrequency": { + "name": "نمایش فراوانی نود در نتایج جستجو", + "tooltip": "فقط برای پیاده‌سازی پیش‌فرض اعمال می‌شود" + }, + "Comfy_NodeSuggestions_number": { + "name": "تعداد پیشنهادهای node", + "tooltip": "فقط برای جعبه جستجوی litegraph/منوی زمینه" + }, + "Comfy_Node_AllowImageSizeDraw": { + "name": "نمایش عرض × ارتفاع زیر پیش‌نمایش تصویر" + }, + "Comfy_Node_AlwaysShowAdvancedWidgets": { + "name": "نمایش همیشگی ابزارهای پیشرفته در همه نودها", + "tooltip": "در صورت فعال بودن، ابزارهای پیشرفته همیشه در همه نودها قابل مشاهده هستند و نیازی به باز کردن جداگانه آن‌ها نیست." + }, + "Comfy_Node_AutoSnapLinkToSlot": { + "name": "اتصال خودکار لینک به اسلات نود", + "tooltip": "هنگام کشیدن یک لینک روی نود، لینک به طور خودکار به ورودی مناسب روی نود متصل می‌شود." + }, + "Comfy_Node_BypassAllLinksOnDelete": { + "name": "حفظ همه لینک‌ها هنگام حذف نود", + "tooltip": "هنگام حذف یک نود، تلاش می‌شود همه ورودی‌ها و خروجی‌های آن مجدداً به هم متصل شوند (با عبور از نود حذف‌شده)." + }, + "Comfy_Node_DoubleClickTitleToEdit": { + "name": "ویرایش عنوان نود با دوبار کلیک" + }, + "Comfy_Node_MiddleClickRerouteNode": { + "name": "ایجاد نود Reroute با کلیک وسط" + }, + "Comfy_Node_Opacity": { + "name": "شفافیت نود" + }, + "Comfy_Node_ShowDeprecated": { + "name": "نمایش نودهای منسوخ در جستجو", + "tooltip": "نودهای منسوخ به طور پیش‌فرض در رابط کاربری پنهان هستند، اما در workflowهای موجود که از آن‌ها استفاده می‌کنند، همچنان فعال خواهند بود." + }, + "Comfy_Node_ShowExperimental": { + "name": "نمایش نودهای آزمایشی در جستجو", + "tooltip": "نودهای آزمایشی در رابط کاربری به این صورت علامت‌گذاری می‌شوند و ممکن است در نسخه‌های آینده تغییرات اساسی یا حذف شوند. در workflowهای تولیدی با احتیاط استفاده شود." + }, + "Comfy_Node_SnapHighlightsNode": { + "name": "برجسته‌سازی نود هنگام اتصال لینک", + "tooltip": "هنگام کشیدن یک لینک روی نود با ورودی مناسب، نود برجسته می‌شود." + }, + "Comfy_Notification_ShowVersionUpdates": { + "name": "نمایش به‌روزرسانی نسخه‌ها", + "tooltip": "نمایش به‌روزرسانی‌ها برای مدل‌های جدید و ویژگی‌های اصلی جدید." + }, + "Comfy_Pointer_ClickBufferTime": { + "name": "تاخیر حرکت اشاره‌گر پس از کلیک", + "tooltip": "پس از فشردن دکمه اشاره‌گر، این بیشترین زمان (بر حسب میلی‌ثانیه) است که حرکت اشاره‌گر می‌تواند نادیده گرفته شود.\n\nبه جلوگیری از جابجایی ناخواسته اشیاء هنگام حرکت اشاره‌گر در حین کلیک کمک می‌کند." + }, + "Comfy_Pointer_ClickDrift": { + "name": "میزان جابجایی اشاره‌گر هنگام کلیک (حداکثر فاصله)", + "tooltip": "اگر اشاره‌گر بیش از این فاصله در هنگام نگه داشتن دکمه حرکت کند، به عنوان کشیدن (drag) در نظر گرفته می‌شود نه کلیک.\n\nبه جلوگیری از جابجایی ناخواسته اشیاء هنگام حرکت اشاره‌گر در حین کلیک کمک می‌کند." + }, + "Comfy_Pointer_DoubleClickTime": { + "name": "بازه زمانی دوبار کلیک (حداکثر)", + "tooltip": "حداکثر زمان به میلی‌ثانیه بین دو کلیک برای ثبت دوبار کلیک. افزایش این مقدار ممکن است در صورتی که دوبار کلیک گاهی ثبت نمی‌شود، کمک کند." + }, + "Comfy_PreviewFormat": { + "name": "فرمت تصویر پیش‌نمایش", + "tooltip": "هنگام نمایش پیش‌نمایش در ابزارک تصویر، آن را به یک تصویر سبک مانند webp، jpeg، webp;50 و غیره تبدیل می‌کند." + }, + "Comfy_PromptFilename": { + "name": "درخواست نام فایل هنگام ذخیره workflow" + }, + "Comfy_QueueButton_BatchCountLimit": { + "name": "محدودیت تعداد batch", + "tooltip": "حداکثر تعداد taskهایی که با یک کلیک به صف اضافه می‌شوند" + }, + "Comfy_Queue_MaxHistoryItems": { + "name": "اندازه تاریخچه صف", + "tooltip": "حداکثر تعداد taskهایی که در تاریخچه صف نمایش داده می‌شوند." + }, + "Comfy_Queue_QPOV2": { + "name": "استفاده از صف کار یکپارچه در پنل کناری دارایی‌ها", + "tooltip": "پنل شناور صف کار را با صف کاری معادل که در پنل کناری دارایی‌ها قرار دارد جایگزین می‌کند. می‌توانید این گزینه را غیرفعال کنید تا به چیدمان پنل شناور بازگردید." + }, + "Comfy_Sidebar_Location": { + "name": "محل نوار کناری", + "options": { + "left": "چپ", + "right": "راست" + } + }, + "Comfy_Sidebar_Size": { + "name": "اندازه نوار کناری", + "options": { + "normal": "معمولی", + "small": "کوچک" + } + }, + "Comfy_Sidebar_Style": { + "name": "سبک نوار کناری", + "options": { + "connected": "متصل", + "floating": "شناور" + } + }, + "Comfy_Sidebar_UnifiedWidth": { + "name": "عرض یکپارچه نوار کناری" + }, + "Comfy_SnapToGrid_GridSize": { + "name": "اندازه شبکه برای چسباندن به شبکه", + "tooltip": "هنگام کشیدن و تغییر اندازه nodeها با نگه داشتن shift، آن‌ها به شبکه تراز می‌شوند. این گزینه اندازه آن شبکه را کنترل می‌کند." + }, + "Comfy_TextareaWidget_FontSize": { + "name": "اندازه فونت ابزارک textarea" + }, + "Comfy_TextareaWidget_Spellcheck": { + "name": "بررسی املای ابزارک textarea" + }, + "Comfy_TreeExplorer_ItemPadding": { + "name": "فاصله داخلی آیتم‌های مرورگر درختی" + }, + "Comfy_UI_TabBarLayout": { + "name": "چیدمان نوار تب", + "options": { + "Default": "پیش‌فرض", + "Integrated": "یکپارچه" + }, + "tooltip": "چیدمان نوار تب را کنترل می‌کند. «یکپارچه» کنترل‌های راهنما و کاربر را به ناحیه نوار تب منتقل می‌کند." + }, + "Comfy_UseNewMenu": { + "name": "استفاده از منوی جدید", + "options": { + "Disabled": "غیرفعال", + "Top": "بالا" + }, + "tooltip": "فعال‌سازی نوار منوی بازطراحی‌شده بالا." + }, + "Comfy_Validation_Workflows": { + "name": "اعتبارسنجی workflowها" + }, + "Comfy_VueNodes_AutoScaleLayout": { + "name": "مقیاس‌بندی خودکار چیدمان (Nodes 2.0)", + "tooltip": "موقعیت nodeها را هنگام تغییر به رندر Nodes 2.0 به طور خودکار مقیاس‌بندی می‌کند تا از هم‌پوشانی جلوگیری شود" + }, + "Comfy_VueNodes_Enabled": { + "name": "طراحی مدرن node (Nodes 2.0)", + "tooltip": "مدرن: رندر مبتنی بر DOM با تعامل‌پذیری بیشتر، ویژگی‌های بومی مرورگر و طراحی بصری به‌روز. کلاسیک: رندر سنتی مبتنی بر canvas." + }, + "Comfy_WidgetControlMode": { + "name": "حالت کنترل ابزارک", + "options": { + "after": "بعد", + "before": "قبل" + }, + "tooltip": "زمان به‌روزرسانی مقادیر ابزارک (تصادفی/افزایش/کاهش) را کنترل می‌کند؛ یا قبل از قرار گرفتن prompt در صف یا بعد از آن." + }, + "Comfy_Window_UnloadConfirmation": { + "name": "نمایش تأیید هنگام بستن پنجره" + }, + "Comfy_Workflow_AutoSave": { + "name": "ذخیره‌سازی خودکار", + "options": { + "after delay": "پس از تأخیر", + "off": "خاموش" + } + }, + "Comfy_Workflow_AutoSaveDelay": { + "name": "تأخیر ذخیره‌سازی خودکار (میلی‌ثانیه)", + "tooltip": "فقط زمانی اعمال می‌شود که ذخیره‌سازی خودکار روی «پس از تأخیر» تنظیم شده باشد." + }, + "Comfy_Workflow_ConfirmDelete": { + "name": "نمایش تأیید هنگام حذف ورک‌فلو" + }, + "Comfy_Workflow_Persist": { + "name": "حفظ وضعیت ورک‌فلو و بازیابی هنگام بارگذاری (مجدد) صفحه" + }, + "Comfy_Workflow_ShowMissingModelsWarning": { + "name": "نمایش هشدار مدل‌های مفقود" + }, + "Comfy_Workflow_ShowMissingNodesWarning": { + "name": "نمایش هشدار نودهای مفقود" + }, + "Comfy_Workflow_SortNodeIdOnSave": { + "name": "مرتب‌سازی شناسه نودها هنگام ذخیره ورک‌فلو" + }, + "Comfy_Workflow_WarnBlueprintOverwrite": { + "name": "نیاز به تأیید برای بازنویسی بلوپرینت ساب‌گراف موجود" + }, + "Comfy_Workflow_WorkflowTabsPosition": { + "name": "موقعیت ورک‌فلوهای باز شده", + "options": { + "Sidebar": "نوار کناری", + "Topbar": "نوار بالا" + } + }, + "LiteGraph_Canvas_MaximumFps": { + "name": "حداکثر فریم بر ثانیه", + "tooltip": "حداکثر تعداد فریم در ثانیه که بوم مجاز به رندر است. مصرف GPU را با هزینه کاهش روانی محدود می‌کند. اگر ۰ باشد، نرخ تازه‌سازی صفحه نمایش استفاده می‌شود. پیش‌فرض: ۰" + }, + "LiteGraph_Canvas_MinFontSizeForLOD": { + "name": "آستانه اندازه فونت برای سطح جزئیات نود هنگام زوم", + "tooltip": "کنترل می‌کند که نودها چه زمانی به رندر LOD با کیفیت پایین‌تر تغییر کنند. از اندازه فونت بر حسب پیکسل برای تعیین زمان تغییر استفاده می‌کند. برای غیرفعال کردن روی ۰ تنظیم کنید. مقادیر ۱ تا ۲۴ آستانه حداقل اندازه فونت برای LOD را تعیین می‌کنند - مقادیر بالاتر (۲۴ پیکسل) = تغییر سریع‌تر به رندر ساده هنگام زوم به بیرون، مقادیر پایین‌تر (۱ پیکسل) = حفظ کیفیت کامل نود برای مدت طولانی‌تر." + }, + "LiteGraph_ContextMenu_Scaling": { + "name": "مقیاس‌بندی منوهای ترکیبی نود هنگام زوم" + }, + "LiteGraph_Node_DefaultPadding": { + "name": "همیشه کوچک‌سازی نودهای جدید", + "tooltip": "تغییر اندازه نودها به کوچک‌ترین اندازه ممکن هنگام ایجاد. اگر غیرفعال باشد، نود جدید کمی عریض‌تر خواهد بود تا مقادیر ویجت نمایش داده شود." + }, + "LiteGraph_Node_TooltipDelay": { + "name": "تأخیر نمایش راهنما" + }, + "LiteGraph_Reroute_SplineOffset": { + "name": "افست اسپلاین مسیر مجدد", + "tooltip": "افست نقطه کنترل بزیه از نقطه مرکزی مسیر مجدد" + }, + "pysssss_SnapToGrid": { + "name": "همیشه چسباندن به شبکه" + } +} diff --git a/src/locales/fr/commands.json b/src/locales/fr/commands.json index 6c9711aba..ea272f379 100644 --- a/src/locales/fr/commands.json +++ b/src/locales/fr/commands.json @@ -1,4 +1,40 @@ { + "Comfy-Desktop_CheckForUpdates": { + "label": "Vérifier les mises à jour" + }, + "Comfy-Desktop_Folders_OpenCustomNodesFolder": { + "label": "Ouvrir le dossier des Custom Nodes" + }, + "Comfy-Desktop_Folders_OpenInputsFolder": { + "label": "Ouvrir le dossier des entrées" + }, + "Comfy-Desktop_Folders_OpenLogsFolder": { + "label": "Ouvrir le dossier des journaux" + }, + "Comfy-Desktop_Folders_OpenModelConfig": { + "label": "Ouvrir extra_model_paths.yaml" + }, + "Comfy-Desktop_Folders_OpenModelsFolder": { + "label": "Ouvrir le dossier des modèles" + }, + "Comfy-Desktop_Folders_OpenOutputsFolder": { + "label": "Ouvrir le dossier des sorties" + }, + "Comfy-Desktop_OpenDevTools": { + "label": "Ouvrir les outils de développement" + }, + "Comfy-Desktop_OpenUserGuide": { + "label": "Guide d'utilisation du bureau" + }, + "Comfy-Desktop_Quit": { + "label": "Quitter" + }, + "Comfy-Desktop_Reinstall": { + "label": "Réinstaller" + }, + "Comfy-Desktop_Restart": { + "label": "Redémarrer" + }, "Comfy_3DViewer_Open3DViewer": { "label": "Ouvrir le visualiseur 3D (bêta) pour le nœud sélectionné" }, @@ -155,18 +191,30 @@ "Comfy_Manager_ShowUpdateAvailablePacks": { "label": "Vérifier les mises à jour" }, - "Comfy_Manager_ToggleManagerProgressDialog": { - "label": "Basculer la boîte de dialogue de progression" - }, "Comfy_MaskEditor_BrushSize_Decrease": { "label": "Réduire la taille du pinceau dans MaskEditor" }, "Comfy_MaskEditor_BrushSize_Increase": { "label": "Augmenter la taille du pinceau dans MaskEditor" }, + "Comfy_MaskEditor_ColorPicker": { + "label": "Ouvrir le sélecteur de couleur dans MaskEditor" + }, + "Comfy_MaskEditor_Mirror_Horizontal": { + "label": "Miroir horizontal dans MaskEditor" + }, + "Comfy_MaskEditor_Mirror_Vertical": { + "label": "Miroir vertical dans MaskEditor" + }, "Comfy_MaskEditor_OpenMaskEditor": { "label": "Ouvrir l'éditeur de masque pour le nœud sélectionné" }, + "Comfy_MaskEditor_Rotate_Left": { + "label": "Rotation à gauche dans MaskEditor" + }, + "Comfy_MaskEditor_Rotate_Right": { + "label": "Rotation à droite dans MaskEditor" + }, "Comfy_Memory_UnloadModels": { "label": "Décharger les modèles" }, @@ -197,12 +245,18 @@ "Comfy_QueueSelectedOutputNodes": { "label": "Mettre en file d’attente les nœuds de sortie sélectionnés" }, + "Comfy_Queue_ToggleOverlay": { + "label": "Afficher/masquer l'historique des tâches" + }, "Comfy_Redo": { "label": "Refaire" }, "Comfy_RefreshNodeDefinitions": { "label": "Actualiser les définitions de nœud" }, + "Comfy_RenameWorkflow": { + "label": "Renommer le workflow" + }, "Comfy_SaveWorkflow": { "label": "Enregistrer le flux de travail" }, @@ -221,6 +275,12 @@ "Comfy_ToggleHelpCenter": { "label": "Centre d'aide" }, + "Comfy_ToggleLinear": { + "label": "basculer en mode linéaire" + }, + "Comfy_ToggleQPOV2": { + "label": "Basculer vers Queue Panel V2" + }, "Comfy_ToggleTheme": { "label": "Changer de thème (Sombre/Clair)" }, diff --git a/src/locales/fr/main.json b/src/locales/fr/main.json index 51d56cbf9..dd864ebc6 100644 --- a/src/locales/fr/main.json +++ b/src/locales/fr/main.json @@ -1,6 +1,8 @@ { "actionbar": { - "dockToTop": "Ancrer en haut" + "dockToTop": "Ancrer en haut", + "feedback": "Retour", + "feedbackTooltip": "Retour" }, "apiNodesCostBreakdown": { "costPerRun": "Coût par exécution", @@ -18,23 +20,141 @@ "assetCard": "Ressource {name} - {type}", "loadingAsset": "Chargement de la ressource" }, + "assetCollection": "Collection d'actifs", "assets": "Ressources", "baseModels": "Modèles de base", "browseAssets": "Parcourir les ressources", + "byType": "Par type", + "checkpoints": "Checkpoints", + "civitaiLinkExample": "{example} {link}", + "civitaiLinkExampleStrong": "Exemple :", + "civitaiLinkExampleUrl": "https://civitai.com/models/10706/luisap-z-image-and-qwen-pixel-art-refiner?modelVersionId=2225295", + "civitaiLinkLabel": "Lien {download} du modèle Civitai", + "civitaiLinkLabelDownload": "télécharger", + "civitaiLinkPlaceholder": "Collez le lien ici", + "confirmModelDetails": "Confirmer les détails du modèle", "connectionError": "Veuillez vérifier votre connexion et réessayer", + "deletion": { + "body": "Ce modèle sera définitivement supprimé de votre bibliothèque.", + "complete": "{assetName} a été supprimé.", + "failed": "{assetName} n'a pas pu être supprimé.", + "header": "Supprimer ce modèle ?", + "inProgress": "Suppression de {assetName}..." + }, + "download": { + "complete": "Téléchargement terminé", + "failed": "Échec du téléchargement", + "inProgress": "Téléchargement de {assetName}..." + }, + "emptyImported": { + "canImport": "Aucun modèle importé pour le moment. Cliquez sur « Importer un modèle » pour ajouter le vôtre.", + "restricted": "Les modèles personnels sont disponibles uniquement à partir du niveau Creator." + }, + "errorFileTooLarge": "Le fichier dépasse la taille maximale autorisée", + "errorFormatNotAllowed": "Seul le format SafeTensor est autorisé", + "errorModelTypeNotSupported": "Ce type de modèle n'est pas pris en charge", + "errorUnknown": "Une erreur inattendue s'est produite", + "errorUnsafePickleScan": "CivitAI a détecté un code potentiellement dangereux dans ce fichier", + "errorUnsafeVirusScan": "CivitAI a détecté un logiciel malveillant ou un contenu suspect dans ce fichier", + "errorUploadFailed": "Échec de l'importation de l'actif. Veuillez réessayer.", "failedToCreateNode": "Échec de la création du nœud. Veuillez réessayer ou consulter la console pour plus de détails.", "fileFormats": "Formats de fichier", + "fileName": "Nom du fichier", + "fileSize": "Taille du fichier", + "filterBy": "Filtrer par", + "findInLibrary": "Trouvez-le dans la section {type} de la bibliothèque de modèles.", + "finish": "Terminer", + "genericLinkPlaceholder": "Collez le lien ici", + "importAnother": "Importer un autre", + "imported": "Importé", + "jobId": "ID de tâche", "loadingModels": "Chargement de {type}...", + "maxFileSize": "Taille maximale du fichier : {size}", + "maxFileSizeValue": "1 Go", + "media": { + "audioPlaceholder": "Audio", + "threeDModelPlaceholder": "Modèle 3D" + }, + "modelAssociatedWithLink": "Le modèle associé au lien que vous avez fourni :", + "modelInfo": { + "addBaseModel": "Ajouter un modèle de base...", + "addTag": "Ajouter un tag...", + "additionalTags": "Tags supplémentaires", + "baseModelUnknown": "Modèle de base inconnu", + "basicInfo": "Informations de base", + "compatibleBaseModels": "Modèles de base compatibles", + "description": "Description", + "descriptionNotSet": "Aucune description définie", + "descriptionPlaceholder": "Ajoutez une description pour ce modèle...", + "displayName": "Nom d'affichage", + "editDisplayName": "Modifier le nom affiché", + "fileName": "Nom du fichier", + "modelDescription": "Description du modèle", + "modelTagging": "Étiquetage du modèle", + "modelType": "Type de modèle", + "noAdditionalTags": "Aucun tag supplémentaire", + "selectModelPrompt": "Sélectionnez un modèle pour voir ses informations", + "selectModelType": "Sélectionner le type de modèle...", + "source": "Source", + "title": "Infos du modèle", + "triggerPhrases": "Phrases déclencheuses", + "viewOnSource": "Voir sur {source}" + }, + "modelName": "Nom du modèle", + "modelNamePlaceholder": "Entrez un nom pour ce modèle", + "modelTypeSelectorLabel": "Quel type de modèle est-ce ?", + "modelTypeSelectorPlaceholder": "Sélectionnez le type de modèle", + "modelUploaded": "Modèle importé avec succès.", "noAssetsFound": "Aucune ressource trouvée", "noModelsInFolder": "Aucun {type} disponible dans ce dossier", - "searchAssetsPlaceholder": "Rechercher des ressources...", + "noValidSourceDetected": "Aucune source d'importation valide détectée", + "notSureLeaveAsIs": "Vous n'êtes pas sûr ? Laissez tel quel", + "onlyCivitaiUrlsSupported": "Seules les URL Civitai sont prises en charge", + "ownership": "Propriété", + "ownershipAll": "Tous", + "ownershipMyModels": "Mes modèles", + "ownershipPublicModels": "Modèles publics", + "processingModel": "Téléchargement démarré", + "processingModelDescription": "Vous pouvez fermer cette fenêtre. Le téléchargement continuera en arrière-plan.", + "providerCivitai": "Civitai", + "providerHuggingFace": "Hugging Face", + "rename": { + "failed": "Impossible de renommer l'actif." + }, + "selectFrameworks": "Sélectionner les frameworks", + "selectModelType": "Sélectionner le type de modèle", + "selectProjects": "Sélectionner les projets", "sortAZ": "A-Z", "sortBy": "Trier par", "sortPopular": "Populaire", "sortRecent": "Récent", "sortZA": "Z-A", + "sortingType": "Type de tri", + "tags": "Tags", + "tagsHelp": "Séparez les tags par des virgules", + "tagsPlaceholder": "ex. : modèles, checkpoint", "tryAdjustingFilters": "Essayez d'ajuster votre recherche ou vos filtres", - "unknown": "Inconnu" + "unknown": "Inconnu", + "unsupportedUrlSource": "Seules les URL provenant de {sources} sont prises en charge", + "upgradeFeatureDescription": "Cette fonctionnalité est disponible uniquement avec les abonnements Creator ou Pro.", + "upgradeToUnlockFeature": "Passez à la version supérieure pour débloquer cette fonctionnalité", + "upload": "Importer", + "uploadFailed": "Échec de l'importation", + "uploadModel": "Importer", + "uploadModelDescription1": "Collez un lien de téléchargement de modèle Civitai pour l'ajouter à votre bibliothèque.", + "uploadModelDescription1Generic": "Collez un lien de téléchargement de modèle pour l'ajouter à votre bibliothèque.", + "uploadModelDescription2": "Seuls les liens provenant de {link} sont actuellement pris en charge", + "uploadModelDescription2Generic": "Seules les URL des fournisseurs suivants sont prises en charge :", + "uploadModelDescription2Link": "https://civitai.com/models", + "uploadModelDescription3": "Taille maximale du fichier : {size}", + "uploadModelFailedToRetrieveMetadata": "Impossible de récupérer les métadonnées. Veuillez vérifier le lien et réessayer.", + "uploadModelFromCivitai": "Importer un modèle depuis Civitai", + "uploadModelGeneric": "Importer un modèle", + "uploadModelHelpFooterText": "Besoin d'aide pour trouver les URL ? Cliquez sur un fournisseur ci-dessous pour voir une vidéo explicative.", + "uploadModelHelpVideo": "Vidéo d'aide à l'importation de modèle", + "uploadModelHowDoIFindThis": "Comment trouver ceci ?", + "uploadSuccess": "Modèle importé avec succès !", + "uploadingModel": "Importation du modèle..." }, "auth": { "apiKey": { @@ -148,12 +268,19 @@ "title": "Créer un compte" } }, + "boundingBox": { + "height": "Hauteur", + "width": "Largeur", + "x": "X", + "y": "Y" + }, "breadcrumbsMenu": { "clearWorkflow": "Effacer le workflow", "deleteBlueprint": "Supprimer le plan", "deleteWorkflow": "Supprimer le workflow", "duplicate": "Dupliquer", - "enterNewName": "Entrez un nouveau nom" + "enterNewName": "Entrez un nouveau nom", + "missingNodesWarning": "Le workflow contient des nœuds non pris en charge (surlignés en rouge)." }, "clipboard": { "errorMessage": "Échec de la copie dans le presse-papiers", @@ -207,6 +334,7 @@ }, "retry": "Réessayer", "retrying": "Nouvelle tentative...", + "skipToCloudApp": "Passer à l'application cloud", "start": { "desc": "Aucune configuration requise. Fonctionne sur tous les appareils.", "download": "Télécharger ComfyUI", @@ -340,6 +468,8 @@ "Edit Subgraph Widgets": "Modifier les widgets du sous-graphe", "Expand": "Développer", "Expand Node": "Développer le nœud", + "Extensions": "Extensions", + "FavoriteWidget": "Ajouter aux favoris", "Horizontal": "Horizontal", "Inputs": "Entrées", "Left": "Gauche", @@ -359,6 +489,7 @@ "Remove": "Supprimer", "Remove Bypass": "Supprimer le contournement", "Rename": "Renommer", + "RenameWidget": "Renommer le widget", "Resize": "Redimensionner", "Right": "Droite", "Run Branch": "Exécuter la branche", @@ -369,6 +500,7 @@ "Shapes": "Formes", "Title": "Titre", "Top": "Haut", + "UnfavoriteWidget": "Retirer des favoris", "Unpack Subgraph": "Décompresser le sous-graphe", "Unpin": "Désépingler", "Vertical": "Vertical", @@ -382,6 +514,7 @@ "additionalInfo": "Informations supplémentaires", "apiPricing": "Tarification de l’API", "credits": "Crédits", + "creditsAvailable": "Crédits disponibles", "details": "Détails", "eventType": "Type d'événement", "faqs": "FAQ", @@ -390,15 +523,46 @@ "messageSupport": "Contacter le support", "model": "Modèle", "purchaseCredits": "Acheter des crédits", + "refreshes": "Rafraîchit le {date}", "time": "Heure", "topUp": { + "addMoreCredits": "Ajouter des crédits", + "addMoreCreditsToRun": "Ajouter des crédits pour exécuter", + "amountToPayLabel": "Montant à payer en dollars", + "buy": "Acheter", + "buyCredits": "Continuer vers le paiement", "buyNow": "Acheter maintenant", + "contactUs": "Contactez-nous", + "creditsDescription": "Les crédits sont utilisés pour exécuter des workflows ou des nœuds partenaires.", + "creditsPerDollar": "crédits par dollar", + "creditsToReceiveLabel": "Crédits à recevoir", + "howManyCredits": "Combien de crédits souhaitez-vous ajouter ?", "insufficientMessage": "Vous n'avez pas assez de crédits pour exécuter ce workflow.", "insufficientTitle": "Crédits insuffisants", + "insufficientWorkflowMessage": "Vous n'avez pas assez de crédits pour exécuter ce workflow.", + "maxAllowed": "Maximum {credits} crédits.", "maxAmount": "(Max. 1 000 $ US)", + "maximumAmount": "Maximum ${amount}.", + "minRequired": "Minimum {credits} crédits", + "minimumPurchase": "Achat minimum de ${amount} ({credits} crédits)", + "needMore": "Besoin de plus ?", + "purchaseError": "Échec de l'achat", + "purchaseErrorDetail": "Échec de l'achat de crédits : {error}", "quickPurchase": "Achat rapide", "seeDetails": "Voir les détails", - "topUp": "Recharger" + "selectAmount": "Sélectionner le montant", + "templateNote": "*Généré avec le modèle Wan Fun Control", + "topUp": "Recharger", + "unknownError": "Une erreur inconnue est survenue", + "usdAmount": "${amount}", + "videosEstimate": "~{count} vidéos", + "viewPricing": "Voir les détails des tarifs", + "youGet": "Crédits", + "youPay": "Montant (USD)" + }, + "unified": { + "message": "Les crédits ont été unifiés", + "tooltip": "Nous avons unifié les paiements sur Comfy. Tout fonctionne désormais avec les Comfy Credits :\n- Nœuds partenaires (anciennement nœuds API)\n- Workflows cloud\n\nVotre solde de nœuds partenaires a été converti en crédits." }, "yourCreditBalance": "Votre solde de crédits" }, @@ -414,6 +578,9 @@ "CLIP_VISION": "CLIP_VISION", "CLIP_VISION_OUTPUT": "SORTIE_CLIP_VISION", "COMBO": "COMBO", + "COMFY_AUTOGROW_V3": "COMFY_AUTOGROW_V3", + "COMFY_DYNAMICCOMBO_V3": "COMFY_DYNAMICCOMBO_V3", + "COMFY_MATCHTYPE_V3": "COMFY_MATCHTYPE_V3", "CONDITIONING": "CONDITIONNEMENT", "CONTROL_NET": "RESEAU_DE_CONTROLE", "FLOAT": "FLOTTANT", @@ -424,18 +591,21 @@ "HOOKS": "CROCHETS", "HOOK_KEYFRAMES": "CLEFS_DE_CROCHET", "IMAGE": "IMAGE", + "IMAGECOMPARE": "COMPARAISON_IMAGE", "INT": "ENTIER", "LATENT": "LATENT", "LATENT_OPERATION": "OPERATION_LATENTE", + "LATENT_UPSCALE_MODEL": "LATENT_UPSCALE_MODEL", "LOAD3D_CAMERA": "Charger la caméra 3D", "LOAD_3D": "CHARGER_3D", - "LOAD_3D_ANIMATION": "CHARGER_ANIMATION_3D", "LORA_MODEL": "MODÈLE_LORA", "LOSS_MAP": "CARTE_PERTES", "LUMA_CONCEPTS": "Concepts Luma", "LUMA_REF": "Référence Luma", "MASK": "MASQUE", "MESH": "MAILLAGE", + "MESHY_RIGGED_TASK_ID": "MESHY_RIGGED_TASK_ID", + "MESHY_TASK_ID": "MESHY_TASK_ID", "MODEL": "MODÈLE", "MODEL_PATCH": "PATCH_MODÈLE", "MODEL_TASK_ID": "ID_TÂCHE_MODÈLE", @@ -455,6 +625,7 @@ "STYLE_MODEL": "MODÈLE_DE_STYLE", "SVG": "SVG", "TIMESTEPS_RANGE": "PLAGE_DES_ÉTAPES_TEMPORELLES", + "TRACKS": "PISTES", "UPSCALE_MODEL": "MODÈLE_DE_MISE_À_L'ÉCHELLE", "VAE": "VAE", "VIDEO": "Vidéo", @@ -523,14 +694,17 @@ "amount": "Quantité", "apply": "Appliquer", "architecture": "Architecture", + "asset": "{count} ressources | {count} ressource | {count} ressources", "audioFailedToLoad": "Échec du chargement de l'audio", "audioProgress": "Progression audio", "author": "Auteur", "back": "Retour", + "batchRename": "Renommer en lot", "beta": "BÊTA", "bookmark": "Enregistrer dans la bibliothèque", "calculatingDimensions": "Calcul des dimensions", "cancel": "Annuler", + "cancelled": "Annulé", "capture": "capture", "category": "Catégorie", "chart": "Graphique", @@ -540,6 +714,7 @@ "clearAll": "Tout effacer", "clearFilters": "Effacer les filtres", "close": "Fermer", + "closeDialog": "Fermer la boîte de dialogue", "color": "Couleur", "comfy": "Comfy", "comfyOrgLogoAlt": "Logo ComfyOrg", @@ -556,13 +731,17 @@ "control_before_generate": "contrôle avant génération", "copied": "Copié", "copy": "Copier", + "copyAll": "Tout copier", "copyJobId": "Copier l'ID du travail", "copyToClipboard": "Copier dans le presse-papiers", "copyURL": "Copier l’URL", + "core": "Noyau", "currentUser": "Utilisateur actuel", + "custom": "Personnalisé", "customBackground": "Arrière-plan personnalisé", "customize": "Personnaliser", "customizeFolder": "Personnaliser le dossier", + "decrement": "Diminuer", "defaultBanner": "bannière par défaut", "delete": "Supprimer", "deleteAudioFile": "Supprimer le fichier audio", @@ -571,27 +750,35 @@ "description": "Description", "devices": "Appareils", "disableAll": "Désactiver tout", + "disableSelected": "Désactiver la sélection", + "disableThirdParty": "Désactiver les tiers", "disabling": "Désactivation", "dismiss": "Fermer", "download": "Télécharger", "downloadImage": "Télécharger l'image", "downloadVideo": "Télécharger la vidéo", + "downloading": "Téléchargement", "dropYourFileOr": "Déposez votre fichier ou", "duplicate": "Dupliquer", "edit": "Modifier", "editImage": "Modifier l'image", "editOrMaskImage": "Modifier ou masquer l'image", + "emDash": "—", "empty": "Vide", "enableAll": "Activer tout", "enableOrDisablePack": "Activer ou désactiver le pack", + "enableSelected": "Activer la sélection", "enabled": "Activé", "enabling": "Activation", + "enterBaseName": "Entrez le nom de base", + "enterNewName": "Entrez le nouveau nom", "error": "Erreur", "errorLoadingImage": "Erreur lors du chargement de l'image", "errorLoadingVideo": "Erreur lors du chargement de la vidéo", "experimental": "BETA", "export": "Exportation", "extensionName": "Nom de l'extension", + "failed": "Échec", "failedToCopyJobId": "Échec de la copie de l'ID du travail", "failedToDownloadImage": "Échec du téléchargement de l'image", "failedToDownloadVideo": "Échec du téléchargement de la vidéo", @@ -607,12 +794,15 @@ "goToNode": "Aller au nœud", "graphNavigation": "Navigation dans le graphe", "halfSpeed": "0.5x", + "hideLeftPanel": "Masquer le panneau de gauche", + "hideRightPanel": "Masquer le panneau de droite", "icon": "Icône", "imageFailedToLoad": "Échec du chargement de l'image", "imagePreview": "Aperçu de l'image - Utilisez les flèches pour naviguer entre les images", "imageUrl": "URL de l'image", "import": "Importer", "inProgress": "En cours", + "increment": "Augmenter", "info": "Informations du nœud", "insert": "Insérer", "install": "Installer", @@ -620,7 +810,9 @@ "installing": "Installation", "interrupted": "Interrompu", "itemSelected": "{selectedCount} élément sélectionné", + "itemsCopiedToClipboard": "Éléments copiés dans le presse-papiers", "itemsSelected": "{selectedCount} éléments sélectionnés", + "job": "Tâche", "jobIdCopied": "ID du travail copié dans le presse-papiers", "keybinding": "Raccourci clavier", "keybindingAlreadyExists": "Le raccourci clavier existe déjà", @@ -638,14 +830,18 @@ "micPermissionDenied": "Permission du microphone refusée", "migrate": "Migrer", "missing": "Manquant", + "more": "Plus", "moreOptions": "Plus d'options", "moreWorkflows": "Plus de workflows", "multiSelectDropdown": "Menu déroulant à sélection multiple", "name": "Nom", "newFolder": "Nouveau dossier", "next": "Suivant", + "nightly": "NIGHTLY", "no": "Non", "noAudioRecorded": "Aucun audio enregistré", + "noItems": "Aucun élément", + "noResults": "Aucun résultat", "noResultsFound": "Aucun résultat trouvé", "noTasksFound": "Aucune tâche trouvée", "noTasksFoundMessage": "Il n'y a pas de tâches dans la file d'attente.", @@ -656,26 +852,45 @@ "nodeSlotsError": "Erreur d'emplacements du nœud", "nodeWidgetsError": "Erreur de widgets du nœud", "nodes": "Nœuds", + "nodesCount": "{count} nœuds | {count} nœud | {count} nœuds", "nodesRunning": "nœuds en cours d’exécution", "none": "Aucun", + "nothingToCopy": "Rien à copier", + "nothingToDelete": "Rien à supprimer", + "nothingToDuplicate": "Rien à dupliquer", + "nothingToRename": "Rien à renommer", "ok": "OK", "openManager": "Ouvrir le gestionnaire", "openNewIssue": "Ouvrir un nouveau problème", + "or": "ou", "overwrite": "Écraser", + "playPause": "Lecture/Pause", "playRecording": "Lire l'enregistrement", "playbackSpeed": "Vitesse de lecture", "playing": "Lecture en cours", "pressKeysForNewBinding": "Appuyez sur les touches pour une nouvelle liaison", "preview": "APERÇU", + "profile": "Profil", "progressCountOf": "sur", + "queued": "En file d’attente", "ready": "Prêt", "reconnected": "Reconnecté", "reconnecting": "Reconnexion", "refresh": "Rafraîchir", "refreshNode": "Actualiser le nœud", + "relativeTime": { + "daysAgo": "il y a {count}j", + "hoursAgo": "il y a {count}h", + "minutesAgo": "il y a {count}min", + "monthsAgo": "il y a {count}mo", + "now": "maintenant", + "weeksAgo": "il y a {count}s", + "yearsAgo": "il y a {count}a" + }, "releaseTitle": "Publication de {package} {version}", "reloadToApplyChanges": "Recharger pour appliquer les modifications", "removeImage": "Supprimer l'image", + "removeTag": "Supprimer le tag", "removeVideo": "Supprimer la vidéo", "rename": "Renommer", "reportIssue": "Envoyer le rapport", @@ -690,21 +905,31 @@ "resizeFromTopRight": "Redimensionner depuis le coin supérieur droit", "restart": "Redémarrer", "resultsCount": "{count} Résultats Trouvés", + "running": "En cours", "save": "Enregistrer", "saving": "Enregistrement", + "scrollLeft": "Faire défiler à gauche", + "scrollRight": "Faire défiler à droite", "search": "Rechercher", "searchExtensions": "Rechercher des extensions", "searchFailedMessage": "Nous n'avons trouvé aucun paramètre correspondant à votre recherche. Essayez d'ajuster vos termes de recherche.", "searchKeybindings": "Rechercher des raccourcis clavier", "searchModels": "Rechercher des modèles", "searchNodes": "Rechercher des nœuds", + "searchPlaceholder": "Rechercher...", "searchSettings": "Rechercher des paramètres", "searchWorkflows": "Rechercher des flux de travail", "seeTutorial": "Voir un tutoriel", + "selectItemsToCopy": "Sélectionnez les éléments à copier", + "selectItemsToDelete": "Sélectionnez les éléments à supprimer", + "selectItemsToDuplicate": "Sélectionnez les éléments à dupliquer", + "selectItemsToRename": "Sélectionnez les éléments à renommer", "selectedFile": "Fichier sélectionné", "setAsBackground": "Définir comme arrière-plan", "settings": "Paramètres", + "showLeftPanel": "Afficher le panneau de gauche", "showReport": "Afficher le rapport", + "showRightPanel": "Afficher le panneau de droite", "singleSelectDropdown": "Menu déroulant à sélection unique", "sort": "Trier", "source": "Source", @@ -712,12 +937,14 @@ "status": "Statut", "stopPlayback": "Arrêter la lecture", "stopRecording": "Arrêter l’enregistrement", + "submit": "Soumettre", "success": "Succès", "systemInfo": "Informations système", "terminal": "Terminal", "title": "Titre", "triggerPhrase": "Phrase déclencheuse", "unknownError": "Erreur inconnue", + "untitled": "Sans titre", "update": "Mettre à jour", "updateAvailable": "Mise à jour disponible", "updateFrontend": "Mettre à jour le frontend", @@ -725,6 +952,7 @@ "updating": "Mise à jour", "upload": "Téléverser", "usageHint": "Conseil d'utilisation", + "use": "Utiliser", "user": "Utilisateur", "versionMismatchWarning": "Avertissement de compatibilité de version", "versionMismatchWarningMessage": "{warning} : {detail} Consultez https://docs.comfy.org/installation/update_comfyui#common-update-issues pour les instructions de mise à jour.", @@ -732,11 +960,10 @@ "videoPreview": "Aperçu de la vidéo - Utilisez les flèches pour naviguer entre les vidéos", "viewImageOfTotal": "Voir l'image {index} sur {total}", "viewVideoOfTotal": "Voir la vidéo {index} sur {total}", - "vitePreloadErrorMessage": "Une nouvelle version de l'application a été publiée. Souhaitez-vous recharger ?\nSi vous refusez, certaines parties de l'application pourraient ne pas fonctionner correctement.\nN'hésitez pas à refuser et à sauvegarder votre progression avant de recharger.", - "vitePreloadErrorTitle": "Nouvelle version disponible", "volume": "Volume", "warning": "Avertissement", - "workflow": "Flux de travail" + "workflow": "Flux de travail", + "you": "Vous" }, "graphCanvasMenu": { "fitView": "Adapter la vue", @@ -758,12 +985,17 @@ "create": "Créer un nœud de groupe", "enterName": "Entrer le nom" }, + "help": { + "helpCenterMenu": "Menu du centre d’aide", + "recentReleases": "Dernières versions" + }, "helpCenter": { "clickToLearnMore": "Cliquez pour en savoir plus →", "desktopUserGuide": "Guide utilisateur de bureau", "docs": "Docs", + "feedback": "Donner un avis", "github": "Github", - "helpFeedback": "Aide & Retour", + "help": "Aide & Support", "loadingReleases": "Chargement des versions...", "managerExtension": "Manager Extension", "more": "Plus...", @@ -772,6 +1004,12 @@ "recentReleases": "Versions récentes", "reinstall": "Réinstaller", "updateAvailable": "Mise à jour", + "updateComfyUI": "Mettre à jour ComfyUI", + "updateComfyUIFailed": "Échec de la mise à jour de ComfyUI. Veuillez réessayer.", + "updateComfyUIStarted": "Mise à jour commencée", + "updateComfyUIStartedDetail": "La mise à jour de ComfyUI a été ajoutée à la file d’attente. Veuillez patienter...", + "updateComfyUISuccess": "Mise à jour terminée", + "updateComfyUISuccessDetail": "ComfyUI a été mis à jour. Redémarrage en cours...", "whatsNew": "Quoi de neuf ?" }, "icon": { @@ -785,6 +1023,18 @@ "inbox": "Boîte de réception", "star": "Étoile" }, + "imageCompare": { + "noImages": "Aucune image à comparer" + }, + "imageCrop": { + "cropPreviewAlt": "Aperçu du recadrage", + "loading": "Chargement...", + "noInputImage": "Aucune image d'entrée connectée" + }, + "importFailed": { + "copyError": "Erreur de copie", + "title": "Échec de l’importation" + }, "install": { "appDataLocationTooltip": "Répertoire des données de l'application ComfyUI. Stocke :\n- Logs\n- Configurations du serveur", "appPathLocationTooltip": "Répertoire des ressources de l'application ComfyUI. Stocke le code et les ressources de ComfyUI", @@ -798,6 +1048,8 @@ "failedToSelectDirectory": "Échec de la sélection du répertoire", "gpu": "GPU", "gpuPicker": { + "amdDescription": "Utilisez votre GPU AMD avec l’accélération ROCm™ pour des performances optimales.", + "amdSubtitle": "AMD ROCm™", "appleMetalDescription": "Exploite le GPU de votre Mac pour une vitesse plus rapide et une meilleure expérience globale", "cpuDescription": "Utilisez le mode CPU pour la compatibilité lorsque l'accélération GPU n'est pas disponible", "cpuSubtitle": "Mode CPU", @@ -824,6 +1076,8 @@ "selectGpuDescription": "Sélectionnez le type de GPU que vous avez" }, "helpImprove": "Veuillez aider à améliorer ComfyUI", + "insideAppInstallDir": "Ce dossier se trouve dans le bundle de l’application ComfyUI Desktop et sera supprimé lors des mises à jour. Choisissez un répertoire en dehors du dossier d’installation, comme Documents/ComfyUI.", + "insideUpdaterCache": "Ce dossier se trouve dans le cache de mise à jour de ComfyUI, qui est effacé à chaque mise à jour. Sélectionnez un autre emplacement pour vos données.", "installLocation": "Emplacement d'installation", "installLocationDescription": "Sélectionnez le répertoire pour les données utilisateur de ComfyUI. Un environnement python sera installé à l'emplacement sélectionné. Veuillez vous assurer que le disque sélectionné a suffisamment d'espace (~15GB) restant.", "installLocationTooltip": "Répertoire des données utilisateur de ComfyUI. Stocke :\n- Environnement Python\n- Modèles\n- Nœuds personnalisés\n", @@ -898,6 +1152,16 @@ "issueReport": { "helpFix": "Aidez à résoudre cela" }, + "linearMode": { + "beta": "Bêta - Donnez votre avis", + "downloadAll": "Tout télécharger", + "dragAndDropImage": "Glissez-déposez une image", + "graphMode": "Mode graphique", + "linearMode": "Mode simple", + "rerun": "Relancer", + "reuseParameters": "Réutiliser les paramètres", + "runCount": "Nombre d’exécutions :" + }, "load3d": { "applyingTexture": "Application de la texture...", "backgroundColor": "Couleur de fond", @@ -924,20 +1188,24 @@ "lineart": "Lineart", "normal": "Normal", "original": "Original", + "pointCloud": "Nuage de points", "wireframe": "Fil de fer" }, "model": "Modèle", "openIn3DViewer": "Ouvrir dans le visualiseur 3D", + "panoramaMode": "Panorama", "previewOutput": "Aperçu de la sortie", "reloadingModel": "Rechargement du modèle...", "removeBackgroundImage": "Supprimer l'image de fond", "resizeNodeMatchOutput": "Redimensionner le nœud pour correspondre à la sortie", "scene": "Scène", "showGrid": "Afficher la grille", + "showSkeleton": "Afficher le squelette", "startRecording": "Démarrer l'enregistrement", "stopRecording": "Arrêter l'enregistrement", "switchCamera": "Changer de caméra", "switchingMaterialMode": "Changement de mode de matériau...", + "tiledMode": "Mosaïque", "unsupportedFileType": "Type de fichier non pris en charge (supporte .gltf, .glb, .obj, .fbx, .stl)", "upDirection": "Direction Haut", "upDirections": { @@ -958,6 +1226,11 @@ "title": "Visualiseur 3D (Bêta)" } }, + "loadWorkflowWarning": { + "coreNodesFromVersion": "Nœuds principaux de la version {version} :", + "outdatedVersion": "Ce workflow a été créé avec une version plus récente de ComfyUI ({version}). Certains nœuds peuvent ne pas fonctionner correctement.", + "outdatedVersionGeneric": "Ce workflow a été créé avec une version plus récente de ComfyUI. Certains nœuds peuvent ne pas fonctionner correctement." + }, "maintenance": { "None": "Aucun", "OK": "OK", @@ -976,7 +1249,15 @@ "showManual": "Afficher les tâches de maintenance", "status": "Statut", "terminalDefaultMessage": "Lorsque vous exécutez une commande de dépannage, toute sortie sera affichée ici.", - "title": "Maintenance" + "title": "Maintenance", + "unsafeMigration": { + "action": "Utilisez la tâche de maintenance « Chemin de base » ci-dessous pour déplacer ComfyUI vers un emplacement sécurisé.", + "appInstallDir": "Votre chemin de base se trouve dans le bundle de l’application ComfyUI Desktop. Ce dossier peut être supprimé ou écrasé lors des mises à jour. Choisissez un répertoire en dehors du dossier d’installation, comme Documents/ComfyUI.", + "generic": "Votre chemin de base ComfyUI actuel se trouve dans un emplacement qui peut être supprimé ou modifié lors des mises à jour. Pour éviter toute perte de données, déplacez-le dans un dossier sécurisé.", + "oneDrive": "Votre chemin de base se trouve sur OneDrive, ce qui peut entraîner des problèmes de synchronisation et une perte accidentelle de données. Choisissez un dossier local qui n’est pas géré par OneDrive.", + "title": "Emplacement d’installation non sécurisé détecté", + "updaterCache": "Votre chemin de base se trouve dans le cache de mise à jour de ComfyUI, qui est effacé à chaque mise à jour. Choisissez un autre emplacement pour vos données." + } }, "manager": { "allMissingNodesInstalled": "Tous les nœuds manquants ont été installés avec succès", @@ -1077,6 +1358,8 @@ "totalNodes": "Total de Nœuds", "tryAgainLater": "Veuillez réessayer plus tard.", "tryDifferentSearch": "Veuillez essayer une autre requête de recherche.", + "tryUpdate": "Essayer de mettre à jour", + "tryUpdateTooltip": "Récupérer les dernières modifications du dépôt. Les versions nightly peuvent avoir des mises à jour qui ne sont pas détectées automatiquement.", "uninstall": "Désinstaller", "uninstallSelected": "Désinstaller sélectionné", "uninstalling": "Désinstallation", @@ -1087,31 +1370,110 @@ "version": "Version" }, "maskEditor": { + "activateLayer": "Activer le calque", + "applyToWholeImage": "Appliquer à toute l'image", + "baseImageLayer": "Calque d'image de base", + "baseLayerPreview": "Aperçu du calque de base", + "black": "Noir", + "brushSettings": "Paramètres du pinceau", + "brushShape": "Forme du pinceau", + "clear": "Effacer", + "clickToResetZoom": "Cliquez pour réinitialiser le zoom", + "colorSelectSettings": "Paramètres de sélection de couleur", + "colorSelector": "Sélecteur de couleur", + "fillOpacity": "Opacité de remplissage", + "hardness": "Dureté", + "imageLayer": "Calque d'image", + "invert": "Inverser", + "layers": "Calques", + "livePreview": "Aperçu en direct", + "maskBlendingOptions": "Options de fusion du masque", + "maskLayer": "Calque de masque", + "maskOpacity": "Opacité du masque", + "maskTolerance": "Tolérance du masque", + "method": "Méthode", + "mirrorHorizontal": "Miroir horizontal", + "mirrorVertical": "Miroir vertical", + "negative": "Négatif", + "opacity": "Opacité", + "paintBucketSettings": "Paramètres du pot de peinture", + "paintLayer": "Calque de peinture", + "redo": "Rétablir", + "resetToDefault": "Réinitialiser par défaut", + "rotateLeft": "Tourner à gauche", + "rotateRight": "Tourner à droite", + "selectionOpacity": "Opacité de la sélection", + "smoothingPrecision": "Précision du lissage", + "stepSize": "Taille de pas", + "stopAtMask": "Arrêter au masque", + "thickness": "Épaisseur", + "title": "Éditeur de masque", + "tolerance": "Tolérance", + "undo": "Annuler", + "white": "Blanc" }, "mediaAsset": { + "actions": { + "copyJobId": "Copier l’ID de tâche", + "delete": "Supprimer", + "download": "Télécharger", + "exportWorkflow": "Exporter le flux de travail", + "insertAsNodeInWorkflow": "Insérer comme nœud dans le workflow", + "inspect": "Inspecter l’actif", + "more": "Plus d’options", + "moreOptions": "Plus d’options", + "openWorkflow": "Ouvrir comme flux de travail dans un nouvel onglet", + "seeMoreOutputs": "Voir plus de sorties", + "zoom": "Zoomer" + }, "assetDeletedSuccessfully": "Élément supprimé avec succès", "deleteAssetDescription": "Cet élément sera définitivement supprimé.", "deleteAssetTitle": "Supprimer cet élément ?", "deleteSelectedDescription": "{count} élément(s) sera(ont) définitivement supprimé(s).", "deleteSelectedTitle": "Supprimer les éléments sélectionnés ?", "deletingImportedFilesCloudOnly": "La suppression des fichiers importés n'est prise en charge que dans la version cloud", + "failedToCreateNode": "Échec de la création du nœud", "failedToDeleteAsset": "Échec de la suppression de l'élément", + "failedToExportWorkflow": "Échec de l’exportation du flux de travail", "jobIdToast": { "copied": "Copié", "error": "Erreur", "jobIdCopied": "ID de tâche copié dans le presse-papiers", "jobIdCopyFailed": "Échec de la copie de l'ID de tâche" }, + "noJobIdFound": "Aucun ID de tâche trouvé pour cet actif", + "noWorkflowDataFound": "Aucune donnée de flux de travail trouvée dans cet actif", + "nodeAddedToWorkflow": "Nœud {nodeType} ajouté au flux de travail", + "nodeTypeNotFound": "Type de nœud {nodeType} introuvable", "selection": { "assetsDeletedSuccessfully": "{count} élément(s) supprimé(s) avec succès", "deleteSelected": "Supprimer", + "deleteSelectedAll": "Tout supprimer", "deselectAll": "Tout désélectionner", "downloadSelected": "Télécharger", + "downloadSelectedAll": "Tout télécharger", "downloadStarted": "Téléchargement de {count} fichier(s)...", "downloadsStarted": "Début du téléchargement de {count} fichier(s)", + "exportWorkflowAll": "Exporter tous les workflows", + "failedToAddNodes": "Échec de l’ajout des nœuds au workflow", "failedToDeleteAssets": "Échec de la suppression des éléments sélectionnés", - "selectedCount": "Éléments sélectionnés : {count}" - } + "insertAllAssetsAsNodes": "Insérer toutes les ressources comme nœuds", + "multipleSelectedAssets": "Plusieurs ressources sélectionnées", + "noWorkflowsFound": "Aucune donnée de workflow trouvée dans les ressources sélectionnées", + "noWorkflowsToExport": "Aucune donnée de workflow à exporter", + "nodesAddedToWorkflow": "{count} nœud(s) ajouté(s) au workflow", + "openWorkflowAll": "Ouvrir tous les workflows", + "partialAddNodesSuccess": "{succeeded} ajouté(s) avec succès, {failed} échec(s)", + "partialDeleteSuccess": "{succeeded} supprimé(s) avec succès, {failed} échec(s)", + "partialWorkflowsExported": "{succeeded} exporté(s) avec succès, {failed} échec(s)", + "partialWorkflowsOpened": "{succeeded} workflow(s) ouvert(s), {failed} échec(s)", + "selectedCount": "Éléments sélectionnés : {count}", + "workflowsExported": "{count} workflow(s) exporté(s) avec succès", + "workflowsOpened": "{count} workflow(s) ouvert(s) dans de nouveaux onglets" + }, + "unsupportedFileType": "Type de fichier non pris en charge pour le nœud de chargement", + "workflowExportedSuccessfully": "Flux de travail exporté avec succès", + "workflowOpenedInNewTab": "Flux de travail ouvert dans un nouvel onglet" }, "menu": { "autoQueue": "File d'attente automatique", @@ -1119,11 +1481,13 @@ "batchCountTooltip": "Le nombre de fois que la génération du flux de travail doit être mise en file d'attente", "clear": "Effacer le flux de travail", "clipspace": "Ouvrir Clipspace", + "customNodesManager": "Gestionnaire de nœuds personnalisés", "dark": "Sombre", "disabled": "Désactivé", "disabledTooltip": "Le flux de travail ne sera pas mis en file d'attente automatiquement", "execute": "Exécuter", "help": "Aide", + "helpAndFeedback": "Aide et commentaires", "hideMenu": "Masquer le menu", "instant": "Instantané", "instantTooltip": "Le flux de travail sera mis en file d'attente immédiatement après la fin d'une génération", @@ -1137,6 +1501,7 @@ "resetView": "Réinitialiser la vue du canevas", "run": "Exécuter", "runWorkflow": "Exécuter le workflow (Maj pour mettre en file d'attente en premier)", + "runWorkflowDisabled": "Le workflow contient des nœuds non pris en charge (en rouge). Supprimez-les pour exécuter le workflow.", "runWorkflowFront": "Exécuter le workflow (Mettre en file d'attente en premier)", "settings": "Paramètres", "showMenu": "Afficher le menu", @@ -1152,6 +1517,7 @@ "Canvas Performance": "Performances du canevas", "Canvas Toggle Lock": "Basculer le verrouillage de la toile", "Check for Custom Node Updates": "Vérifier les mises à jour des nœuds personnalisés", + "Check for Updates": "Vérifier les mises à jour", "Clear Pending Tasks": "Effacer les tâches en attente", "Clear Workflow": "Effacer le flux de travail", "Clipspace": "Espace de clip", @@ -1168,13 +1534,14 @@ "Custom Nodes Manager": "Gestionnaire de Nœuds Personnalisés", "Decrease Brush Size in MaskEditor": "Réduire la taille du pinceau dans MaskEditor", "Delete Selected Items": "Supprimer les éléments sélectionnés", + "Desktop User Guide": "Guide utilisateur du bureau", "Duplicate Current Workflow": "Dupliquer le flux de travail actuel", "Edit": "Éditer", "Edit Subgraph Widgets": "Modifier les widgets de sous-graphe", "Exit Subgraph": "Quitter le sous-graphe", "Experimental: Browse Model Assets": "Expérimental : Parcourir les ressources de modèles", "Experimental: Enable AssetAPI": "Expérimental : Activer AssetAPI", - "Experimental: Enable Vue Nodes": "Expérimental : Activer les nœuds Vue", + "Experimental: Enable Nodes 2_0": "Expérimental : Activer Nodes 2.0", "Export": "Exporter", "Export (API)": "Exporter (API)", "File": "Fichier", @@ -1186,12 +1553,15 @@ "Increase Brush Size in MaskEditor": "Augmenter la taille du pinceau dans MaskEditor", "Install Missing Custom Nodes": "Installer les nœuds personnalisés manquants", "Interrupt": "Interrompre", + "Job History": "Historique des tâches", "Load Default Workflow": "Charger le flux de travail par défaut", "Lock Canvas": "Verrouiller le canevas", "Manage group nodes": "Gérer les nœuds de groupe", "Manager": "Gestionnaire", "Manager Menu (Legacy)": "Menu du gestionnaire (héritage)", "Minimap": "Mini-carte", + "Mirror Horizontal in MaskEditor": "Miroir horizontal dans l'éditeur de masque", + "Mirror Vertical in MaskEditor": "Miroir vertical dans l'éditeur de masque", "Model Library": "Bibliothèque de modèles", "Move Selected Nodes Down": "Déplacer les nœuds sélectionnés vers le bas", "Move Selected Nodes Left": "Déplacer les nœuds sélectionnés vers la gauche", @@ -1204,8 +1574,16 @@ "Node Links": "Liens de nœuds", "Open": "Ouvrir", "Open 3D Viewer (Beta) for Selected Node": "Ouvrir le visualiseur 3D (Bêta) pour le nœud sélectionné", + "Open Color Picker in MaskEditor": "Ouvrir le sélecteur de couleur dans MaskEditor", + "Open Custom Nodes Folder": "Ouvrir le dossier des nœuds personnalisés", + "Open DevTools": "Ouvrir les outils de développement", + "Open Inputs Folder": "Ouvrir le dossier des entrées", + "Open Logs Folder": "Ouvrir le dossier des journaux", "Open Mask Editor for Selected Node": "Ouvrir l’éditeur de mask pour le nœud sélectionné", + "Open Models Folder": "Ouvrir le dossier des modèles", + "Open Outputs Folder": "Ouvrir le dossier des sorties", "Open Sign In Dialog": "Ouvrir la boîte de dialogue de connexion", + "Open extra_model_paths_yaml": "Ouvrir extra_model_paths.yaml", "Pin/Unpin Selected Items": "Épingler/Désépingler les éléments sélectionnés", "Pin/Unpin Selected Nodes": "Épingler/Désépingler les nœuds sélectionnés", "Previous Opened Workflow": "Flux de travail ouvert précédent", @@ -1213,10 +1591,16 @@ "Queue Prompt": "Invite de file d'attente", "Queue Prompt (Front)": "Invite de file d'attente (Front)", "Queue Selected Output Nodes": "Mettre en file d’attente les nœuds de sortie sélectionnés", + "Quit": "Quitter", "Redo": "Refaire", "Refresh Node Definitions": "Actualiser les définitions de nœud", + "Reinstall": "Réinstaller", + "Rename": "Renommer", "Reset View": "Réinitialiser la vue", "Resize Selected Nodes": "Redimensionner les nœuds sélectionnés", + "Restart": "Redémarrer", + "Rotate Left in MaskEditor": "Tourner à gauche dans l'éditeur de masque", + "Rotate Right in MaskEditor": "Tourner à droite dans l'éditeur de masque", "Save": "Enregistrer", "Save As": "Enregistrer sous", "Show Keybindings Dialog": "Afficher la boîte de dialogue des raccourcis clavier", @@ -1225,12 +1609,13 @@ "Sign Out": "Se déconnecter", "Toggle Essential Bottom Panel": "Basculer le panneau inférieur essentiel", "Toggle Logs Bottom Panel": "Basculer le panneau inférieur des journaux", + "Toggle Queue Panel V2": "Basculer le panneau de file d’attente V2", "Toggle Search Box": "Basculer la boîte de recherche", + "Toggle Simple Mode": "Basculer en mode simple", "Toggle Terminal Bottom Panel": "Basculer le panneau inférieur du terminal", "Toggle Theme (Dark/Light)": "Basculer le thème (Sombre/Clair)", "Toggle View Controls Bottom Panel": "Basculer le panneau inférieur des contrôles d’affichage", "Toggle promotion of hovered widget": "Basculer la promotion du widget survolé", - "Toggle the Custom Nodes Manager Progress Bar": "Basculer la barre de progression du gestionnaire de nœuds personnalisés", "Undo": "Annuler", "Ungroup selected group nodes": "Dégrouper les nœuds de groupe sélectionnés", "Unload Models": "Décharger les modèles", @@ -1255,30 +1640,56 @@ "missingModels": "Modèles manquants", "missingModelsMessage": "Lors du chargement du graphique, les modèles suivants n'ont pas été trouvés" }, + "missingNodes": { + "cloud": { + "description": "Ce flux de travail utilise des nœuds personnalisés qui ne sont pas encore pris en charge dans la version Cloud.", + "gotIt": "Ok, compris", + "learnMore": "En savoir plus", + "priorityMessage": "Nous avons automatiquement signalé ces nœuds afin de prioriser leur ajout.", + "replacementInstruction": "En attendant, remplacez ces nœuds (surlignés en rouge sur le canevas) par des nœuds pris en charge si possible, ou essayez un autre flux de travail.", + "title": "Ces nœuds ne sont pas encore disponibles sur Comfy Cloud" + }, + "oss": { + "description": "Ce flux de travail utilise des nœuds personnalisés que vous n’avez pas encore installés.", + "replacementInstruction": "Installez ces nœuds pour exécuter ce flux de travail, ou remplacez-les par des alternatives installées. Les nœuds manquants sont surlignés en rouge sur le canevas.", + "title": "Ce flux de travail a des nœuds manquants" + } + }, + "nightly": { + "badge": { + "label": "Version de prévisualisation", + "tooltip": "Vous utilisez une version nightly de ComfyUI. Veuillez utiliser le bouton de retour pour partager vos impressions sur ces fonctionnalités." + } + }, "nodeCategories": { + "": "", "3d": "3d", "3d_models": "modèles_3d", "BFL": "BFL", + "Bria": "Bria", "ByteDance": "ByteDance", "Gemini": "Gemini", "Ideogram": "Ideogram", "Kling": "Kling", "LTXV": "LTXV", "Luma": "Luma", + "Meshy": "Meshy", "MiniMax": "MiniMax", "Moonvalley Marey": "Moonvalley Marey", "OpenAI": "OpenAI", - "Pika": "Pika", "PixVerse": "PixVerse", "Recraft": "Recraft", "Rodin": "Rodin", "Runway": "Runway", "Sora": "Sora", "Stability AI": "Stability AI", + "Tencent": "Tencent", + "Topaz": "Topaz", "Tripo": "Tripo", "Veo": "Veo", "Vidu": "Vidu", "Wan": "Wan", + "WaveSpeed": "WaveSpeed", "_for_testing": "_pour_test", "advanced": "avancé", "animation": "animation", @@ -1299,6 +1710,7 @@ "controlnet": "controlnet", "create": "créer", "custom_sampling": "échantillonnage_personnalisé", + "dataset": "jeu de données", "debug": "débogage", "deprecated": "déprécié", "edit_models": "edit_models", @@ -1310,8 +1722,10 @@ "image": "image", "inpaint": "inpaint", "instructpix2pix": "instructpix2pix", + "kandinsky5": "kandinsky5", "latent": "latent", "loaders": "chargeurs", + "logic": "logique", "lotus": "lotus", "ltxv": "ltxv", "mask": "masque", @@ -1345,7 +1759,15 @@ "upscaling": "mise_à_l'échelle", "utils": "utilitaires", "video": "vidéo", - "video_models": "modèles_vidéo" + "video_models": "modèles_vidéo", + "zimage": "zimage" + }, + "nodeErrors": { + "content": "Erreur de contenu du nœud", + "header": "Erreur d'en-tête du nœud", + "render": "Erreur de rendu du nœud", + "slots": "Erreur de slots du nœud", + "widgets": "Erreur de widgets du nœud" }, "nodeHelpPage": { "documentationPage": "page de documentation", @@ -1362,6 +1784,7 @@ "notSupported": { "continue": "Continuer", "continueTooltip": "Je suis sûr que mon appareil est pris en charge", + "illustrationAlt": "Illustration d’une fille triste", "learnMore": "En savoir plus", "message": "Seuls les appareils suivants sont pris en charge :", "reportIssue": "Signaler un problème", @@ -1371,12 +1794,136 @@ }, "title": "Votre appareil n'est pas pris en charge" }, + "progressToast": { + "allDownloadsCompleted": "Tous les téléchargements sont terminés", + "downloadingModel": "Téléchargement du modèle...", + "downloadsFailed": "{count} téléchargements échoués | {count} téléchargement échoué | {count} téléchargements échoués", + "failed": "Échec", + "filter": { + "all": "Tous", + "completed": "Terminés", + "failed": "Échoués" + }, + "finished": "Terminé", + "importingModels": "Importation des modèles", + "noImportsInQueue": "Aucun {filter} dans la file d’attente", + "pending": "En attente", + "progressCount": "{completed} sur {total}" + }, + "queue": { + "completedIn": "Terminé en {duration}", + "inQueue": "En file d'attente...", + "initializingAlmostReady": "Initialisation - Presque prêt", + "jobAddedToQueue": "Tâche ajoutée à la file d'attente", + "jobDetails": { + "computeHoursUsed": "Heures de calcul utilisées", + "errorMessage": "Message d'erreur", + "estimatedFinishIn": "Fin estimée dans", + "estimatedStartIn": "Début estimé dans", + "eta": { + "minutes": "~{count} minute | ~{count} minutes", + "minutesRange": "~{lo}-{hi} minutes", + "seconds": "~{count} seconde | ~{count} secondes", + "secondsRange": "~{lo}-{hi} secondes" + }, + "failedAfter": "Échec après", + "generatedOn": "Généré le", + "header": "Détails de la tâche", + "jobId": "ID de la tâche", + "queuePosition": "Position dans la file", + "queuePositionValue": "~{count} tâche devant la vôtre | ~{count} tâches devant la vôtre", + "queuedAt": "Mis en file à", + "report": "Signaler", + "timeElapsed": "Temps écoulé", + "totalGenerationTime": "Temps total de génération", + "workflow": "Workflow" + }, + "jobHistory": "Historique des tâches", + "jobList": { + "sortComputeHoursUsed": "Heures de calcul utilisées (le plus en premier)", + "sortMostRecent": "Le plus récent", + "sortTotalGenerationTime": "Temps total de génération (le plus long en premier)", + "undated": "Sans date" + }, + "jobMenu": { + "addToCurrentWorkflow": "Ajouter au workflow actuel", + "cancelJob": "Annuler la tâche", + "copyErrorMessage": "Copier le message d'erreur", + "copyJobId": "Copier l'ID de la tâche", + "delete": "Supprimer", + "deleteAsset": "Supprimer l'élément", + "download": "Télécharger", + "exportWorkflow": "Exporter le workflow", + "inspectAsset": "Inspecter l'élément", + "openAsWorkflowNewTab": "Ouvrir comme workflow dans un nouvel onglet", + "openWorkflowNewTab": "Ouvrir le workflow dans un nouvel onglet", + "removeJob": "Retirer la tâche", + "reportError": "Signaler une erreur" + }, + "toggleJobHistory": "Afficher/Masquer l'historique des tâches" + }, "releaseToast": { + "description": "Découvrez les dernières améliorations et fonctionnalités de cette mise à jour.", "newVersionAvailable": "Nouvelle version disponible !", "skip": "Ignorer", "update": "Mettre à jour", "whatsNew": "Quoi de neuf ?" }, + "rightSidePanel": { + "addFavorite": "Favori", + "advancedInputs": "ENTRÉES AVANCÉES", + "bypass": "Contourner", + "color": "Couleur du nœud", + "fallbackGroupTitle": "Groupe", + "fallbackNodeTitle": "Nœud", + "favorites": "ENTRÉES FAVORITES", + "favoritesNone": "AUCUNE ENTRÉE FAVORITE", + "favoritesNoneDesc": "Les entrées que vous ajoutez en favori apparaîtront ici", + "favoritesNoneTooltip": "Étoilez les widgets pour y accéder rapidement sans sélectionner de nœud", + "globalSettings": { + "canvas": "CANEVA", + "connectionLinks": "LIENS DE CONNEXION", + "gridSpacing": "Espacement de la grille", + "linkShape": "Forme du lien", + "nodes": "NŒUDS", + "nodes2": "Nœuds 2.0", + "searchPlaceholder": "Rechercher dans les paramètres rapides...", + "showAdvanced": "Afficher les paramètres avancés", + "showAdvancedTooltip": "Ceci est un paramètre important qui, lorsqu’il est activé, affiche tous les paramètres avancés des nœuds", + "showConnectedLinks": "Afficher les liens connectés", + "showInfoBadges": "Afficher les badges d’information", + "showToolbox": "Afficher la boîte à outils à la sélection", + "snapNodesToGrid": "Aligner les nœuds sur la grille", + "title": "Paramètres globaux", + "viewAllSettings": "Voir tous les paramètres" + }, + "groupSettings": "Paramètres du groupe", + "groups": "Groupes", + "hideAdvancedInputsButton": "Masquer les entrées avancées", + "hideInput": "Masquer l’entrée", + "info": "Infos", + "inputs": "ENTRÉES", + "inputsNone": "AUCUNE ENTRÉE", + "inputsNoneTooltip": "Le nœud n’a pas d’entrées", + "locateNode": "Localiser le nœud sur le canevas", + "mute": "Muet", + "noSelection": "Sélectionnez un nœud pour voir ses propriétés et informations.", + "nodeState": "État du nœud", + "nodes": "Nœuds", + "nodesNoneDesc": "AUCUN NŒUD", + "noneSearchDesc": "Aucun élément ne correspond à votre recherche", + "normal": "Normal", + "parameters": "Paramètres", + "pinned": "Épinglé", + "properties": "Propriétés", + "removeFavorite": "Retirer des favoris", + "settings": "Paramètres", + "showAdvancedInputsButton": "Afficher les entrées avancées", + "showInput": "Afficher l’entrée", + "title": "Aucun nœud sélectionné | 1 nœud sélectionné | {count} nœuds sélectionnés", + "togglePanel": "Afficher/masquer le panneau des propriétés", + "workflowOverview": "Aperçu du workflow" + }, "selectionToolbox": { "Bypass Group Nodes": "Contourner les nœuds de groupe", "Set Group Nodes to Always": "Définir les nœuds de groupe sur Toujours", @@ -1389,6 +1936,8 @@ "serverConfig": { "modifiedConfigs": "Vous avez modifié les configurations suivantes du serveur. Redémarrez pour appliquer les modifications.", "restart": "Redémarrer", + "restartRequiredToastDetail": "Redémarrez l’application pour appliquer les modifications de configuration du serveur.", + "restartRequiredToastSummary": "Redémarrage requis", "revertChanges": "Annuler les modifications" }, "serverConfigCategories": { @@ -1457,6 +2006,10 @@ "enable-cors-header": { "name": "Activer l'en-tête CORS: Utilisez \"*\" pour toutes les origines ou spécifiez le domaine" }, + "enable-manager-legacy-ui": { + "name": "Utiliser l’interface Manager héritée", + "tooltip": "Utilise l’ancienne interface ComfyUI-Manager au lieu de la nouvelle interface." + }, "fast": { "name": "Activer certaines optimisations non testées et potentiellement dégradantes pour la qualité." }, @@ -1558,6 +2111,7 @@ "CustomColorPalettes": "Palettes de Couleurs Personnalisées", "DevMode": "Mode Développeur", "EditTokenWeight": "Modifier le Poids du Jeton", + "Execution": "Exécution", "Extension": "Extension", "General": "Général", "Graph": "Graphique", @@ -1572,12 +2126,14 @@ "Mask Editor": "Éditeur de Masque", "Menu": "Menu", "ModelLibrary": "Bibliothèque de Modèles", - "NewEditor": "Nouvel Éditeur", "Node": "Nœud", "Node Search Box": "Boîte de Recherche de Nœud", "Node Widget": "Widget de Nœud", "NodeLibrary": "Bibliothèque de Nœuds", + "Nodes 2_0": "Nodes 2.0", "Notification Preferences": "Préférences de notification", + "Other": "Autre", + "PLY": "PLY", "PlanCredits": "Forfait et crédits", "Pointer": "Pointeur", "Queue": "File d'Attente", @@ -1596,7 +2152,8 @@ "Vue Nodes": "Nœuds Vue", "VueNodes": "Nœuds Vue", "Window": "Fenêtre", - "Workflow": "Flux de Travail" + "Workflow": "Flux de Travail", + "Workspace": "Espace de travail" }, "shape": { "CARD": "Carte", @@ -1622,11 +2179,14 @@ "viewControls": "Contrôles d'affichage" }, "sideToolbar": { + "activeJobStatus": "Tâche active : {status}", "assets": "Ressources", "backToAssets": "Retour à toutes les ressources", "browseTemplates": "Parcourir les modèles d'exemple", "downloads": "Téléchargements", + "generatedAssetsHeader": "Ressources générées", "helpCenter": "Centre d'aide", + "importedAssetsHeader": "Ressources importées", "labels": { "assets": "Ressources", "console": "Console", @@ -1669,6 +2229,47 @@ }, "openWorkflow": "Ouvrir le flux de travail dans le système de fichiers local", "queue": "File d'attente", + "queueProgressOverlay": { + "activeJobs": "{count} travail actif | {count} travaux actifs", + "activeJobsShort": "{count} actif(s) | {count} actif(s)", + "activeJobsSuffix": "travaux actifs", + "cancelJobTooltip": "Annuler le travail", + "clearHistory": "Effacer l’historique de la file d’attente", + "clearHistoryDialogAssetsNote": "Les ressources générées par ces travaux ne seront pas supprimées et resteront accessibles depuis le panneau des ressources.", + "clearHistoryDialogDescription": "Tous les travaux terminés ou échoués ci-dessous seront supprimés de ce panneau de file d’attente.", + "clearHistoryDialogTitle": "Effacer l’historique de la file d’attente ?", + "clearQueueTooltip": "Vider la file d’attente", + "clearQueued": "Vider la file d’attente", + "colonPercent": ": {percent}", + "currentNode": "Nœud actuel :", + "expandCollapsedQueue": "Développer la file d’attente", + "filterAllWorkflows": "Tous les workflows", + "filterBy": "Filtrer par", + "filterCurrentWorkflow": "Workflow actuel", + "filterJobs": "Filtrer les travaux", + "interruptAll": "Interrompre tous les travaux en cours", + "jobQueue": "File d’attente des travaux", + "jobsCompleted": "{count} travail terminé | {count} travaux terminés", + "jobsFailed": "{count} travail échoué | {count} travaux échoués", + "moreOptions": "Plus d’options", + "noActiveJobs": "Aucun travail actif", + "preview": "Aperçu", + "queuedSuffix": "en file d’attente", + "running": "en cours", + "showAssets": "Afficher les ressources", + "showAssetsPanel": "Afficher le panneau des ressources", + "sortBy": "Trier par", + "sortJobs": "Trier les travaux", + "stubClipTextEncode": "CLIP Text Encode :", + "title": "Progression de la file d’attente", + "total": "Total : {percent}", + "viewAllJobs": "Voir tous les travaux", + "viewGrid": "Vue en grille", + "viewJobHistory": "Voir l’historique des travaux", + "viewList": "Vue en liste" + }, + "searchAssets": "Rechercher des ressources", + "sidebar": "Barre latérale", "templates": "Modèles", "themeToggle": "Basculer le thème", "workflowTab": { @@ -1711,24 +2312,58 @@ "subscription": { "addApiCredits": "Ajouter des crédits API", "addCredits": "Ajouter des crédits", + "addCreditsLabel": "Ajoutez des crédits à tout moment", "benefits": { "benefit1": "Crédits mensuels pour les Nœuds Partenaires — rechargez si nécessaire", "benefit2": "Jusqu'à 30 min d'exécution par tâche" }, "beta": "BÊTA", + "billedMonthly": "Facturé mensuellement", + "billedYearly": "{total} facturé annuellement", + "billingComingSoon": { + "message": "La facturation d'équipe arrive bientôt. Vous pourrez souscrire à un abonnement pour votre espace de travail avec un tarif par utilisateur. Restez à l'écoute pour les mises à jour.", + "title": "Bientôt disponible" + }, + "cancelSubscription": "Annuler l’abonnement", + "changeTo": "Changer pour {plan}", "comfyCloud": "Comfy Cloud", + "comfyCloudLogo": "Logo Comfy Cloud", + "contactOwnerToSubscribe": "Contactez le propriétaire de l’espace de travail pour vous abonner", + "contactUs": "Contactez-nous", + "creditsRemainingThisMonth": "Crédits restants ce mois-ci", + "creditsRemainingThisYear": "Crédits restants cette année", + "creditsYouveAdded": "Crédits ajoutés", + "currentPlan": "Forfait actuel", + "customLoRAsLabel": "Importer vos propres LoRAs", + "description": "Choisissez le forfait qui vous convient", "expiresDate": "Expire le {date}", + "gpuLabel": "RTX 6000 Pro (96GB VRAM)", + "haveQuestions": "Des questions ou besoin d'une offre entreprise ?", "invoiceHistory": "Historique des factures", "learnMore": "En savoir plus", + "managePayment": "Gérer le paiement", + "managePlan": "Gérer le forfait", "manageSubscription": "Gérer l'abonnement", + "maxDuration": { + "creator": "30 min", + "founder": "30 min", + "pro": "1 h", + "standard": "30 min" + }, + "maxDurationLabel": "Durée maximale de chaque exécution de workflow", "messageSupport": "Contacter le support", + "monthly": "Mensuel", "monthlyBonusDescription": "Bonus de crédits mensuel", + "monthlyCreditsInfo": "Ces crédits se renouvellent chaque mois et ne sont pas reportés", + "monthlyCreditsLabel": "Crédits mensuels", "monthlyCreditsRollover": "Ces crédits seront reportés au mois suivant", + "mostPopular": "Le plus populaire", "nextBillingCycle": "prochain cycle de facturation", "partnerNodesBalance": "Solde de crédits \"Nœuds Partenaires\"", "partnerNodesCredits": "Crédits Nœuds Partenaires", "partnerNodesDescription": "Pour exécuter des modèles commerciaux/propriétaires", "perMonth": "USD / mois", + "plansAndPricing": "Forfaits & tarifs", "prepaidCreditsInfo": "Crédits achetés séparément qui n'expirent pas", "prepaidDescription": "Crédits prépayés", "renewsDate": "Renouvellement le {date}", @@ -1738,14 +2373,46 @@ "waitingForSubscription": "Complétez votre abonnement dans le nouvel onglet. Nous détecterons automatiquement quand vous aurez terminé !" }, "subscribeNow": "S'abonner maintenant", + "subscribeTo": "S'abonner à {plan}", "subscribeToComfyCloud": "S'abonner à Comfy Cloud", "subscribeToRun": "S'abonner", "subscribeToRunFull": "S'abonner pour exécuter", + "subscriptionRequiredMessage": "Un abonnement est requis pour que les membres puissent exécuter des workflows sur le Cloud", + "tierNameYearly": "{name} Annuel", + "tiers": { + "creator": { + "name": "Créateur" + }, + "founder": { + "name": "Édition Fondateur" + }, + "pro": { + "name": "Pro" + }, + "standard": { + "name": "Standard" + } + }, "title": "Abonnement", "titleUnsubscribed": "Abonnez-vous à Comfy Cloud", "totalCredits": "Total des crédits", + "upgrade": "AMÉLIORER", + "upgradePlan": "Améliorer le forfait", + "upgradeTo": "Passer à {plan}", + "usdPerMonth": "USD / mois", + "videoEstimateExplanation": "Ces estimations sont basées sur le modèle Wan 2.2 Image-to-Video avec les paramètres par défaut (5 secondes, 640x640, 16fps, échantillonnage en 4 étapes).", + "videoEstimateHelp": "Plus de détails sur ce modèle", + "videoEstimateLabel": "Nombre approx. de vidéos de 5s générées avec le modèle Wan 2.2 Image-to-Video", + "videoEstimateTryTemplate": "Essayer ce modèle", + "videoTemplateBasedCredits": "Vidéos générées avec Wan 2.2 Image to Video", + "viewEnterprise": "Voir l'offre entreprise", "viewMoreDetails": "Voir plus de détails", + "viewMoreDetailsPlans": "Voir plus de détails sur les forfaits et tarifs", "viewUsageHistory": "Voir l'historique d'utilisation", + "workspaceNotSubscribed": "Cet espace de travail n’a pas d’abonnement", + "yearly": "Annuel", + "yearlyCreditsLabel": "Crédits annuels totaux", + "yearlyDiscount": "20% DE RÉDUCTION", "yourPlanIncludes": "Votre forfait comprend :" }, "tabMenu": { @@ -1757,8 +2424,14 @@ "duplicateTab": "Dupliquer l'onglet", "removeFromBookmarks": "Retirer des Favoris" }, + "templateWidgets": { + "sort": { + "searchPlaceholder": "Rechercher..." + } + }, "templateWorkflows": { "activeFilters": "Filtres :", + "allTemplates": "Tous les modèles", "categories": "Catégories", "category": { "3D": "3D", @@ -1785,6 +2458,7 @@ "error": { "templateNotFound": "Modèle \"{templateName}\" introuvable" }, + "licenseFilter": "Licence", "loading": "Chargement des modèles...", "loadingMore": "Chargement de plus de modèles...", "modelFilter": "Filtre de modèle", @@ -1801,12 +2475,14 @@ "default": "Par défaut", "modelSizeLowToHigh": "Taille du modèle (faible à élevée)", "newest": "Plus récent", + "popular": "Populaire", "recommended": "Recommandé", "searchPlaceholder": "Rechercher...", "vramLowToHigh": "Utilisation VRAM (faible à élevée)" }, "sorting": "Trier par", "title": "Commencez avec un modèle", + "useCaseFilter": "Tâches", "useCasesSelected": "{count} Cas d'usage" }, "toastMessages": { @@ -1835,9 +2511,22 @@ "failedToLoadModel": "Échec du chargement du modèle 3D", "failedToPurchaseCredits": "Échec de l'achat de crédits : {error}", "failedToQueue": "Échec de la mise en file d'attente", + "failedToToggleCamera": "Échec de l’activation/désactivation de la caméra", + "failedToToggleGrid": "Échec de l’activation/désactivation de la grille", + "failedToUpdateBackgroundColor": "Échec de la mise à jour de la couleur d’arrière-plan", + "failedToUpdateBackgroundImage": "Échec de la mise à jour de l’image d’arrière-plan", + "failedToUpdateBackgroundRenderMode": "Échec de la mise à jour du mode de rendu d’arrière-plan vers {mode}", + "failedToUpdateEdgeThreshold": "Échec de la mise à jour du seuil de contour", + "failedToUpdateFOV": "Échec de la mise à jour du champ de vision", + "failedToUpdateLightIntensity": "Échec de la mise à jour de l’intensité lumineuse", + "failedToUpdateMaterialMode": "Échec de la mise à jour du mode matériau", + "failedToUpdateUpDirection": "Échec de la mise à jour de la direction vers le haut", + "failedToUploadBackgroundImage": "Échec du téléchargement de l’image d’arrière-plan", "fileLoadError": "Impossible de trouver le flux de travail dans {fileName}", + "fileTooLarge": "Fichier trop volumineux ({size} Mo). La taille maximale prise en charge est de {maxSize} Mo", "fileUploadFailed": "Échec du téléchargement du fichier", "interrupted": "L'exécution a été interrompue", + "legacyMaskEditorDeprecated": "L’éditeur de masque hérité est obsolète et sera bientôt supprimé.", "migrateToLitegraphReroute": "Les nœuds de reroute seront supprimés dans les futures versions. Cliquez pour migrer vers le reroute natif de litegraph.", "modelLoadedSuccessfully": "Modèle 3D chargé avec succès", "no3dScene": "Aucune scène 3D pour appliquer la texture", @@ -1864,12 +2553,14 @@ "selectUser": "Sélectionnez un utilisateur" }, "userSettings": { + "accountSettings": "Paramètres du compte", "email": "E-mail", "name": "Nom", "notSet": "Non défini", "provider": "Méthode de connexion", "title": "Paramètres utilisateur", - "updatePassword": "Mettre à jour le mot de passe" + "updatePassword": "Mettre à jour le mot de passe", + "workspaceSettings": "Paramètres de l’espace de travail" }, "validation": { "descriptionRequired": "La description est requise", @@ -1898,22 +2589,32 @@ "updateFrontend": "Mettre à jour le frontend" }, "vueNodesBanner": { - "message": "Les nœuds ont une toute nouvelle apparence", + "desc": "– Flux de travail plus flexibles, nouveaux widgets puissants, conçu pour l’extensibilité", + "title": "Présentation de Nodes 2.0", "tryItOut": "Essayer" }, "vueNodesMigration": { "button": "Ouvrir les paramètres", "message": "Préférez-vous le design classique des nœuds ?" }, + "vueNodesMigrationMainMenu": { + "message": "Revenez à Nodes 2.0 à tout moment depuis le menu principal." + }, "welcome": { "getStarted": "Commencer", "title": "Bienvenue sur ComfyUI" }, "whatsNewPopup": { + "later": "Plus tard", "learnMore": "En savoir plus", "noReleaseNotes": "Aucune note de version disponible." }, + "widgetFileUpload": { + "browseFiles": "Parcourir les fichiers", + "dropPrompt": "Déposez votre fichier ou" + }, "widgets": { + "node2only": "Node 2.0 uniquement", "selectModel": "Sélectionner un modèle", "uploadSelect": { "placeholder": "Sélectionner...", @@ -1922,6 +2623,26 @@ "placeholderModel": "Sélectionner un modèle...", "placeholderUnknown": "Sélectionner un média...", "placeholderVideo": "Sélectionner une vidéo..." + }, + "valueControl": { + "decrement": "Décrémenter la valeur", + "decrementDesc": "Soustrait 1 de la valeur ou sélectionne l'option précédente", + "editSettings": "Modifier les paramètres de contrôle", + "fixed": "Valeur fixe", + "fixedDesc": "Laisse la valeur inchangée", + "header": { + "after": "APRÈS", + "before": "AVANT", + "postfix": "l'exécution du workflow :", + "prefix": "Mettre à jour automatiquement la valeur" + }, + "increment": "Incrémenter la valeur", + "incrementDesc": "Ajoute 1 à la valeur ou sélectionne l'option suivante", + "linkToGlobal": "Lier à", + "linkToGlobalDesc": "Valeur unique liée au paramètre de contrôle de la Valeur globale", + "linkToGlobalSeed": "Valeur globale", + "randomize": "Valeur aléatoire", + "randomizeDesc": "Mélange la valeur aléatoirement après chaque génération" } }, "workflowService": { @@ -1929,6 +2650,146 @@ "exportWorkflow": "Exporter le flux de travail", "saveWorkflow": "Enregistrer le flux de travail" }, + "workspace": { + "addedToWorkspace": "Vous avez été ajouté à {workspaceName}", + "inviteAccepted": "Invitation acceptée", + "inviteFailed": "Échec de l'acceptation de l'invitation", + "unsavedChanges": { + "message": "Vous avez des modifications non enregistrées. Voulez-vous les abandonner et changer d’espace de travail ?", + "title": "Modifications non enregistrées" + } + }, + "workspaceAuth": { + "errors": { + "accessDenied": "Vous n’avez pas accès à cet espace de travail", + "invalidFirebaseToken": "Échec de l’authentification. Veuillez vous reconnecter.", + "notAuthenticated": "Vous devez être connecté pour accéder aux espaces de travail", + "tokenExchangeFailed": "Échec de l’authentification avec l’espace de travail : {error}", + "workspaceNotFound": "Espace de travail introuvable" + } + }, + "workspacePanel": { + "createWorkspaceDialog": { + "create": "Créer", + "message": "Les espaces de travail permettent aux membres de partager un même pool de crédits. Vous deviendrez le propriétaire après la création.", + "nameLabel": "Nom de l’espace de travail*", + "namePlaceholder": "Saisissez le nom de l’espace de travail", + "title": "Créer un nouvel espace de travail" + }, + "dashboard": { + "placeholder": "Paramètres de l'espace de travail du tableau de bord" + }, + "deleteDialog": { + "message": "Tout crédit inutilisé ou ressource non enregistrée sera perdu. Cette action est irréversible.", + "messageWithName": "Supprimer « {name} » ? Tout crédit inutilisé ou ressource non enregistrée sera perdu. Cette action est irréversible.", + "title": "Supprimer cet espace de travail ?" + }, + "editWorkspaceDialog": { + "nameLabel": "Nom de l’espace de travail", + "save": "Enregistrer", + "title": "Modifier les détails de l’espace de travail" + }, + "invite": "Inviter", + "inviteLimitReached": "Vous avez atteint le maximum de 50 membres", + "inviteMember": "Inviter un membre", + "inviteMemberDialog": { + "createLink": "Créer le lien", + "linkCopied": "Copié", + "linkCopyFailed": "Échec de la copie du lien", + "linkStep": { + "copyLink": "Copier le lien", + "done": "Terminé", + "message": "Assurez-vous que son compte utilise cet email.", + "title": "Envoyez ce lien à la personne" + }, + "message": "Créez un lien d'invitation partageable à envoyer à quelqu'un", + "placeholder": "Entrez l'email de la personne", + "title": "Inviter une personne dans cet espace de travail" + }, + "leaveDialog": { + "leave": "Quitter", + "message": "Vous ne pourrez pas le rejoindre à nouveau sans contacter le propriétaire de l’espace de travail.", + "title": "Quitter cet espace de travail ?" + }, + "members": { + "actions": { + "copyLink": "Copier le lien d'invitation", + "removeMember": "Retirer le membre", + "revokeInvite": "Révoquer l'invitation" + }, + "columns": { + "expiryDate": "Date d'expiration", + "inviteDate": "Date d'invitation", + "joinDate": "Date d'adhésion" + }, + "createNewWorkspace": "créez-en un nouveau.", + "membersCount": "{count}/50 membres", + "noInvites": "Aucune invitation en attente", + "noMembers": "Aucun membre", + "pendingInvitesCount": "{count} invitation en attente | {count} invitations en attente", + "personalWorkspaceMessage": "Vous ne pouvez pas inviter d'autres membres dans votre espace de travail personnel pour le moment. Pour ajouter des membres à un espace de travail,", + "tabs": { + "active": "Actif", + "pendingCount": "En attente ({count})" + } + }, + "menu": { + "deleteWorkspace": "Supprimer l’espace de travail", + "deleteWorkspaceDisabledTooltip": "Annulez d’abord l’abonnement actif de votre espace de travail", + "editWorkspace": "Modifier les détails de l’espace de travail", + "leaveWorkspace": "Quitter l’espace de travail" + }, + "removeMemberDialog": { + "error": "Échec du retrait du membre", + "message": "Ce membre sera retiré de votre espace de travail. Les crédits qu'il a utilisés ne seront pas remboursés.", + "remove": "Retirer le membre", + "success": "Membre retiré", + "title": "Retirer ce membre ?" + }, + "revokeInviteDialog": { + "message": "Cette personne ne pourra plus rejoindre votre espace de travail. Son lien d'invitation sera invalidé.", + "revoke": "Annuler l'invitation", + "title": "Annuler l'invitation de cette personne ?" + }, + "tabs": { + "dashboard": "Tableau de bord", + "membersCount": "Membres ({count})", + "planCredits": "Forfait & Crédits" + }, + "toast": { + "failedToCreateWorkspace": "Échec de la création de l’espace de travail", + "failedToDeleteWorkspace": "Échec de la suppression de l’espace de travail", + "failedToFetchWorkspaces": "Échec du chargement des espaces de travail", + "failedToLeaveWorkspace": "Échec de la sortie de l’espace de travail", + "failedToUpdateWorkspace": "Échec de la mise à jour de l’espace de travail", + "workspaceCreated": { + "message": "Abonnez-vous à un plan, invitez des coéquipiers et commencez à collaborer.", + "subscribe": "S'abonner", + "title": "Espace de travail créé" + }, + "workspaceDeleted": { + "message": "L'espace de travail a été définitivement supprimé.", + "title": "Espace de travail supprimé" + }, + "workspaceLeft": { + "message": "Vous avez quitté l'espace de travail.", + "title": "Espace de travail quitté" + }, + "workspaceUpdated": { + "message": "Les détails de l’espace de travail ont été enregistrés.", + "title": "Espace de travail mis à jour" + } + } + }, + "workspaceSwitcher": { + "createWorkspace": "Créer un nouvel espace de travail", + "maxWorkspacesReached": "Vous ne pouvez posséder que 10 espaces de travail. Supprimez-en un pour en créer un nouveau.", + "personal": "Personnel", + "roleMember": "Membre", + "roleOwner": "Propriétaire", + "subscribe": "S’abonner", + "switchWorkspace": "Changer d’espace de travail" + }, "zoomControls": { "hideMinimap": "Masquer la mini-carte", "label": "Contrôles de zoom", diff --git a/src/locales/fr/nodeDefs.json b/src/locales/fr/nodeDefs.json index c603afd97..1774dee80 100644 --- a/src/locales/fr/nodeDefs.json +++ b/src/locales/fr/nodeDefs.json @@ -39,6 +39,87 @@ "sigmas": { "name": "sigmas" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AddTextPrefix": { + "display_name": "Ajouter un préfixe au texte", + "inputs": { + "prefix": { + "name": "préfixe", + "tooltip": "Préfixe à ajouter." + }, + "texts": { + "name": "textes", + "tooltip": "Texte à traiter." + } + }, + "outputs": { + "0": { + "name": "textes", + "tooltip": "Textes traités" + } + } + }, + "AddTextSuffix": { + "display_name": "Ajouter un suffixe au texte", + "inputs": { + "suffix": { + "name": "suffixe", + "tooltip": "Suffixe à ajouter." + }, + "texts": { + "name": "textes", + "tooltip": "Texte à traiter." + } + }, + "outputs": { + "0": { + "name": "textes", + "tooltip": "Textes traités" + } + } + }, + "AdjustBrightness": { + "display_name": "Ajuster la luminosité", + "inputs": { + "factor": { + "name": "facteur", + "tooltip": "Facteur de luminosité. 1.0 = aucun changement, <1.0 = plus sombre, >1.0 = plus clair." + }, + "images": { + "name": "images", + "tooltip": "Image à traiter." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Images traitées" + } + } + }, + "AdjustContrast": { + "display_name": "Ajuster le contraste", + "inputs": { + "factor": { + "name": "facteur", + "tooltip": "Facteur de contraste. 1.0 = aucun changement, <1.0 = moins de contraste, >1.0 = plus de contraste." + }, + "images": { + "name": "images", + "tooltip": "Image à traiter." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Images traitées" + } } }, "AlignYourStepsScheduler": { @@ -70,6 +151,11 @@ "name": "volume", "tooltip": "Ajustement du volume en décibels (dB). 0 = pas de changement, +6 = double, -6 = moitié, etc." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "AudioConcat": { @@ -86,6 +172,11 @@ "name": "direction", "tooltip": "Indique s'il faut ajouter audio2 après ou avant audio1." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "AudioEncoderEncode": { @@ -131,6 +222,11 @@ "name": "méthode_fusion", "tooltip": "La méthode utilisée pour combiner les formes d'onde audio." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BasicGuider": { @@ -142,6 +238,11 @@ "model": { "name": "modèle" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BasicScheduler": { @@ -159,6 +260,50 @@ "steps": { "name": "étapes" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchImagesNode": { + "display_name": "Images par lot", + "inputs": { + "images": { + "name": "images" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchLatentsNode": { + "display_name": "Latents par lot", + "inputs": { + "latents": { + "name": "latents" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchMasksNode": { + "display_name": "Masques par lot", + "inputs": { + "masks": { + "name": "masques" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BetaSamplingScheduler": { @@ -176,6 +321,73 @@ "steps": { "name": "étapes" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BriaImageEditNode": { + "description": "Modifiez des images en utilisant le dernier modèle Bria", + "display_name": "Bria Image Edit", + "inputs": { + "control_after_generate": { + "name": "contrôle après génération" + }, + "guidance_scale": { + "name": "échelle de guidage", + "tooltip": "Une valeur plus élevée fait suivre l'image à l'invite de façon plus précise." + }, + "image": { + "name": "image" + }, + "mask": { + "name": "masque", + "tooltip": "Si omis, la modification s'applique à l'image entière." + }, + "model": { + "name": "modèle" + }, + "moderation": { + "name": "modération", + "tooltip": "Paramètres de modération" + }, + "moderation_prompt_content_moderation": { + "name": "modération_du_contenu_de_l'invite" + }, + "moderation_visual_input_moderation": { + "name": "modération_de_l'entrée_visuelle" + }, + "moderation_visual_output_moderation": { + "name": "modération_de_la_sortie_visuelle" + }, + "negative_prompt": { + "name": "invite négative" + }, + "prompt": { + "name": "invite", + "tooltip": "Instruction pour modifier l'image" + }, + "seed": { + "name": "graine" + }, + "steps": { + "name": "étapes" + }, + "structured_prompt": { + "name": "invite structurée", + "tooltip": "Une chaîne contenant l'invite d'édition structurée au format JSON. Utilisez ceci à la place de l'invite habituelle pour un contrôle précis et programmatique." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "invite structurée", + "tooltip": null + } } }, "ByteDanceFirstLastFrameNode": { @@ -201,13 +413,16 @@ "name": "première_image", "tooltip": "Première image à utiliser pour la vidéo." }, + "generate_audio": { + "name": "générer_audio", + "tooltip": "Ce paramètre est ignoré pour tout modèle sauf seedance-1-5-pro." + }, "last_frame": { "name": "dernière_image", "tooltip": "Dernière image à utiliser pour la vidéo." }, "model": { - "name": "modèle", - "tooltip": "Nom du modèle" + "name": "modèle" }, "prompt": { "name": "invite", @@ -248,8 +463,7 @@ "tooltip": "L'image de base à modifier" }, "model": { - "name": "model", - "tooltip": "Nom du modèle" + "name": "model" }, "prompt": { "name": "prompt", @@ -286,8 +500,7 @@ "tooltip": "Hauteur personnalisée pour l'image. La valeur fonctionne uniquement si `size_preset` est défini sur `Personnalisé`" }, "model": { - "name": "model", - "tooltip": "Nom du modèle" + "name": "model" }, "prompt": { "name": "prompt", @@ -336,8 +549,7 @@ "tooltip": "Une à quatre images." }, "model": { - "name": "modèle", - "tooltip": "Nom du modèle" + "name": "modèle" }, "prompt": { "name": "prompt", @@ -381,13 +593,16 @@ "name": "durée", "tooltip": "La durée de la vidéo en sortie en secondes." }, + "generate_audio": { + "name": "générer_audio", + "tooltip": "Ce paramètre est ignoré pour tout modèle sauf seedance-1-5-pro." + }, "image": { "name": "image", "tooltip": "Première image à utiliser pour la vidéo." }, "model": { - "name": "modèle", - "tooltip": "Nom du modèle" + "name": "modèle" }, "prompt": { "name": "prompt", @@ -489,9 +704,12 @@ "name": "duration", "tooltip": "La durée de la vidéo de sortie en secondes." }, + "generate_audio": { + "name": "générer_audio", + "tooltip": "Ce paramètre est ignoré pour tout modèle sauf seedance-1-5-pro." + }, "model": { - "name": "model", - "tooltip": "Nom du modèle" + "name": "model" }, "prompt": { "name": "prompt", @@ -531,6 +749,11 @@ "positive": { "name": "positive" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "CFGNorm": { @@ -769,6 +992,25 @@ } } }, + "CLIPTextEncodeKandinsky5": { + "display_name": "CLIPTextEncodeKandinsky5", + "inputs": { + "clip": { + "name": "clip" + }, + "clip_l": { + "name": "clip_l" + }, + "qwen25_7b": { + "name": "qwen25_7b" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "CLIPTextEncodeLumina2": { "description": "Encode une invite système et une invite utilisateur à l'aide d'un modèle CLIP en une intégration qui peut être utilisée pour guider le modèle de diffusion vers la génération d'images spécifiques.", "display_name": "CLIP Text Encode pour Lumina2", @@ -959,6 +1201,29 @@ } } }, + "CenterCropImages": { + "display_name": "Rogner les images au centre", + "inputs": { + "height": { + "name": "hauteur", + "tooltip": "Hauteur du rognage." + }, + "images": { + "name": "images", + "tooltip": "Image à traiter." + }, + "width": { + "name": "largeur", + "tooltip": "Largeur du rognage." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Images traitées" + } + } + }, "CheckpointLoader": { "display_name": "Charger Point de Contrôle Avec Config (OBSOLÈTE)", "inputs": { @@ -1095,6 +1360,26 @@ } } }, + "ComfySwitchNode": { + "display_name": "Commutateur", + "inputs": { + "on_false": { + "name": "faux" + }, + "on_true": { + "name": "vrai" + }, + "switch": { + "name": "interrupteur" + } + }, + "outputs": { + "0": { + "name": "sortie", + "tooltip": null + } + } + }, "ConditioningAverage": { "display_name": "Moyenne de Conditionnement", "inputs": { @@ -1327,14 +1612,14 @@ "name": "secondes_total" } }, - "outputs": { - "0": { - "name": "positive" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "négative" + { + "tooltip": null } - } + ] }, "ConditioningTimestepsRange": { "display_name": "Plage de pas de temps", @@ -1391,6 +1676,10 @@ "name": "dimension", "tooltip": "La dimension à laquelle appliquer les fenêtres de contexte." }, + "freenoise": { + "name": "bruit_libre", + "tooltip": "Indique s'il faut appliquer le mélange de bruit FreeNoise, améliore la fusion des fenêtres." + }, "fuse_method": { "name": "méthode_fusion", "tooltip": "La méthode à utiliser pour fusionner les fenêtres de contexte." @@ -1791,8 +2080,32 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, + "CustomCombo": { + "display_name": "Combo personnalisé", + "inputs": { + "choice": { + "name": "choix" + }, + "index": { + }, + "option1": { + } + }, + "outputs": [ + null, + { + "name": "INDEX", + "tooltip": null + } + ] + }, "DiffControlNetLoader": { "display_name": "Charger le modèle ControlNet (diff)", "inputs": { @@ -1829,7 +2142,12 @@ } }, "DisableNoise": { - "display_name": "DésactiverBruit" + "display_name": "DésactiverBruit", + "outputs": { + "0": { + "tooltip": null + } + } }, "DualCFGGuider": { "display_name": "GuideurDualCFG", @@ -1855,6 +2173,11 @@ "style": { "name": "style" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "DualCLIPLoader": { @@ -1938,6 +2261,11 @@ "name": "fréquence_d'échantillonnage", "tooltip": "Fréquence d'échantillonnage du clip audio vide." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyChromaRadianceLatentImage": { @@ -1981,6 +2309,25 @@ } } }, + "EmptyFlux2LatentImage": { + "display_name": "Flux vide vers latent", + "inputs": { + "batch_size": { + "name": "taille_lot" + }, + "height": { + "name": "hauteur" + }, + "width": { + "name": "largeur" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptyHunyuanImageLatent": { "display_name": "EmptyHunyuanImageLatent", "inputs": { @@ -2022,6 +2369,28 @@ } } }, + "EmptyHunyuanVideo15Latent": { + "display_name": "HunyuanVideo 1.5 latent vide", + "inputs": { + "batch_size": { + "name": "taille_lot" + }, + "height": { + "name": "hauteur" + }, + "length": { + "name": "longueur" + }, + "width": { + "name": "largeur" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptyImage": { "display_name": "ImageVide", "inputs": { @@ -2071,6 +2440,11 @@ "seconds": { "name": "secondes" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyLatentHunyuan3Dv2": { @@ -2083,6 +2457,11 @@ "resolution": { "name": "résolution" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyLatentImage": { @@ -2130,6 +2509,28 @@ } } }, + "EmptyQwenImageLayeredLatentImage": { + "display_name": "Qwen Image Layered latent vide", + "inputs": { + "batch_size": { + "name": "taille_lot" + }, + "height": { + "name": "hauteur" + }, + "layers": { + "name": "couches" + }, + "width": { + "name": "largeur" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptySD3LatentImage": { "display_name": "EmptySD3LatentImage", "inputs": { @@ -2177,6 +2578,11 @@ "steps": { "name": "étapes" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ExtendIntermediateSigmas": { @@ -2197,6 +2603,11 @@ "steps": { "name": "étapes" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FeatherMask": { @@ -2217,6 +2628,11 @@ "top": { "name": "haut" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FlipSigmas": { @@ -2225,6 +2641,102 @@ "sigmas": { "name": "sigmas" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2MaxImageNode": { + "description": "Génère des images de manière synchrone à partir d'une invite et d'une résolution.", + "display_name": "Flux.2 [max] Image", + "inputs": { + "control_after_generate": { + "name": "contrôle après génération" + }, + "height": { + "name": "hauteur" + }, + "images": { + "name": "images", + "tooltip": "Jusqu’à 9 images à utiliser comme références." + }, + "prompt": { + "name": "prompt", + "tooltip": "Invite pour la génération ou l’édition d’image" + }, + "prompt_upsampling": { + "name": "suréchantillonnage de l'invite", + "tooltip": "Indique s’il faut effectuer un suréchantillonnage sur l’invite. Si activé, modifie automatiquement l’invite pour une génération plus créative." + }, + "seed": { + "name": "graine", + "tooltip": "La graine aléatoire utilisée pour créer le bruit." + }, + "width": { + "name": "largeur" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2ProImageNode": { + "description": "Génère des images de manière synchrone à partir d'une invite et d'une résolution.", + "display_name": "Flux.2 [pro] Image", + "inputs": { + "control_after_generate": { + "name": "contrôle après génération" + }, + "height": { + "name": "hauteur" + }, + "images": { + "name": "images", + "tooltip": "Jusqu’à 9 images à utiliser comme références." + }, + "prompt": { + "name": "prompt", + "tooltip": "Invite pour la génération ou l’édition d’image" + }, + "prompt_upsampling": { + "name": "suréchantillonnage de l'invite", + "tooltip": "Indique s’il faut effectuer un suréchantillonnage sur l’invite. Si activé, modifie automatiquement l’invite pour une génération plus créative." + }, + "seed": { + "name": "graine", + "tooltip": "La graine aléatoire utilisée pour créer le bruit." + }, + "width": { + "name": "largeur" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2Scheduler": { + "display_name": "Flux2Scheduler", + "inputs": { + "height": { + "name": "hauteur" + }, + "steps": { + "name": "étapes" + }, + "width": { + "name": "largeur" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FluxDisableGuidance": { @@ -2547,6 +3059,11 @@ "s2": { "name": "s2" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FreeU_V2": { @@ -2567,6 +3084,11 @@ "s2": { "name": "s2" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "GITSScheduler": { @@ -2625,6 +3147,58 @@ } } }, + "GeminiImage2Node": { + "description": "Générez ou modifiez des images de manière synchrone via l'API Google Vertex.", + "display_name": "Nano Banana Pro (Google Gemini Image)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "Si défini sur 'auto', correspond au format d'image de votre entrée ; si aucune image n'est fournie, un carré 16:9 est généralement généré." + }, + "control_after_generate": { + "name": "control after generate" + }, + "files": { + "name": "files", + "tooltip": "Fichier(s) optionnel(s) à utiliser comme contexte pour le modèle. Accepte les entrées du nœud Gemini Generate Content Input Files." + }, + "images": { + "name": "images", + "tooltip": "Image(s) de référence optionnelle(s). Pour inclure plusieurs images, utilisez le nœud Batch Images (jusqu'à 14)." + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "Invite textuelle décrivant l'image à générer ou les modifications à appliquer. Incluez toutes contraintes, styles ou détails que le modèle doit suivre." + }, + "resolution": { + "name": "resolution", + "tooltip": "Résolution de sortie cible. Pour 2K/4K, l'upscaler natif Gemini est utilisé." + }, + "response_modalities": { + "name": "response_modalities", + "tooltip": "Choisissez 'IMAGE' pour une sortie image uniquement, ou 'IMAGE+TEXT' pour retourner à la fois l'image générée et une réponse textuelle." + }, + "seed": { + "name": "seed", + "tooltip": "Lorsque la graine est fixée à une valeur spécifique, le modèle s'efforce de fournir la même réponse pour des requêtes répétées. Un résultat déterministe n'est pas garanti. De plus, changer le modèle ou les paramètres, comme la température, peut entraîner des variations de la réponse même avec la même valeur de graine. Par défaut, une graine aléatoire est utilisée." + }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "Instructions fondamentales qui dictent le comportement de l'IA." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "tooltip": null + } + } + }, "GeminiImageNode": { "description": "Modifier les images de manière synchrone via l'API Google.", "display_name": "Image Google Gemini", @@ -2652,9 +3226,17 @@ "name": "prompt", "tooltip": "Prompt texte pour la génération" }, + "response_modalities": { + "name": "response_modalities", + "tooltip": "Choisissez 'IMAGE' pour une sortie image uniquement, ou 'IMAGE+TEXT' pour retourner à la fois l'image générée et une réponse textuelle." + }, "seed": { "name": "graine", "tooltip": "Lorsque la graine est fixée à une valeur spécifique, le modèle fait de son mieux pour fournir la même réponse pour des requêtes répétées. La sortie déterministe n'est pas garantie. De plus, changer le modèle ou les paramètres, comme la température, peut entraîner des variations dans la réponse même en utilisant la même valeur de graine. Par défaut, une valeur de graine aléatoire est utilisée." + }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "Instructions fondamentales qui dictent le comportement de l'IA." } }, "outputs": { @@ -2716,6 +3298,10 @@ "name": "graine", "tooltip": "Lorsque la graine est fixée à une valeur spécifique, le modèle fait de son mieux pour fournir la même réponse pour des requêtes répétées. La sortie déterministe n'est pas garantie. De plus, changer le modèle ou les paramètres, comme la température, peut entraîner des variations dans la réponse même en utilisant la même valeur de graine. Par défaut, une valeur de graine aléatoire est utilisée." }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "Instructions fondamentales qui dictent le comportement de l'IA." + }, "video": { "name": "vidéo", "tooltip": "Vidéo optionnelle à utiliser comme contexte pour le modèle." @@ -2727,6 +3313,72 @@ } } }, + "GenerateTracks": { + "display_name": "GenerateTracks", + "inputs": { + "bezier": { + "name": "bezier", + "tooltip": "Activer le chemin en courbe de Bézier en utilisant le point central comme point de contrôle." + }, + "end_x": { + "name": "fin_x", + "tooltip": "Coordonnée X normalisée (0-1) pour la position d'arrivée." + }, + "end_y": { + "name": "fin_y", + "tooltip": "Coordonnée Y normalisée (0-1) pour la position d'arrivée." + }, + "height": { + "name": "hauteur" + }, + "interpolation": { + "name": "interpolation", + "tooltip": "Contrôle le timing/la vitesse du mouvement le long du chemin." + }, + "mid_x": { + "name": "milieu_x", + "tooltip": "Point de contrôle X normalisé pour la courbe de Bézier. Utilisé uniquement lorsque 'bezier' est activé." + }, + "mid_y": { + "name": "milieu_y", + "tooltip": "Point de contrôle Y normalisé pour la courbe de Bézier. Utilisé uniquement lorsque 'bezier' est activé." + }, + "num_frames": { + "name": "nombre_d_images" + }, + "num_tracks": { + "name": "nombre_de_pistes" + }, + "start_x": { + "name": "départ_x", + "tooltip": "Coordonnée X normalisée (0-1) pour la position de départ." + }, + "start_y": { + "name": "départ_y", + "tooltip": "Coordonnée Y normalisée (0-1) pour la position de départ." + }, + "track_mask": { + "name": "masque_de_piste", + "tooltip": "Masque optionnel pour indiquer les images visibles." + }, + "track_spread": { + "name": "écartement_des_pistes", + "tooltip": "Distance normalisée entre les pistes. Les pistes sont réparties perpendiculairement à la direction du mouvement." + }, + "width": { + "name": "largeur" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "longueur_de_piste", + "tooltip": null + } + } + }, "GetImageSize": { "description": "Retourne la largeur et la hauteur de l'image, et la transmet inchangée.", "display_name": "Obtenir la taille de l'image", @@ -2735,17 +3387,17 @@ "name": "image" } }, - "outputs": { - "0": { - "name": "largeur" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "hauteur" + { + "tooltip": null }, - "2": { - "name": "taille_du_lot" + { + "tooltip": null } - } + ] }, "GetVideoComponents": { "description": "Extrait tous les composants d'une vidéo : images, audio et fréquence d’images.", @@ -2783,6 +3435,11 @@ "tapered_corners": { "name": "coins_évasés" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Hunyuan3Dv2Conditioning": { @@ -2792,14 +3449,14 @@ "name": "sortie_vision_clip" } }, - "outputs": { - "0": { - "name": "positif" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "négatif" + { + "tooltip": null } - } + ] }, "Hunyuan3Dv2ConditioningMultiView": { "display_name": "Hunyuan3Dv2ConditioningMultiView", @@ -2817,14 +3474,14 @@ "name": "droite" } }, - "outputs": { - "0": { - "name": "positif" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "négatif" + { + "tooltip": null } - } + ] }, "HunyuanImageToVideo": { "display_name": "HunyuanImageToVideo", @@ -2896,6 +3553,120 @@ } } }, + "HunyuanVideo15ImageToVideo": { + "display_name": "HunyuanVideo15ImageToVideo", + "inputs": { + "batch_size": { + "name": "taille_du_lot" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "height": { + "name": "hauteur" + }, + "length": { + "name": "longueur" + }, + "negative": { + "name": "négatif" + }, + "positive": { + "name": "positif" + }, + "start_image": { + "name": "image_de_départ" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largeur" + } + }, + "outputs": { + "0": { + "name": "positif", + "tooltip": null + }, + "1": { + "name": "négatif", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "HunyuanVideo15LatentUpscaleWithModel": { + "display_name": "Hunyuan Video 15 Latent Upscale With Model", + "inputs": { + "crop": { + "name": "rogner" + }, + "height": { + "name": "hauteur" + }, + "model": { + "name": "modèle" + }, + "samples": { + "name": "échantillons" + }, + "upscale_method": { + "name": "méthode_d_agrandissement" + }, + "width": { + "name": "largeur" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "HunyuanVideo15SuperResolution": { + "display_name": "HunyuanVideo15SuperResolution", + "inputs": { + "clip_vision_output": { + "name": "clip_vision_output" + }, + "latent": { + "name": "latent" + }, + "negative": { + "name": "négatif" + }, + "noise_augmentation": { + "name": "augmentation_du_bruit" + }, + "positive": { + "name": "positif" + }, + "start_image": { + "name": "image_de_départ" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "positif", + "tooltip": null + }, + "1": { + "name": "négatif", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, "HyperTile": { "display_name": "HyperTile", "inputs": { @@ -3100,6 +3871,11 @@ "strength": { "name": "intensité" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageBatch": { @@ -3163,6 +3939,26 @@ "image": { "name": "image" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageCompare": { + "description": "Compare deux images côte à côte avec un curseur.", + "display_name": "Comparaison d’images", + "inputs": { + "compare_view": { + "name": "vue de comparaison" + }, + "image_a": { + "name": "image A" + }, + "image_b": { + "name": "image B" + } } }, "ImageCompositeMasked": { @@ -3186,6 +3982,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageCrop": { @@ -3206,6 +4007,30 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageDeduplication": { + "display_name": "Déduplication d'image", + "inputs": { + "images": { + "name": "images", + "tooltip": "Liste des images à traiter." + }, + "similarity_threshold": { + "name": "seuil_de_similarité", + "tooltip": "Seuil de similarité (0-1). Plus la valeur est élevée, plus les images sont similaires. Les images au-dessus de ce seuil sont considérées comme des doublons." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Images traitées" + } } }, "ImageFlip": { @@ -3217,6 +4042,11 @@ "image": { "name": "image" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageFromBatch": { @@ -3231,6 +4061,42 @@ "length": { "name": "longueur" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageGrid": { + "display_name": "Grille d'images", + "inputs": { + "cell_height": { + "name": "hauteur_de_cellule", + "tooltip": "Hauteur de chaque cellule dans la grille." + }, + "cell_width": { + "name": "largeur_de_cellule", + "tooltip": "Largeur de chaque cellule dans la grille." + }, + "columns": { + "name": "colonnes", + "tooltip": "Nombre de colonnes dans la grille." + }, + "images": { + "name": "images", + "tooltip": "Liste des images à traiter." + }, + "padding": { + "name": "marge", + "tooltip": "Marge entre les images." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Images traitées" + } } }, "ImageInvert": { @@ -3339,6 +4205,11 @@ "rotation": { "name": "rotation" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageScale": { @@ -3387,6 +4258,11 @@ "upscale_method": { "name": "méthode_d'agrandissement" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageScaleToTotalPixels": { @@ -3398,6 +4274,9 @@ "megapixels": { "name": "mégapixels" }, + "resolution_steps": { + "name": "étapes_de_résolution" + }, "upscale_method": { "name": "méthode_d'agrandissement" } @@ -3452,6 +4331,11 @@ "spacing_width": { "name": "espacement_largeur" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageToMask": { @@ -3463,6 +4347,11 @@ "image": { "name": "image" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageUpscaleWithModel": { @@ -3572,6 +4461,29 @@ "mask": { "name": "masque" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "JoinAudioChannels": { + "description": "Fusionne les canaux audio mono gauche et droit en un audio stéréo.", + "display_name": "Fusionner les canaux audio", + "inputs": { + "audio_left": { + "name": "audio gauche" + }, + "audio_right": { + "name": "audio droit" + } + }, + "outputs": { + "0": { + "name": "audio", + "tooltip": null + } } }, "JoinImageWithAlpha": { @@ -3697,6 +4609,58 @@ "sampler_name": { "name": "sampler_name" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Kandinsky5ImageToVideo": { + "display_name": "Kandinsky5ImageToVideo", + "inputs": { + "batch_size": { + "name": "taille_du_lot" + }, + "height": { + "name": "hauteur" + }, + "length": { + "name": "longueur" + }, + "negative": { + "name": "négatif" + }, + "positive": { + "name": "positif" + }, + "start_image": { + "name": "image_de_départ" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largeur" + } + }, + "outputs": { + "0": { + "name": "positif", + "tooltip": null + }, + "1": { + "name": "négatif", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": "Latent vidéo vide" + }, + "3": { + "name": "cond_latent", + "tooltip": "Images de départ encodées propres, utilisées pour remplacer le début bruité des latents de sortie du modèle" + } } }, "KarrasScheduler": { @@ -3714,6 +4678,11 @@ "steps": { "name": "étapes" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "KlingCameraControlI2VNode": { @@ -3869,7 +4838,6 @@ } }, "KlingImage2VideoNode": { - "description": "Nœud Kling Image to Video", "display_name": "Kling Image to Video", "inputs": { "aspect_ratio": { @@ -3957,6 +4925,35 @@ } } }, + "KlingImageToVideoWithAudio": { + "display_name": "Kling Image (première image) vers vidéo avec audio", + "inputs": { + "duration": { + "name": "durée" + }, + "generate_audio": { + "name": "générer_audio" + }, + "mode": { + "name": "mode" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "invite", + "tooltip": "Invite textuelle positive." + }, + "start_frame": { + "name": "image_de_départ" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingLipSyncAudioToVideoNode": { "description": "Nœud de synchronisation labiale Kling. Synchronise les mouvements de la bouche dans un fichier vidéo avec le contenu audio d’un fichier audio.", "display_name": "Synchronisation labiale Kling : Vidéo avec audio", @@ -4018,6 +5015,227 @@ } } }, + "KlingMotionControl": { + "display_name": "Kling Contrôle du Mouvement", + "inputs": { + "character_orientation": { + "name": "orientation_du_personnage", + "tooltip": "Contrôle l'origine de l'orientation/position du personnage.\nvidéo : les mouvements, expressions, mouvements de caméra et orientation suivent la vidéo de référence de mouvement (autres détails via l'invite).\nimage : les mouvements et expressions suivent toujours la vidéo de référence de mouvement, mais l'orientation du personnage correspond à l'image de référence (caméra/autres détails via l'invite)." + }, + "keep_original_sound": { + "name": "garder_son_original" + }, + "mode": { + "name": "mode" + }, + "prompt": { + "name": "invite" + }, + "reference_image": { + "name": "image_de_référence" + }, + "reference_video": { + "name": "vidéo_de_référence", + "tooltip": "Vidéo de référence de mouvement utilisée pour piloter le mouvement/l'expression.\nLes limites de durée dépendent de l'orientation_du_personnage :\n - image : 3–10s (max 10s)\n - vidéo : 3–30s (max 30s)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProEditVideoNode": { + "description": "Éditez une vidéo existante avec le dernier modèle de Kling.", + "display_name": "Kling Omni Édition Vidéo (Pro)", + "inputs": { + "keep_original_sound": { + "name": "garder_son_original" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "invite", + "tooltip": "Une invite textuelle décrivant le contenu de la vidéo. Cela peut inclure des descriptions positives et négatives." + }, + "reference_images": { + "name": "images_de_référence", + "tooltip": "Jusqu'à 4 images de référence supplémentaires." + }, + "resolution": { + "name": "résolution" + }, + "video": { + "name": "vidéo", + "tooltip": "Vidéo à éditer. La longueur de la vidéo de sortie sera la même." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProFirstLastFrameNode": { + "description": "Utilisez une image de départ, une image de fin optionnelle ou des images de référence avec le dernier modèle Kling.", + "display_name": "Kling Omni Première-Dernière-Image vers Vidéo (Pro)", + "inputs": { + "duration": { + "name": "duration" + }, + "end_frame": { + "name": "end_frame", + "tooltip": "Une image de fin optionnelle pour la vidéo. Cela ne peut pas être utilisé en même temps que 'reference_images'." + }, + "first_frame": { + "name": "first_frame" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Une invite textuelle décrivant le contenu de la vidéo. Cela peut inclure des descriptions positives et négatives." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "Jusqu'à 6 images de référence supplémentaires." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageNode": { + "description": "Créez ou modifiez des images avec le dernier modèle de Kling.", + "display_name": "Kling Omni Image (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Une invite textuelle décrivant le contenu de l'image. Cela peut inclure des descriptions positives et négatives." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "Jusqu'à 10 images de référence supplémentaires." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageToVideoNode": { + "description": "Utilisez jusqu'à 7 images de référence pour générer une vidéo avec le dernier modèle Kling.", + "display_name": "Kling Omni Image vers Vidéo (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Une invite textuelle décrivant le contenu de la vidéo. Cela peut inclure des descriptions positives et négatives." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "Jusqu'à 7 images de référence." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProTextToVideoNode": { + "description": "Utilisez des invites textuelles pour générer des vidéos avec le dernier modèle Kling.", + "display_name": "Kling Omni Texte vers Vidéo (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Une invite textuelle décrivant le contenu de la vidéo. Cela peut inclure des descriptions positives et négatives." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProVideoToVideoNode": { + "description": "Utilisez une vidéo et jusqu'à 4 images de référence pour générer une vidéo avec le dernier modèle Kling.", + "display_name": "Kling Omni Vidéo à Vidéo (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "keep_original_sound": { + "name": "keep_original_sound" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Une invite textuelle décrivant le contenu de la vidéo. Cela peut inclure des descriptions positives et négatives." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "Jusqu'à 4 images de référence supplémentaires." + }, + "reference_video": { + "name": "reference_video", + "tooltip": "Vidéo à utiliser comme référence." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingSingleImageVideoEffectNode": { "description": "Obtenez différents effets spéciaux lors de la génération d'une vidéo selon le effect_scene.", "display_name": "Effets vidéo Kling", @@ -4132,6 +5350,35 @@ } } }, + "KlingTextToVideoWithAudio": { + "display_name": "Kling Texte en Vidéo avec Audio", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "generate_audio": { + "name": "generate_audio" + }, + "mode": { + "name": "mode" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Invite textuelle positive." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingVideoExtendNode": { "description": "Nœud Kling Video Extend. Étend les vidéos créées par d'autres nœuds Kling. Le video_id est créé en utilisant d'autres nœuds Kling.", "display_name": "Kling Video Extend", @@ -4186,6 +5433,26 @@ } } }, + "LTXAVTextEncoderLoader": { + "description": "[Recettes]\n\nltxav : gemma 3 12B", + "display_name": "Chargeur d'encodeur texte audio LTXV", + "inputs": { + "ckpt_name": { + "name": "ckpt_name" + }, + "device": { + "name": "device" + }, + "text_encoder": { + "name": "text_encoder" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LTXVAddGuide": { "display_name": "LTXVAddGuide", "inputs": { @@ -4228,6 +5495,76 @@ } } }, + "LTXVAudioVAEDecode": { + "display_name": "Décodage Audio VAE LTXV", + "inputs": { + "audio_vae": { + "name": "audio_vae", + "tooltip": "Le modèle Audio VAE utilisé pour décoder le latent." + }, + "samples": { + "name": "samples", + "tooltip": "Le latent à décoder." + } + }, + "outputs": { + "0": { + "name": "Audio", + "tooltip": null + } + } + }, + "LTXVAudioVAEEncode": { + "display_name": "Encodage Audio VAE LTXV", + "inputs": { + "audio": { + "name": "audio", + "tooltip": "L'audio à encoder." + }, + "audio_vae": { + "name": "audio_vae", + "tooltip": "Le modèle Audio VAE à utiliser pour l'encodage." + } + }, + "outputs": { + "0": { + "name": "Audio Latent", + "tooltip": null + } + } + }, + "LTXVAudioVAELoader": { + "display_name": "Chargeur Audio VAE LTXV", + "inputs": { + "ckpt_name": { + "name": "ckpt_name", + "tooltip": "Point de contrôle Audio VAE à charger." + } + }, + "outputs": { + "0": { + "name": "Audio VAE", + "tooltip": null + } + } + }, + "LTXVConcatAVLatent": { + "display_name": "Concaténation AV Latent LTXV", + "inputs": { + "audio_latent": { + "name": "audio_latent" + }, + "video_latent": { + "name": "video_latent" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, "LTXVConditioning": { "display_name": "LTXVConditioning", "inputs": { @@ -4280,6 +5617,33 @@ } } }, + "LTXVEmptyLatentAudio": { + "display_name": "Audio latent vide LTXV", + "inputs": { + "audio_vae": { + "name": "audio_vae", + "tooltip": "Le modèle Audio VAE dont obtenir la configuration." + }, + "batch_size": { + "name": "batch_size", + "tooltip": "Le nombre d'échantillons audio latents dans le lot." + }, + "frame_rate": { + "name": "frame_rate", + "tooltip": "Nombre d'images par seconde." + }, + "frames_number": { + "name": "frames_number", + "tooltip": "Nombre d'images." + } + }, + "outputs": { + "0": { + "name": "Latent", + "tooltip": null + } + } + }, "LTXVImgToVideo": { "display_name": "LTXVImgToVideo", "inputs": { @@ -4326,6 +5690,47 @@ } } }, + "LTXVImgToVideoInplace": { + "display_name": "LTXVImgToVideoInplace", + "inputs": { + "bypass": { + "name": "contournement", + "tooltip": "Ignorer le conditionnement." + }, + "image": { + "name": "image" + }, + "latent": { + "name": "latent" + }, + "strength": { + "name": "force" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, + "LTXVLatentUpsampler": { + "display_name": "LTXVLatentUpsampler", + "inputs": { + "samples": { + "name": "échantillons" + }, + "upscale_model": { + "name": "modèle_d’agrandissement" + }, + "vae": { + "name": "vae" + } + } + }, "LTXVPreprocess": { "display_name": "LTXVPreprocess", "inputs": { @@ -4374,6 +5779,25 @@ } } }, + "LTXVSeparateAVLatent": { + "description": "LTXV Séparer AV Latent", + "display_name": "LTXVSeparateAVLatent", + "inputs": { + "av_latent": { + "name": "av_latent" + } + }, + "outputs": { + "0": { + "name": "latent_vidéo", + "tooltip": null + }, + "1": { + "name": "latent_audio", + "tooltip": null + } + } + }, "LaplaceScheduler": { "display_name": "LaplaceScheduler", "inputs": { @@ -4392,6 +5816,11 @@ "steps": { "name": "steps" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LatentAdd": { @@ -4529,6 +5958,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LatentConcat": { @@ -4592,6 +6026,25 @@ } } }, + "LatentCutToBatch": { + "display_name": "LatentCutToBatch", + "inputs": { + "dim": { + "name": "dim" + }, + "samples": { + "name": "samples" + }, + "slice_size": { + "name": "slice_size" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LatentFlip": { "display_name": "Retourner Latent", "inputs": { @@ -4745,6 +6198,19 @@ } } }, + "LatentUpscaleModelLoader": { + "display_name": "Charger le Modèle d'Agrandissement Latent", + "inputs": { + "model_name": { + "name": "model_name" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LazyCache": { "description": "Une version maison d'EasyCache - une version encore 'plus facile' d'EasyCache à implémenter. Fonctionne globalement moins bien qu'EasyCache, mais mieux dans certains cas rares ET une compatibilité universelle avec tout dans ComfyUI.", "display_name": "CacheParesseux", @@ -4792,70 +6258,32 @@ }, "upload 3d model": { }, - "width": { - "name": "largeur" - } - }, - "outputs": { - "0": { - "name": "image" - }, - "1": { - "name": "masque" - }, - "2": { - "name": "chemin_maillage" - }, - "3": { - "name": "normale" - }, - "4": { - "name": "lineart" - }, - "5": { - "name": "info_caméra" - }, - "6": { - "name": "enregistrement_vidéo" - } - } - }, - "Load3DAnimation": { - "display_name": "Charger 3D - Animation", - "inputs": { - "height": { - "name": "hauteur" - }, - "image": { - "name": "image" - }, - "model_file": { - "name": "fichier_modèle" + "upload extra resources": { }, "width": { "name": "largeur" } }, - "outputs": { - "0": { - "name": "image" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "masque" + { + "tooltip": null }, - "2": { - "name": "chemin_maillage" + { + "tooltip": null }, - "3": { - "name": "normale" + { + "tooltip": null }, - "4": { - "name": "info_caméra" + { + "tooltip": null }, - "5": { - "name": "enregistrement_vidéo" + { + "tooltip": null } - } + ] }, "LoadAudio": { "display_name": "ChargerAudio", @@ -4869,6 +6297,11 @@ "upload": { "name": "choisissez le fichier à télécharger" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LoadImage": { @@ -4882,6 +6315,21 @@ } } }, + "LoadImageDataSetFromFolder": { + "display_name": "Charger un Jeu de Données d'Images depuis un Dossier", + "inputs": { + "folder": { + "name": "folder", + "tooltip": "Le dossier depuis lequel charger les images." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Liste des images chargées" + } + } + }, "LoadImageMask": { "display_name": "Charger Image (comme Masque)", "inputs": { @@ -4900,6 +6348,8 @@ "description": "Chargez une image à partir du dossier de sortie. Lorsque le bouton de rafraîchissement est cliqué, le nœud mettra à jour la liste des images et sélectionnera automatiquement la première image, permettant une itération facile.", "display_name": "Charger l'image (à partir des sorties)", "inputs": { + "Auto-refresh after generation": { + }, "image": { "name": "image" }, @@ -4910,41 +6360,22 @@ } } }, - "LoadImageSetFromFolderNode": { - "description": "Charge un lot d'images depuis un répertoire pour l'entraînement.", - "display_name": "Charger un jeu de données d'images depuis un dossier", + "LoadImageTextDataSetFromFolder": { + "display_name": "Charger un Jeu de Données Images et Textes depuis un Dossier", "inputs": { "folder": { - "name": "dossier", + "name": "folder", "tooltip": "Le dossier depuis lequel charger les images." - }, - "resize_method": { - "name": "méthode_redimensionnement" } - } - }, - "LoadImageTextSetFromFolderNode": { - "description": "Charge un lot d'images et de légendes depuis un répertoire pour l'entraînement.", - "display_name": "Charger un jeu de données d'images et de texte depuis un dossier", - "inputs": { - "clip": { - "name": "clip", - "tooltip": "Le modèle CLIP utilisé pour encoder le texte." + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Liste des images chargées" }, - "folder": { - "name": "dossier", - "tooltip": "Le dossier depuis lequel charger les images." - }, - "height": { - "name": "hauteur", - "tooltip": "La hauteur à laquelle redimensionner les images. -1 signifie utiliser la hauteur originale." - }, - "resize_method": { - "name": "méthode_redimensionnement" - }, - "width": { - "name": "largeur", - "tooltip": "La largeur à laquelle redimensionner les images. -1 signifie utiliser la largeur originale." + "1": { + "name": "texts", + "tooltip": "Liste des légendes textuelles" } } }, @@ -4956,6 +6387,25 @@ } } }, + "LoadTrainingDataset": { + "display_name": "Charger le jeu de données d'entraînement", + "inputs": { + "folder_name": { + "name": "folder_name", + "tooltip": "Nom du dossier contenant le jeu de données sauvegardé (dans le répertoire de sortie)." + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "Liste de dictionnaires latents" + }, + "1": { + "name": "conditioning", + "tooltip": "Liste de listes de conditionnement" + } + } + }, "LoadVideo": { "display_name": "Charger une vidéo", "inputs": { @@ -5027,7 +6477,6 @@ } }, "LoraModelLoader": { - "description": "Charge les poids LoRA entraînés depuis le nœud Train LoRA.", "display_name": "Charger le modèle LoRA", "inputs": { "lora": { @@ -5043,11 +6492,11 @@ "tooltip": "Intensité de modification du modèle de diffusion. Cette valeur peut être négative." } }, - "outputs": { - "0": { - "tooltip": "Le modèle de diffusion modifié." + "outputs": [ + { + "name": "model" } - } + ] }, "LoraSave": { "display_name": "Extraire et Sauvegarder Lora", @@ -5075,14 +6524,15 @@ } }, "LossGraphNode": { - "description": "Trace le graphique de perte et l'enregistre dans le répertoire de sortie.", "display_name": "Tracer le graphique de perte", "inputs": { "filename_prefix": { - "name": "préfixe_nom_fichier" + "name": "préfixe_nom_fichier", + "tooltip": "Préfixe pour l'image du graphique de perte sauvegardée." }, "loss": { - "name": "perte" + "name": "perte", + "tooltip": "Carte de perte provenant du nœud d'entraînement." } } }, @@ -5388,6 +6838,50 @@ } } }, + "MakeTrainingDataset": { + "display_name": "Créer un jeu de données d’entraînement", + "inputs": { + "clip": { + "name": "clip", + "tooltip": "Modèle CLIP pour encoder le texte en conditionnement." + }, + "images": { + "name": "images", + "tooltip": "Liste d’images à encoder." + }, + "texts": { + "name": "textes", + "tooltip": "Liste de légendes textuelles. Peut être de longueur n (correspondant aux images), 1 (répété pour tous), ou omis (utilise une chaîne vide)." + }, + "vae": { + "name": "vae", + "tooltip": "Modèle VAE pour encoder les images en latents." + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "Liste de dictionnaires latents" + }, + "1": { + "name": "conditionnement", + "tooltip": "Liste de listes de conditionnement" + } + } + }, + "ManualSigmas": { + "display_name": "Sigmas manuels", + "inputs": { + "sigmas": { + "name": "sigmas" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "MaskComposite": { "display_name": "MaskComposite", "inputs": { @@ -5406,6 +6900,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "MaskPreview": { @@ -5423,6 +6922,315 @@ "mask": { "name": "masque" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MergeImageLists": { + "display_name": "Fusionner les listes d’images", + "inputs": { + "images": { + "name": "images", + "tooltip": "Liste d’images à traiter." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Images traitées" + } + } + }, + "MergeTextLists": { + "display_name": "Fusionner les listes de textes", + "inputs": { + "texts": { + "name": "textes", + "tooltip": "Liste de textes à traiter." + } + }, + "outputs": { + "0": { + "name": "textes", + "tooltip": "Textes traités" + } + } + }, + "MeshyAnimateModelNode": { + "description": "Appliquez une action d’animation spécifique à un personnage déjà riggé.", + "display_name": "Meshy : Animer le modèle", + "inputs": { + "action_id": { + "name": "action_id", + "tooltip": "Visitez https://docs.meshy.ai/en/api/animation-library pour la liste des valeurs disponibles." + }, + "rig_task_id": { + "name": "rig_task_id" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + } + } + }, + "MeshyImageToModelNode": { + "display_name": "Meshy : Image vers modèle", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "image": { + "name": "image" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "Spécifiez le mode de pose pour le modèle généré." + }, + "seed": { + "name": "seed", + "tooltip": "Le seed contrôle si le nœud doit être relancé ; les résultats restent non déterministes quel que soit le seed." + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "Si défini sur faux, retourne un maillage triangulaire non traité." + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "should_texture": { + "name": "should_texture", + "tooltip": "Détermine si des textures sont générées. Si défini sur faux, la phase de texturage est ignorée et un maillage sans textures est retourné." + }, + "should_texture_enable_pbr": { + "name": "enable_pbr" + }, + "should_texture_texture_prompt": { + "name": "texture_prompt" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyMultiImageToModelNode": { + "display_name": "Meshy : Multi-image vers modèle", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "Spécifiez le mode de pose pour le modèle généré." + }, + "seed": { + "name": "seed", + "tooltip": "Le seed contrôle si le nœud doit être relancé ; les résultats restent non déterministes quel que soit le seed." + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "Si défini sur faux, retourne un maillage triangulaire non traité." + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "should_texture": { + "name": "should_texture", + "tooltip": "Détermine si des textures sont générées. Si défini sur faux, la phase de texturage est ignorée et un maillage sans textures est retourné." + }, + "should_texture_enable_pbr": { + "name": "enable_pbr" + }, + "should_texture_texture_prompt": { + "name": "texture_prompt" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRefineNode": { + "description": "Affinez un modèle brouillon précédemment créé.", + "display_name": "Meshy : Affiner le modèle brouillon", + "inputs": { + "enable_pbr": { + "name": "activer_pbr", + "tooltip": "Générer des cartes PBR (métallique, rugosité, normale) en plus de la couleur de base. Remarque : cela doit être désactivé lors de l’utilisation du style Sculpture, car le style Sculpture génère ses propres cartes PBR." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "model": { + "name": "modèle" + }, + "texture_image": { + "name": "image_texture", + "tooltip": "Seul l’un de 'texture_image' ou 'texture_prompt' peut être utilisé à la fois." + }, + "texture_prompt": { + "name": "invite_texture", + "tooltip": "Fournissez une invite textuelle pour guider le processus de texturage. Maximum 600 caractères. Ne peut pas être utilisé en même temps que 'texture_image'." + } + }, + "outputs": { + "0": { + "name": "fichier_modèle", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRigModelNode": { + "description": "Fournit un personnage riggé dans des formats standards. L’auto-rigging n’est actuellement pas adapté aux maillages non texturés, aux assets non humanoïdes ou aux assets humanoïdes avec une structure de membres et de corps peu claire.", + "display_name": "Meshy : Rig du modèle", + "inputs": { + "height_meters": { + "name": "hauteur_mètres", + "tooltip": "La hauteur approximative du modèle de personnage en mètres. Cela aide à l’échelle et à la précision du rigging." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "texture_image": { + "name": "image_texture", + "tooltip": "L’image de texture couleur de base UV-dépliée du modèle." + } + }, + "outputs": { + "0": { + "name": "fichier_modèle", + "tooltip": null + }, + "1": { + "name": "rig_task_id", + "tooltip": null + } + } + }, + "MeshyTextToModelNode": { + "display_name": "Meshy : Texte vers modèle", + "inputs": { + "control_after_generate": { + "name": "contrôle après génération" + }, + "model": { + "name": "modèle" + }, + "pose_mode": { + "name": "mode_pose", + "tooltip": "Spécifiez le mode de pose pour le modèle généré." + }, + "prompt": { + "name": "invite" + }, + "seed": { + "name": "graine", + "tooltip": "La graine contrôle si le nœud doit être relancé ; les résultats sont non déterministes quel que soit la graine." + }, + "should_remesh": { + "name": "doit_remesher", + "tooltip": "Si désactivé, retourne un maillage triangulaire non traité." + }, + "should_remesh_target_polycount": { + "name": "cible_polygones" + }, + "should_remesh_topology": { + "name": "topologie" + }, + "style": { + "name": "style" + }, + "symmetry_mode": { + "name": "mode_symétrie" + } + }, + "outputs": { + "0": { + "name": "fichier_modèle", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyTextureNode": { + "display_name": "Meshy : Modèle de texture", + "inputs": { + "enable_original_uv": { + "name": "activer_uv_original", + "tooltip": "Utiliser l’UV original du modèle au lieu de générer de nouveaux UV. Lorsque cette option est activée, Meshy préserve les textures existantes du modèle importé. Si le modèle ne possède pas d’UV original, la qualité du résultat peut être moins bonne." + }, + "image_style": { + "name": "style_image", + "tooltip": "Une image 2D pour guider le processus de texturisation. Ne peut pas être utilisée en même temps que « text_style_prompt »." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "model": { + "name": "modèle" + }, + "pbr": { + "name": "pbr" + }, + "text_style_prompt": { + "name": "invite_style_texte", + "tooltip": "Décrivez le style de texture souhaité pour l’objet en utilisant du texte. 600 caractères maximum. Ne peut pas être utilisé en même temps que « image_style »." + } + }, + "outputs": { + "0": { + "name": "fichier_modèle", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } } }, "MinimaxHailuoVideoNode": { @@ -7866,6 +9674,52 @@ } } }, + "NormalizeImages": { + "display_name": "Normaliser les images", + "inputs": { + "images": { + "name": "images", + "tooltip": "Image à traiter." + }, + "mean": { + "name": "moyenne", + "tooltip": "Valeur moyenne pour la normalisation." + }, + "std": { + "name": "écart_type", + "tooltip": "Écart type pour la normalisation." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Images traitées" + } + } + }, + "NormalizeVideoLatentStart": { + "description": "Normalise les images initiales d’un latent vidéo pour faire correspondre leur moyenne et leur écart type à ceux des images de référence suivantes. Permet de réduire les différences entre les premières images et le reste de la vidéo.", + "display_name": "NormalizeVideoLatentStart", + "inputs": { + "latent": { + "name": "latent" + }, + "reference_frame_count": { + "name": "reference_frame_count", + "tooltip": "Nombre d’images latentes après les images de départ à utiliser comme référence" + }, + "start_frame_count": { + "name": "start_frame_count", + "tooltip": "Nombre d’images latentes à normaliser, à partir du début" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, "OpenAIChatConfig": { "description": "Permet de spécifier des options de configuration avancées pour les nœuds de chat OpenAI.", "display_name": "Options avancées OpenAI ChatGPT", @@ -8015,6 +9869,9 @@ "name": "mask", "tooltip": "Masque optionnel pour l'inpainting (les zones blanches seront remplacées)" }, + "model": { + "name": "model" + }, "n": { "name": "n", "tooltip": "Combien d'images générer" @@ -8373,265 +10230,6 @@ } } }, - "PikaImageToVideoNode2_2": { - "description": "Envoie une image et une invite à l'API Pika v2.2 pour générer une vidéo.", - "display_name": "Pika Image vers Vidéo", - "inputs": { - "control_after_generate": { - "name": "contrôle après génération" - }, - "duration": { - "name": "durée" - }, - "image": { - "name": "image", - "tooltip": "L'image à convertir en vidéo" - }, - "negative_prompt": { - "name": "invite négative" - }, - "prompt_text": { - "name": "texte de l'invite" - }, - "resolution": { - "name": "résolution" - }, - "seed": { - "name": "graine" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaScenesV2_2": { - "description": "Combinez vos images pour créer une vidéo avec les objets qu'elles contiennent. Téléchargez plusieurs images comme ingrédients et générez une vidéo de haute qualité qui les intègre toutes.", - "display_name": "Pika Scenes (Composition Vidéo Image)", - "inputs": { - "aspect_ratio": { - "name": "aspect_ratio", - "tooltip": "Rapport d'aspect (largeur / hauteur)" - }, - "control_after_generate": { - "name": "control after generate" - }, - "duration": { - "name": "duration" - }, - "image_ingredient_1": { - "name": "image_ingredient_1", - "tooltip": "Image qui sera utilisée comme ingrédient pour créer une vidéo." - }, - "image_ingredient_2": { - "name": "image_ingredient_2", - "tooltip": "Image qui sera utilisée comme ingrédient pour créer une vidéo." - }, - "image_ingredient_3": { - "name": "image_ingredient_3", - "tooltip": "Image qui sera utilisée comme ingrédient pour créer une vidéo." - }, - "image_ingredient_4": { - "name": "image_ingredient_4", - "tooltip": "Image qui sera utilisée comme ingrédient pour créer une vidéo." - }, - "image_ingredient_5": { - "name": "image_ingredient_5", - "tooltip": "Image qui sera utilisée comme ingrédient pour créer une vidéo." - }, - "ingredients_mode": { - "name": "ingredients_mode" - }, - "negative_prompt": { - "name": "negative_prompt" - }, - "prompt_text": { - "name": "prompt_text" - }, - "resolution": { - "name": "resolution" - }, - "seed": { - "name": "seed" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaStartEndFrameNode2_2": { - "description": "Générez une vidéo en combinant votre première et dernière image. Téléversez deux images pour définir les points de départ et d’arrivée, et laissez l’IA créer une transition fluide entre elles.", - "display_name": "Pika Début et Fin d’Image en Vidéo", - "inputs": { - "control_after_generate": { - "name": "contrôle après génération" - }, - "duration": { - "name": "duration" - }, - "image_end": { - "name": "image_end", - "tooltip": "La dernière image à combiner." - }, - "image_start": { - "name": "image_start", - "tooltip": "La première image à combiner." - }, - "negative_prompt": { - "name": "negative_prompt" - }, - "prompt_text": { - "name": "prompt_text" - }, - "resolution": { - "name": "resolution" - }, - "seed": { - "name": "seed" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaTextToVideoNode2_2": { - "description": "Envoie une invite textuelle à l'API Pika v2.2 pour générer une vidéo.", - "display_name": "Pika Texte en Vidéo", - "inputs": { - "aspect_ratio": { - "name": "rapport d'aspect", - "tooltip": "Rapport d'aspect (largeur / hauteur)" - }, - "control_after_generate": { - "name": "contrôle après génération" - }, - "duration": { - "name": "durée" - }, - "negative_prompt": { - "name": "invite négative" - }, - "prompt_text": { - "name": "texte de l'invite" - }, - "resolution": { - "name": "résolution" - }, - "seed": { - "name": "graine" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikadditions": { - "description": "Ajoutez n'importe quel objet ou image dans votre vidéo. Téléchargez une vidéo et spécifiez ce que vous souhaitez ajouter pour obtenir un résultat parfaitement intégré.", - "display_name": "Pikadditions (Insertion d'objet vidéo)", - "inputs": { - "control_after_generate": { - "name": "contrôle après génération" - }, - "image": { - "name": "image", - "tooltip": "L'image à ajouter à la vidéo." - }, - "negative_prompt": { - "name": "invite négative" - }, - "prompt_text": { - "name": "texte d'invite" - }, - "seed": { - "name": "graine" - }, - "video": { - "name": "vidéo", - "tooltip": "La vidéo à laquelle ajouter une image." - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikaffects": { - "description": "Générez une vidéo avec un Pikaffect spécifique. Pikaffects pris en charge : Cake-ify, Crumble, Crush, Decapitate, Deflate, Dissolve, Explode, Eye-pop, Inflate, Levitate, Melt, Peel, Poke, Squish, Ta-da, Tear", - "display_name": "Pikaffects (Effets vidéo)", - "inputs": { - "control_after_generate": { - "name": "contrôle après génération" - }, - "image": { - "name": "image", - "tooltip": "L’image de référence à laquelle appliquer le Pikaffect." - }, - "negative_prompt": { - "name": "negative_prompt" - }, - "pikaffect": { - "name": "pikaffect" - }, - "prompt_text": { - "name": "prompt_text" - }, - "seed": { - "name": "seed" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikaswaps": { - "description": "Remplacez n’importe quel objet ou région de votre vidéo par une nouvelle image ou un nouvel objet. Définissez les zones à remplacer soit avec un mask, soit avec des coordonnées.", - "display_name": "Pika Swaps (Remplacement d’objet vidéo)", - "inputs": { - "control_after_generate": { - "name": "contrôle après génération" - }, - "image": { - "name": "image", - "tooltip": "L’image utilisée pour remplacer l’objet masqué dans la vidéo." - }, - "mask": { - "name": "mask", - "tooltip": "Utilisez le mask pour définir les zones à remplacer dans la vidéo" - }, - "negative_prompt": { - "name": "invite négative" - }, - "prompt_text": { - "name": "texte d’invite" - }, - "region_to_modify": { - "name": "région_à_modifier", - "tooltip": "Description en texte brut de l'objet / de la région à modifier." - }, - "seed": { - "name": "graine" - }, - "video": { - "name": "vidéo", - "tooltip": "La vidéo dans laquelle remplacer un objet." - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, "PixverseImageToVideoNode": { "description": "Génère des vidéos de manière synchrone à partir du prompt et de la taille de sortie.", "display_name": "PixVerse Image vers Vidéo", @@ -8786,6 +10384,11 @@ "steps": { "name": "étapes" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "PorterDuffImageComposite": { @@ -8819,6 +10422,9 @@ "Preview3D": { "display_name": "Aperçu 3D", "inputs": { + "bg_image": { + "name": "bg_image" + }, "camera_info": { "name": "informations_de_camera" }, @@ -8830,22 +10436,13 @@ } } }, - "Preview3DAnimation": { - "display_name": "Aperçu 3D - Animation", - "inputs": { - "camera_info": { - "name": "informations_camera" - }, - "model_file": { - "name": "fichier_modèle" - } - } - }, "PreviewAny": { "display_name": "Aperçu de n'importe quel", "inputs": { "preview": { }, + "previewMode": { + }, "source": { "name": "source" } @@ -8985,6 +10582,36 @@ } } }, + "RandomCropImages": { + "display_name": "Rogner aléatoirement des images", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "height": { + "name": "height", + "tooltip": "Hauteur du rognage." + }, + "images": { + "name": "images", + "tooltip": "Image à traiter." + }, + "seed": { + "name": "seed", + "tooltip": "Graine aléatoire." + }, + "width": { + "name": "width", + "tooltip": "Largeur du rognage." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Images traitées" + } + } + }, "RandomNoise": { "display_name": "BruitAléatoire", "inputs": { @@ -8994,6 +10621,11 @@ "noise_seed": { "name": "graine_de_bruit" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RebatchImages": { @@ -9034,6 +10666,11 @@ "audio": { "name": "audio" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RecraftColorRGB": { @@ -9538,6 +11175,11 @@ "image": { "name": "image" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RepeatLatentBatch": { @@ -9551,6 +11193,51 @@ } } }, + "ReplaceText": { + "display_name": "Remplacer le texte", + "inputs": { + "find": { + "name": "find", + "tooltip": "Texte à rechercher." + }, + "replace": { + "name": "replace", + "tooltip": "Texte de remplacement." + }, + "texts": { + "name": "texts", + "tooltip": "Texte à traiter." + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "Textes traités" + } + } + }, + "ReplaceVideoLatentFrames": { + "display_name": "Remplacer les images latentes vidéo", + "inputs": { + "destination": { + "name": "destination", + "tooltip": "Le latent de destination où les images seront remplacées." + }, + "index": { + "name": "index", + "tooltip": "L’index de la première image latente dans le latent de destination où les images du latent source seront placées. Les valeurs négatives comptent à partir de la fin." + }, + "source": { + "name": "source", + "tooltip": "Le latent source fournissant les images à insérer dans le latent de destination. Si non fourni, le latent de destination est retourné inchangé." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "RescaleCFG": { "display_name": "RescaleCFG", "inputs": { @@ -9580,6 +11267,104 @@ "target_width": { "name": "largeur_cible" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ResizeImageMaskNode": { + "description": "Redimensionner une image ou un mask en utilisant différentes méthodes de mise à l'échelle.", + "display_name": "Redimensionner image/masque", + "inputs": { + "input": { + "name": "input" + }, + "resize_type": { + "name": "resize_type", + "tooltip": "Sélectionnez la méthode de redimensionnement : par dimensions exactes, facteur d'échelle, correspondance avec une autre image, etc." + }, + "resize_type_crop": { + "name": "rogner" + }, + "resize_type_height": { + "name": "hauteur" + }, + "resize_type_width": { + "name": "largeur" + }, + "scale_method": { + "name": "scale_method", + "tooltip": "Algorithme d'interpolation. 'area' est optimal pour la réduction, 'lanczos' pour l'agrandissement, 'nearest-exact' pour le pixel art." + } + }, + "outputs": { + "0": { + "name": "resized", + "tooltip": null + } + } + }, + "ResizeImagesByLongerEdge": { + "display_name": "Redimensionner les images par le bord le plus long", + "inputs": { + "images": { + "name": "images", + "tooltip": "Image à traiter." + }, + "longer_edge": { + "name": "longer_edge", + "tooltip": "Longueur cible pour le bord le plus long." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Images traitées" + } + } + }, + "ResizeImagesByShorterEdge": { + "display_name": "Redimensionner les images par le bord le plus court", + "inputs": { + "images": { + "name": "images", + "tooltip": "Image à traiter." + }, + "shorter_edge": { + "name": "shorter_edge", + "tooltip": "Longueur cible pour le bord le plus court." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Images traitées" + } + } + }, + "ResolutionBucket": { + "display_name": "Regroupement par résolution", + "inputs": { + "conditioning": { + "name": "conditioning", + "tooltip": "Liste de listes de conditionnement (doit correspondre à la longueur des latents)." + }, + "latents": { + "name": "latents", + "tooltip": "Liste de dictionnaires de latent à regrouper par résolution." + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "Liste de dictionnaires de latent groupés, un par regroupement de résolution." + }, + "1": { + "name": "conditioning", + "tooltip": "Liste de listes de conditions, une par regroupement de résolution." + } } }, "Rodin3D_Detail": { @@ -9833,6 +11618,11 @@ "steps": { "name": "étapes" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SD_4XUpscale_Conditioning": { @@ -9986,14 +11776,14 @@ "name": "sigmas" } }, - "outputs": { - "0": { - "name": "sortie" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "sortie_débruitée" + { + "tooltip": null } - } + ] }, "SamplerCustomAdvanced": { "display_name": "ÉchantillonneurPersonnaliséAvancé", @@ -10014,14 +11804,14 @@ "name": "sigmas" } }, - "outputs": { - "0": { - "name": "sortie" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "sortie_débruitée" + { + "tooltip": null } - } + ] }, "SamplerDPMAdaptative": { "display_name": "SamplerDPMAdaptative", @@ -10056,6 +11846,11 @@ "s_noise": { "name": "s_bruit" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_2M_SDE": { @@ -10073,6 +11868,11 @@ "solver_type": { "name": "type_solveur" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_2S_Ancestral": { @@ -10084,6 +11884,11 @@ "s_noise": { "name": "s_bruit" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_3M_SDE": { @@ -10098,6 +11903,11 @@ "s_noise": { "name": "s_bruit" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_SDE": { @@ -10115,6 +11925,11 @@ "s_noise": { "name": "s_bruit" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerER_SDE": { @@ -10133,6 +11948,11 @@ "solver_type": { "name": "type_solveur" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerAncestral": { @@ -10144,6 +11964,11 @@ "s_noise": { "name": "s_bruit" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerAncestralCFGPP": { @@ -10155,6 +11980,11 @@ "s_noise": { "name": "s_bruit" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerCFGpp": { @@ -10195,6 +12025,11 @@ "order": { "name": "ordre" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerSASolver": { @@ -10227,6 +12062,37 @@ "use_pece": { "name": "utiliser_pece" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerSEEDS2": { + "description": "Ce nœud d'échantillonnage peut représenter plusieurs échantillonneurs :\n\nseeds_2\n- paramètre par défaut\n\nexp_heun_2_x0\n- solver_type=phi_2, r=1.0, eta=0.0\n\nexp_heun_2_x0_sde\n- solver_type=phi_2, r=1.0, eta=1.0, s_noise=1.0", + "display_name": "SamplerSEEDS2", + "inputs": { + "eta": { + "name": "eta", + "tooltip": "Intensité stochastique" + }, + "r": { + "name": "r", + "tooltip": "Taille relative de l'étape pour l'étape intermédiaire (nœud c2)" + }, + "s_noise": { + "name": "s_noise", + "tooltip": "Multiplicateur de bruit SDE" + }, + "solver_type": { + "name": "solver_type" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplingPercentToSigma": { @@ -10243,11 +12109,11 @@ "name": "pourcent_échantillonnage" } }, - "outputs": { - "0": { - "name": "valeur_sigma" + "outputs": [ + { + "tooltip": null } - } + ] }, "SaveAnimatedPNG": { "display_name": "EnregistrerPNGAnimé", @@ -10365,6 +12231,44 @@ } } }, + "SaveImageDataSetToFolder": { + "display_name": "Enregistrer l'ensemble d'images dans un dossier", + "inputs": { + "filename_prefix": { + "name": "filename_prefix", + "tooltip": "Préfixe pour les noms de fichiers des images enregistrées." + }, + "folder_name": { + "name": "folder_name", + "tooltip": "Nom du dossier dans lequel enregistrer les images (dans le répertoire de sortie)." + }, + "images": { + "name": "images", + "tooltip": "Liste des images à enregistrer." + } + } + }, + "SaveImageTextDataSetToFolder": { + "display_name": "Enregistrer l'ensemble d'images et de textes dans un dossier", + "inputs": { + "filename_prefix": { + "name": "filename_prefix", + "tooltip": "Préfixe pour les noms de fichiers des images enregistrées." + }, + "folder_name": { + "name": "folder_name", + "tooltip": "Nom du dossier dans lequel enregistrer les images (dans le répertoire de sortie)." + }, + "images": { + "name": "images", + "tooltip": "Liste des images à enregistrer." + }, + "texts": { + "name": "texts", + "tooltip": "Liste des légendes textuelles à enregistrer." + } + } + }, "SaveImageWebsocket": { "display_name": "EnregistrerImageWebsocket", "inputs": { @@ -10384,20 +12288,20 @@ } } }, - "SaveLoRANode": { + "SaveLoRA": { "display_name": "Enregistrer les poids LoRA", "inputs": { "lora": { "name": "lora", - "tooltip": "Le modèle LoRA à enregistrer. Ne pas utiliser le modèle avec des couches LoRA." + "tooltip": "Le modèle LoRA à enregistrer. N'utilisez pas le modèle avec des couches LoRA." }, "prefix": { - "name": "préfixe", + "name": "prefix", "tooltip": "Le préfixe à utiliser pour le fichier LoRA enregistré." }, "steps": { - "name": "étapes", - "tooltip": "Optionnel : Le nombre d'étapes pour lesquelles le LoRA a été entraîné, utilisé pour nommer le fichier enregistré." + "name": "steps", + "tooltip": "Optionnel : Le nombre d'étapes pour lesquelles LoRA a été entraîné, utilisé pour nommer le fichier enregistré." } } }, @@ -10414,6 +12318,27 @@ } } }, + "SaveTrainingDataset": { + "display_name": "Enregistrer l'ensemble d'entraînement", + "inputs": { + "conditioning": { + "name": "conditioning", + "tooltip": "Liste de listes de conditionnement provenant de MakeTrainingDataset." + }, + "folder_name": { + "name": "folder_name", + "tooltip": "Nom du dossier dans lequel enregistrer l'ensemble de données (dans le répertoire de sortie)." + }, + "latents": { + "name": "latents", + "tooltip": "Liste de dictionnaires latents provenant de MakeTrainingDataset." + }, + "shard_size": { + "name": "shard_size", + "tooltip": "Nombre d'échantillons par fichier fragment." + } + } + }, "SaveVideo": { "description": "Enregistre les images d'entrée dans votre répertoire de sortie ComfyUI.", "display_name": "Enregistrer la vidéo", @@ -10534,6 +12459,11 @@ "sigmas": { "name": "sigmas" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SetHookKeyframes": { @@ -10574,6 +12504,58 @@ } } }, + "ShuffleDataset": { + "display_name": "Mélanger l'ensemble d'images", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images", + "tooltip": "Liste des images à traiter." + }, + "seed": { + "name": "seed", + "tooltip": "Graine aléatoire." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Images traitées" + } + } + }, + "ShuffleImageTextDataset": { + "display_name": "Mélanger l'ensemble image-texte", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images", + "tooltip": "Liste des images à mélanger." + }, + "seed": { + "name": "seed", + "tooltip": "Graine aléatoire." + }, + "texts": { + "name": "texts", + "tooltip": "Liste des textes à mélanger." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Images mélangées" + }, + "1": { + "name": "texts", + "tooltip": "Textes mélangés" + } + } + }, "SkipLayerGuidanceDiT": { "description": "Version générique du nœud SkipLayerGuidance qui peut être utilisée sur chaque modèle DiT.", "display_name": "SkipLayerGuidanceDiT", @@ -10670,6 +12652,11 @@ "width": { "name": "largeur" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SplitAudioChannels": { @@ -10680,14 +12667,14 @@ "name": "audio" } }, - "outputs": { - "0": { - "name": "gauche" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "droite" + { + "tooltip": null } - } + ] }, "SplitImageWithAlpha": { "display_name": "Diviser l'image avec Alpha", @@ -10715,14 +12702,14 @@ "name": "étape" } }, - "outputs": { - "0": { - "name": "high_sigmas" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "low_sigmas" + { + "tooltip": null } - } + ] }, "SplitSigmasDenoise": { "display_name": "SplitSigmasDenoise", @@ -10734,14 +12721,14 @@ "name": "sigmas" } }, - "outputs": { - "0": { - "name": "high_sigmas" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "low_sigmas" + { + "tooltip": null } - } + ] }, "StabilityAudioInpaint": { "description": "Transforme une partie d'un échantillon audio existant en utilisant des instructions textuelles.", @@ -11343,6 +13330,21 @@ } } }, + "StripWhitespace": { + "display_name": "Supprimer les espaces", + "inputs": { + "texts": { + "name": "textes", + "tooltip": "Texte à traiter." + } + }, + "outputs": { + "0": { + "name": "textes", + "tooltip": "Textes traités" + } + } + }, "StyleModelApply": { "display_name": "Appliquer le modèle de style", "inputs": { @@ -11428,6 +13430,84 @@ } } }, + "TencentImageToModelNode": { + "display_name": "Hunyuan3D : Image(s) vers Modèle (Pro)", + "inputs": { + "control_after_generate": { + "name": "contrôle après génération" + }, + "face_count": { + "name": "nombre de faces" + }, + "generate_type": { + "name": "type de génération" + }, + "generate_type_pbr": { + "name": "pbr" + }, + "image": { + "name": "image" + }, + "image_back": { + "name": "image arrière" + }, + "image_left": { + "name": "image gauche" + }, + "image_right": { + "name": "image droite" + }, + "model": { + "name": "modèle", + "tooltip": "L’option LowPoly n’est pas disponible pour le modèle `3.1`." + }, + "seed": { + "name": "graine", + "tooltip": "La graine contrôle si le nœud doit être relancé ; les résultats restent non déterministes quelle que soit la graine." + } + }, + "outputs": { + "0": { + "name": "fichier_modèle", + "tooltip": null + } + } + }, + "TencentTextToModelNode": { + "display_name": "Hunyuan3D : Texte vers Modèle (Pro)", + "inputs": { + "control_after_generate": { + "name": "contrôle après génération" + }, + "face_count": { + "name": "nombre de faces" + }, + "generate_type": { + "name": "type de génération" + }, + "generate_type_pbr": { + "name": "pbr" + }, + "model": { + "name": "modèle", + "tooltip": "L’option LowPoly n’est pas disponible pour le modèle `3.1`." + }, + "prompt": { + "name": "invite", + "tooltip": "Jusqu’à 1024 caractères pris en charge." + }, + "seed": { + "name": "graine", + "tooltip": "La graine contrôle si le nœud doit être relancé ; les résultats restent non déterministes quelle que soit la graine." + } + }, + "outputs": { + "0": { + "name": "fichier_modèle", + "tooltip": null + } + } + }, "TextEncodeAceStepAudio": { "display_name": "TextEncodeAceStepAudio", "inputs": { @@ -11523,6 +13603,70 @@ } } }, + "TextEncodeZImageOmni": { + "display_name": "TextEncodeZImageOmni", + "inputs": { + "auto_resize_images": { + "name": "redimensionnement automatique des images" + }, + "clip": { + "name": "clip" + }, + "image1": { + "name": "image1" + }, + "image2": { + "name": "image2" + }, + "image3": { + "name": "image3" + }, + "image_encoder": { + "name": "encodeur d'image" + }, + "prompt": { + "name": "invite" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TextToLowercase": { + "display_name": "Texte en minuscules", + "inputs": { + "texts": { + "name": "textes", + "tooltip": "Texte à traiter." + } + }, + "outputs": { + "0": { + "name": "textes", + "tooltip": "Textes traités" + } + } + }, + "TextToUppercase": { + "display_name": "Texte en majuscules", + "inputs": { + "texts": { + "name": "textes", + "tooltip": "Texte à traiter." + } + }, + "outputs": { + "0": { + "name": "textes", + "tooltip": "Textes traités" + } + } + }, "ThresholdMask": { "display_name": "SeuilMasque", "inputs": { @@ -11532,6 +13676,11 @@ "value": { "name": "valeur" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "TomePatchModel": { @@ -11550,6 +13699,118 @@ } } }, + "TopazImageEnhance": { + "description": "Agrandissement et amélioration d'image de référence dans l'industrie.", + "display_name": "Topaz Amélioration d'image", + "inputs": { + "color_preservation": { + "name": "préservation des couleurs", + "tooltip": "Préserver les couleurs d'origine." + }, + "creativity": { + "name": "créativité" + }, + "crop_to_fill": { + "name": "rogner pour remplir", + "tooltip": "Par défaut, l'image est encadrée si le format de sortie diffère. Activez pour rogner l'image afin de remplir les dimensions de sortie." + }, + "face_enhancement": { + "name": "amélioration du visage", + "tooltip": "Améliorer les visages (si présents) lors du traitement." + }, + "face_enhancement_creativity": { + "name": "créativité de l'amélioration du visage", + "tooltip": "Définir le niveau de créativité pour l'amélioration du visage." + }, + "face_enhancement_strength": { + "name": "intensité de l'amélioration du visage", + "tooltip": "Contrôle la netteté des visages améliorés par rapport à l'arrière-plan." + }, + "face_preservation": { + "name": "préservation du visage", + "tooltip": "Préserver l'identité faciale des sujets." + }, + "image": { + "name": "image" + }, + "model": { + "name": "modèle" + }, + "output_height": { + "name": "hauteur de sortie", + "tooltip": "Une valeur de zéro signifie conserver la hauteur d'origine ou la largeur de sortie." + }, + "output_width": { + "name": "largeur de sortie", + "tooltip": "Une valeur de zéro signifie calcul automatique (généralement la taille d'origine ou la hauteur de sortie si spécifiée)." + }, + "prompt": { + "name": "invite", + "tooltip": "Invite textuelle optionnelle pour guider l'agrandissement créatif." + }, + "subject_detection": { + "name": "détection du sujet" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TopazVideoEnhance": { + "description": "Redonnez vie à vos vidéos grâce à une technologie puissante d’upscaling et de restauration.", + "display_name": "Topaz Video Enhance", + "inputs": { + "dynamic_compression_level": { + "name": "dynamic_compression_level", + "tooltip": "Niveau CQP." + }, + "interpolation_duplicate": { + "name": "interpolation_duplicate", + "tooltip": "Analyse la vidéo d’entrée pour détecter et supprimer les images dupliquées." + }, + "interpolation_duplicate_threshold": { + "name": "interpolation_duplicate_threshold", + "tooltip": "Sensibilité de détection des images dupliquées." + }, + "interpolation_enabled": { + "name": "interpolation_enabled" + }, + "interpolation_frame_rate": { + "name": "interpolation_frame_rate", + "tooltip": "Fréquence d’images de sortie." + }, + "interpolation_model": { + "name": "interpolation_model" + }, + "interpolation_slowmo": { + "name": "interpolation_slowmo", + "tooltip": "Facteur de ralenti appliqué à la vidéo d’entrée. Par exemple, 2 rend la sortie deux fois plus lente et double la durée." + }, + "upscaler_creativity": { + "name": "upscaler_creativity", + "tooltip": "Niveau de créativité (s’applique uniquement à Starlight (Astra) Creative)." + }, + "upscaler_enabled": { + "name": "upscaler_enabled" + }, + "upscaler_model": { + "name": "upscaler_model" + }, + "upscaler_resolution": { + "name": "upscaler_resolution" + }, + "video": { + "name": "vidéo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "TorchCompileModel": { "display_name": "ModèleCompilationTorch", "inputs": { @@ -11577,6 +13838,10 @@ "name": "taille_du_lot", "tooltip": "La taille du lot à utiliser pour l'entraînement." }, + "bucket_mode": { + "name": "bucket_mode", + "tooltip": "Activer le mode de résolution par compartiments. Lorsqu’il est activé, attend des latents pré-triés depuis le nœud ResolutionBucket." + }, "control_after_generate": { "name": "contrôle après génération" }, @@ -11637,20 +13902,20 @@ "tooltip": "Le type de données à utiliser pour l'entraînement." } }, - "outputs": { - "0": { - "name": "modèle_avec_lora" + "outputs": [ + { + "tooltip": "Modèle avec LoRA appliqué" }, - "1": { - "name": "lora" + { + "tooltip": "Poids LoRA" }, - "2": { - "name": "perte" + { + "tooltip": "Historique de la perte" }, - "3": { - "name": "étapes" + { + "tooltip": "Nombre total d’étapes d’entraînement" } - } + ] }, "TrimAudioDuration": { "description": "Tronquer le tenseur audio dans la plage de temps choisie.", @@ -11667,6 +13932,11 @@ "name": "index_début", "tooltip": "Heure de début en secondes, peut être négative pour compter depuis la fin (prend en charge les sous-secondes)." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "TrimVideoLatent": { @@ -11708,23 +13978,62 @@ "TripoConversionNode": { "display_name": "Tripo : Convertir le modèle", "inputs": { + "animate_in_place": { + "name": "animate_in_place" + }, + "bake": { + "name": "bake" + }, + "export_orientation": { + "name": "export_orientation" + }, + "export_vertex_colors": { + "name": "export_vertex_colors" + }, "face_limit": { "name": "limite_faces" }, + "fbx_preset": { + "name": "fbx_preset" + }, + "flatten_bottom": { + "name": "flatten_bottom" + }, + "flatten_bottom_threshold": { + "name": "flatten_bottom_threshold" + }, + "force_symmetry": { + "name": "force_symmetry" + }, "format": { "name": "format" }, "original_model_task_id": { "name": "id_tâche_modèle_original" }, + "pack_uv": { + "name": "pack_uv" + }, + "part_names": { + "name": "part_names" + }, + "pivot_to_center_bottom": { + "name": "pivot_to_center_bottom" + }, "quad": { "name": "quad" }, + "scale_factor": { + "name": "scale_factor" + }, "texture_format": { "name": "format_texture" }, "texture_size": { "name": "taille_texture" + }, + "with_animation": { + "name": "with_animation" } } }, @@ -11734,6 +14043,9 @@ "face_limit": { "name": "limite_faces" }, + "geometry_quality": { + "name": "geometry_quality" + }, "image": { "name": "image" }, @@ -11786,6 +14098,9 @@ "face_limit": { "name": "limite_visage" }, + "geometry_quality": { + "name": "geometry_quality" + }, "image": { "name": "image" }, @@ -11903,6 +14218,9 @@ "face_limit": { "name": "limite_visage" }, + "geometry_quality": { + "name": "geometry_quality" + }, "image_seed": { "name": "graine_image" }, @@ -11981,6 +14299,25 @@ } } }, + "TruncateText": { + "display_name": "Tronquer le texte", + "inputs": { + "max_length": { + "name": "longueur_maximale", + "tooltip": "Longueur maximale du texte." + }, + "texts": { + "name": "textes", + "tooltip": "Texte à traiter." + } + }, + "outputs": { + "0": { + "name": "textes", + "tooltip": "Textes traités" + } + } + }, "UNETLoader": { "display_name": "Charger Modèle Diffusion", "inputs": { @@ -12122,6 +14459,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEDecodeHunyuan3D": { @@ -12139,6 +14481,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEDecodeTiled": { @@ -12186,6 +14533,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEEncodeForInpaint": { @@ -12264,6 +14616,63 @@ "steps": { "name": "étapes" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Veo3FirstLastFrameNode": { + "description": "Générer une vidéo à l'aide d'un prompt et des images de début et de fin.", + "display_name": "Google Veo 3 Première-Dernière-Image vers Vidéo", + "inputs": { + "aspect_ratio": { + "name": "rapport d'aspect", + "tooltip": "Rapport d'aspect de la vidéo générée" + }, + "control_after_generate": { + "name": "contrôle après génération" + }, + "duration": { + "name": "durée", + "tooltip": "Durée de la vidéo générée en secondes" + }, + "first_frame": { + "name": "première image", + "tooltip": "Image de début" + }, + "generate_audio": { + "name": "générer audio", + "tooltip": "Générer l'audio pour la vidéo." + }, + "last_frame": { + "name": "dernière image", + "tooltip": "Image de fin" + }, + "model": { + "name": "modèle" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Prompt négatif pour guider ce qu'il faut éviter dans la vidéo" + }, + "prompt": { + "name": "prompt", + "tooltip": "Description textuelle de la vidéo" + }, + "resolution": { + "name": "résolution" + }, + "seed": { + "name": "seed", + "tooltip": "Seed pour la génération de la vidéo" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Veo3VideoGenerationNode": { @@ -12392,6 +14801,166 @@ } } }, + "Vidu2ImageToVideoNode": { + "description": "Génère une vidéo à partir d’une image et d’une invite optionnelle.", + "display_name": "Génération d’image vers vidéo Vidu2", + "inputs": { + "control_after_generate": { + "name": "contrôle après génération" + }, + "duration": { + "name": "durée" + }, + "image": { + "name": "image", + "tooltip": "Une image à utiliser comme première image de la vidéo générée." + }, + "model": { + "name": "modèle" + }, + "movement_amplitude": { + "name": "amplitude du mouvement", + "tooltip": "L’amplitude du mouvement des objets dans l’image." + }, + "prompt": { + "name": "invite", + "tooltip": "Une invite textuelle optionnelle pour la génération vidéo (max 2000 caractères)." + }, + "resolution": { + "name": "résolution" + }, + "seed": { + "name": "graine" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2ReferenceVideoNode": { + "description": "Génère une vidéo à partir de plusieurs images de référence et d’une invite.", + "display_name": "Génération de vidéo à partir de références Vidu2", + "inputs": { + "aspect_ratio": { + "name": "rapport d’aspect" + }, + "audio": { + "name": "audio", + "tooltip": "Si activé, la vidéo contiendra une voix générée et une musique de fond basée sur l’invite." + }, + "control_after_generate": { + "name": "contrôle après génération" + }, + "duration": { + "name": "durée" + }, + "model": { + "name": "modèle" + }, + "movement_amplitude": { + "name": "amplitude du mouvement", + "tooltip": "L’amplitude du mouvement des objets dans l’image." + }, + "prompt": { + "name": "invite", + "tooltip": "Si activé, la vidéo inclura une voix générée et une musique de fond basée sur l’invite." + }, + "resolution": { + "name": "résolution" + }, + "seed": { + "name": "graine" + }, + "subjects": { + "name": "sujets", + "tooltip": "Pour chaque sujet, fournissez jusqu’à 3 images de référence (7 images au total pour tous les sujets). Référencez-les dans les invites via @subject{subject_id}." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2StartEndToVideoNode": { + "description": "Générez une vidéo à partir d'une image de départ, d'une image de fin et d'une invite.", + "display_name": "Génération vidéo Vidu2 à partir d'une image de début/fin", + "inputs": { + "control_after_generate": { + "name": "contrôle après génération" + }, + "duration": { + "name": "durée" + }, + "end_frame": { + "name": "image de fin" + }, + "first_frame": { + "name": "image de début" + }, + "model": { + "name": "modèle" + }, + "movement_amplitude": { + "name": "amplitude du mouvement", + "tooltip": "L'amplitude du mouvement des objets dans l'image." + }, + "prompt": { + "name": "invite", + "tooltip": "Description de l'invite (2000 caractères maximum)." + }, + "resolution": { + "name": "résolution" + }, + "seed": { + "name": "graine" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2TextToVideoNode": { + "description": "Générez une vidéo à partir d'une invite textuelle", + "display_name": "Génération vidéo Vidu2 à partir d'un texte", + "inputs": { + "aspect_ratio": { + "name": "rapport d'aspect" + }, + "background_music": { + "name": "musique de fond", + "tooltip": "Ajouter ou non une musique de fond à la vidéo générée." + }, + "control_after_generate": { + "name": "contrôle après génération" + }, + "duration": { + "name": "durée" + }, + "model": { + "name": "modèle" + }, + "prompt": { + "name": "invite", + "tooltip": "Une description textuelle pour la génération vidéo, longueur maximale de 2000 caractères." + }, + "resolution": { + "name": "résolution" + }, + "seed": { + "name": "graine" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "ViduImageToVideoNode": { "description": "Générer une vidéo à partir d'une image et d'un éventuel prompt", "display_name": "Génération vidéo Vidu à partir d'image", @@ -12580,6 +15149,11 @@ "voxel": { "name": "voxel" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VoxelToMeshBasic": { @@ -12591,6 +15165,11 @@ "voxel": { "name": "voxel" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Wan22FunControlToVideo": { @@ -12870,6 +15449,10 @@ "name": "pas de contexte", "tooltip": "La progression de la fenêtre contextuelle ; applicable uniquement aux échéanciers uniformes." }, + "freenoise": { + "name": "freenoise", + "tooltip": "Appliquer ou non le mélange de bruit FreeNoise, améliore la fusion des fenêtres." + }, "fuse_method": { "name": "méthode_de_fusion", "tooltip": "La méthode à utiliser pour fusionner les fenêtres contextuelles." @@ -13210,6 +15793,10 @@ "name": "graine", "tooltip": "Graine à utiliser pour la génération." }, + "shot_type": { + "name": "type de plan", + "tooltip": "Spécifie le type de plan pour la vidéo générée, c'est-à-dire si la vidéo est un plan continu unique ou plusieurs plans avec coupures. Ce paramètre n'est effectif que lorsque prompt_extend est True." + }, "watermark": { "name": "filigrane", "tooltip": "S'il faut ajouter un filigrane \"Généré par IA\" au résultat." @@ -13221,6 +15808,196 @@ } } }, + "WanInfiniteTalkToVideo": { + "display_name": "WanInfiniteTalkToVideo", + "inputs": { + "audio_encoder_output_1": { + "name": "sortie encodeur audio 1" + }, + "audio_scale": { + "name": "échelle audio" + }, + "clip_vision_output": { + "name": "sortie vision clip" + }, + "height": { + "name": "hauteur" + }, + "length": { + "name": "longueur" + }, + "mode": { + "name": "mode" + }, + "model": { + "name": "modèle" + }, + "model_patch": { + "name": "correctif du modèle" + }, + "motion_frame_count": { + "name": "nombre d’images de mouvement", + "tooltip": "Nombre d’images précédentes à utiliser comme contexte de mouvement." + }, + "negative": { + "name": "négatif" + }, + "positive": { + "name": "positif" + }, + "previous_frames": { + "name": "images précédentes" + }, + "start_image": { + "name": "image de départ" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largeur" + } + }, + "outputs": { + "0": { + "name": "modèle", + "tooltip": null + }, + "1": { + "name": "positif", + "tooltip": null + }, + "2": { + "name": "négatif", + "tooltip": null + }, + "3": { + "name": "latent", + "tooltip": null + }, + "4": { + "name": "image rognée", + "tooltip": null + } + } + }, + "WanMoveConcatTrack": { + "display_name": "WanMoveConcatTrack", + "inputs": { + "tracks_1": { + "name": "pistes_1" + }, + "tracks_2": { + "name": "pistes_2" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanMoveTrackToVideo": { + "display_name": "WanMoveTrackToVideo", + "inputs": { + "batch_size": { + "name": "taille_du_lot" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "height": { + "name": "hauteur" + }, + "length": { + "name": "longueur" + }, + "negative": { + "name": "négatif" + }, + "positive": { + "name": "positif" + }, + "start_image": { + "name": "image_de_départ" + }, + "strength": { + "name": "force", + "tooltip": "Intensité du conditionnement de la piste." + }, + "tracks": { + "name": "pistes" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largeur" + } + }, + "outputs": { + "0": { + "name": "positif", + "tooltip": null + }, + "1": { + "name": "négatif", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "WanMoveTracksFromCoords": { + "display_name": "WanMoveTracksFromCoords", + "inputs": { + "track_coords": { + "name": "coordonnées_piste" + }, + "track_mask": { + "name": "masque_piste" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "longueur_piste", + "tooltip": null + } + } + }, + "WanMoveVisualizeTracks": { + "display_name": "WanMoveVisualizeTracks", + "inputs": { + "circle_size": { + "name": "taille_du_cercle" + }, + "images": { + "name": "images" + }, + "line_resolution": { + "name": "résolution_de_ligne" + }, + "line_width": { + "name": "largeur_de_ligne" + }, + "opacity": { + "name": "opacité" + }, + "tracks": { + "name": "pistes" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WanPhantomSubjectToVideo": { "display_name": "WanPhantomSubjectToVideo", "inputs": { @@ -13268,6 +16045,51 @@ } } }, + "WanReferenceVideoApi": { + "description": "Utilisez le personnage et la voix des vidéos d'entrée, combinés à une invite, pour générer une nouvelle vidéo qui maintient la cohérence du personnage.", + "display_name": "Wan Reference to Video", + "inputs": { + "control_after_generate": { + "name": "contrôle_après_génération" + }, + "duration": { + "name": "durée" + }, + "model": { + "name": "modèle" + }, + "negative_prompt": { + "name": "invite_négative", + "tooltip": "Invite négative décrivant ce qu'il faut éviter." + }, + "prompt": { + "name": "invite", + "tooltip": "Invite décrivant les éléments et les caractéristiques visuelles. Prend en charge l'anglais et le chinois. Utilisez des identifiants tels que `character1` et `character2` pour faire référence aux personnages de référence." + }, + "reference_videos": { + "name": "vidéos_de_référence" + }, + "seed": { + "name": "graine" + }, + "shot_type": { + "name": "type_de_plan", + "tooltip": "Spécifie le type de plan pour la vidéo générée, c'est-à-dire si la vidéo est un plan continu unique ou plusieurs plans avec coupures." + }, + "size": { + "name": "taille" + }, + "watermark": { + "name": "filigrane", + "tooltip": "Indique s'il faut ajouter un filigrane généré par l'IA au résultat." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WanSoundImageToVideo": { "display_name": "WanSoundImageToVideo", "inputs": { @@ -13446,6 +16268,10 @@ "name": "seed", "tooltip": "Graine à utiliser pour la génération." }, + "shot_type": { + "name": "type_de_plan", + "tooltip": "Spécifie le type de plan pour la vidéo générée, c'est-à-dire si la vidéo est un plan continu unique ou plusieurs plans avec coupures. Ce paramètre prend effet uniquement lorsque prompt_extend est True." + }, "size": { "name": "taille" }, @@ -13571,6 +16397,43 @@ } } }, + "WavespeedFlashVSRNode": { + "description": "Upscaler vidéo rapide et de haute qualité qui augmente la résolution et restaure la clarté des séquences basse résolution ou floues.", + "display_name": "FlashVSR Upscale Vidéo", + "inputs": { + "target_resolution": { + "name": "résolution cible" + }, + "video": { + "name": "vidéo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WavespeedImageUpscaleNode": { + "description": "Augmentez la résolution et la qualité de l'image, en upscalant les photos en 4K ou 8K pour des résultats nets et détaillés.", + "display_name": "WaveSpeed Upscale Image", + "inputs": { + "image": { + "name": "image" + }, + "model": { + "name": "modèle" + }, + "target_resolution": { + "name": "résolution cible" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WebcamCapture": { "display_name": "Capture Webcam", "inputs": { @@ -13590,6 +16453,32 @@ } } }, + "ZImageFunControlnet": { + "display_name": "ZImageFunControlnet", + "inputs": { + "image": { + "name": "image" + }, + "inpaint_image": { + "name": "image_de_repeinture" + }, + "mask": { + "name": "mask" + }, + "model": { + "name": "modèle" + }, + "model_patch": { + "name": "modèle_patch" + }, + "strength": { + "name": "force" + }, + "vae": { + "name": "vae" + } + } + }, "unCLIPCheckpointLoader": { "display_name": "ChargeurPointContrôleunCLIP", "inputs": { @@ -13614,5 +16503,19 @@ "name": "force" } } + }, + "wanBlockSwap": { + "description": "NOP", + "display_name": "wanBlockSwap", + "inputs": { + "model": { + "name": "modèle" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } } } diff --git a/src/locales/fr/settings.json b/src/locales/fr/settings.json index ec17fde2b..422690985 100644 --- a/src/locales/fr/settings.json +++ b/src/locales/fr/settings.json @@ -29,12 +29,26 @@ "name": "Image de fond du canevas", "tooltip": "URL de l'image pour le fond du canevas. Vous pouvez faire un clic droit sur une image dans le panneau de sortie et sélectionner « Définir comme fond » pour l'utiliser." }, + "Comfy_Canvas_LeftMouseClickBehavior": { + "name": "Comportement du clic gauche de la souris", + "options": { + "Panning": "Défilement", + "Select": "Sélectionner" + } + }, + "Comfy_Canvas_MouseWheelScroll": { + "name": "Défilement de la molette de la souris", + "options": { + "Panning": "Défilement", + "Zoom in/out": "Zoom avant/arrière" + } + }, "Comfy_Canvas_NavigationMode": { "name": "Mode de navigation sur le canvas", "options": { + "Custom": "Personnalisé", "Drag Navigation": "Navigation par glisser-déposer", - "Standard (New)": "Standard (Nouveau)", - "Custom": "Personnalisé" + "Standard (New)": "Standard (Nouveau)" } }, "Comfy_Canvas_SelectionToolbox": { @@ -65,6 +79,17 @@ "Comfy_EnableWorkflowViewRestore": { "name": "Sauvegarder et restaurer la position et le niveau de zoom du canevas dans les flux de travail" }, + "Comfy_Execution_PreviewMethod": { + "name": "Méthode d’aperçu en direct", + "options": { + "auto": "auto", + "default": "default", + "latent2rgb": "latent2rgb", + "none": "aucun", + "taesd": "taesd" + }, + "tooltip": "Méthode d’aperçu en direct pendant la génération d’image. « default » utilise le paramètre CLI du serveur." + }, "Comfy_FloatRoundingPrecision": { "name": "Nombre de décimales pour l'arrondi du widget flottant [0 = auto].", "tooltip": "(nécessite le rechargement de la page)" @@ -86,6 +111,10 @@ "None": "Aucun" } }, + "Comfy_Graph_LiveSelection": { + "name": "Sélection en direct", + "tooltip": "Lorsqu’elle est activée, les nœuds sont sélectionnés/désélectionnés en temps réel pendant que vous faites glisser le rectangle de sélection, comme dans d’autres outils de conception." + }, "Comfy_Graph_ZoomSpeed": { "name": "Vitesse de zoom du canevas" }, @@ -152,6 +181,15 @@ "name": "Intensité lumineuse minimale", "tooltip": "Définit la valeur minimale autorisée de l’intensité lumineuse pour les scènes 3D. Cela définit la limite inférieure de luminosité pouvant être réglée lors de l’ajustement de l’éclairage dans tout widget 3D." }, + "Comfy_Load3D_PLYEngine": { + "name": "Moteur PLY", + "options": { + "fastply": "fastply", + "sparkjs": "sparkjs", + "threejs": "threejs" + }, + "tooltip": "Sélectionnez le moteur pour charger les fichiers PLY. « threejs » utilise le PLYLoader natif de Three.js (idéal pour les fichiers PLY de maillage). « fastply » utilise un chargeur optimisé pour les fichiers PLY de nuages de points ASCII. « sparkjs » utilise Spark.js pour les fichiers PLY de Gaussian Splatting 3D." + }, "Comfy_Load3D_ShowGrid": { "name": "Afficher la Grille", "tooltip": "Basculer pour afficher la grille par défaut" @@ -167,10 +205,6 @@ "name": "Verrouiller l'ajustement du pinceau sur l'axe dominant", "tooltip": "Lorsqu'il est activé, les ajustements du pinceau n'affecteront que la taille OU la dureté en fonction de la direction dans laquelle vous bougez le plus" }, - "Comfy_MaskEditor_UseNewEditor": { - "name": "Utiliser le nouvel éditeur de masque", - "tooltip": "Passer à la nouvelle interface de l'éditeur de masque" - }, "Comfy_ModelLibrary_AutoLoadAll": { "name": "Charger automatiquement tous les dossiers de modèles", "tooltip": "Si vrai, tous les dossiers seront chargés dès que vous ouvrez la bibliothèque de modèles (cela peut causer des retards pendant le chargement). Si faux, les dossiers de modèles de niveau racine ne seront chargés que lorsque vous cliquerez dessus." @@ -238,6 +272,10 @@ "Comfy_Node_AllowImageSizeDraw": { "name": "Afficher la largeur × la hauteur sous l'aperçu de l'image" }, + "Comfy_Node_AlwaysShowAdvancedWidgets": { + "name": "Toujours afficher les widgets avancés sur tous les nœuds", + "tooltip": "Lorsque cette option est activée, les widgets avancés sont toujours visibles sur tous les nœuds sans avoir besoin de les développer individuellement." + }, "Comfy_Node_AutoSnapLinkToSlot": { "name": "Lien d'ancrage automatique à l'emplacement du nœud", "tooltip": "Lorsque vous faites glisser un lien sur un nœud, le lien se fixe automatiquement à une fente d'entrée viable sur le nœud" @@ -298,6 +336,10 @@ "name": "Taille de l'historique de la file d'attente", "tooltip": "Le nombre maximum de tâches qui s'affichent dans l'historique de la file d'attente." }, + "Comfy_Queue_QPOV2": { + "name": "Utiliser la file d’attente unifiée dans le panneau latéral des ressources", + "tooltip": "Remplace le panneau flottant de la file d’attente des tâches par une file d’attente équivalente intégrée dans le panneau latéral des ressources. Vous pouvez désactiver cette option pour revenir à la disposition du panneau flottant." + }, "Comfy_Sidebar_Location": { "name": "Emplacement de la barre latérale", "options": { @@ -312,6 +354,13 @@ "small": "petit" } }, + "Comfy_Sidebar_Style": { + "name": "Style de la barre latérale", + "options": { + "connected": "Connectée", + "floating": "Flottante" + } + }, "Comfy_Sidebar_UnifiedWidth": { "name": "Largeur unifiée de la barre latérale" }, @@ -328,6 +377,14 @@ "Comfy_TreeExplorer_ItemPadding": { "name": "Espacement des éléments de l'explorateur d'arborescence" }, + "Comfy_UI_TabBarLayout": { + "name": "Disposition de la barre d’onglets", + "options": { + "Default": "Par défaut", + "Integrated": "Intégrée" + }, + "tooltip": "Contrôle la disposition de la barre d’onglets. « Intégrée » déplace les contrôles Aide et Utilisateur dans la zone de la barre d’onglets." + }, "Comfy_UseNewMenu": { "name": "Utiliser le nouveau menu", "options": { @@ -339,6 +396,14 @@ "Comfy_Validation_Workflows": { "name": "Valider les flux de travail" }, + "Comfy_VueNodes_AutoScaleLayout": { + "name": "Mise à l'échelle automatique de la mise en page (nœuds Vue)", + "tooltip": "Redimensionne automatiquement les positions des nœuds lors du passage au rendu Vue pour éviter les chevauchements" + }, + "Comfy_VueNodes_Enabled": { + "name": "Design moderne des nœuds (nœuds Vue)", + "tooltip": "Moderne : rendu basé sur DOM avec interactivité améliorée, fonctionnalités natives du navigateur et design visuel actualisé. Classique : rendu traditionnel sur toile." + }, "Comfy_WidgetControlMode": { "name": "Mode de contrôle du widget", "options": { @@ -376,6 +441,9 @@ "Comfy_Workflow_SortNodeIdOnSave": { "name": "Trier les ID de nœuds lors de l'enregistrement du flux de travail" }, + "Comfy_Workflow_WarnBlueprintOverwrite": { + "name": "Exiger une confirmation pour écraser un plan de sous-graphe existant" + }, "Comfy_Workflow_WorkflowTabsPosition": { "name": "Position des flux de travail ouverts", "options": { @@ -407,37 +475,5 @@ }, "pysssss_SnapToGrid": { "name": "Toujours aligner sur la grille" - }, - "Comfy_Canvas_LeftMouseClickBehavior": { - "name": "Comportement du clic gauche de la souris", - "options": { - "Panning": "Défilement", - "Select": "Sélectionner" - } - }, - "Comfy_Canvas_MouseWheelScroll": { - "name": "Défilement de la molette de la souris", - "options": { - "Panning": "Défilement", - "Zoom in/out": "Zoom avant/arrière" - } - }, - "Comfy_Sidebar_Style": { - "name": "Style de la barre latérale", - "options": { - "floating": "Flottante", - "connected": "Connectée" - } - }, - "Comfy_VueNodes_AutoScaleLayout": { - "name": "Mise à l'échelle automatique de la mise en page (nœuds Vue)", - "tooltip": "Redimensionne automatiquement les positions des nœuds lors du passage au rendu Vue pour éviter les chevauchements" - }, - "Comfy_VueNodes_Enabled": { - "name": "Design moderne des nœuds (nœuds Vue)", - "tooltip": "Moderne : rendu basé sur DOM avec interactivité améliorée, fonctionnalités natives du navigateur et design visuel actualisé. Classique : rendu traditionnel sur toile." - }, - "Comfy_Workflow_WarnBlueprintOverwrite": { - "name": "Exiger une confirmation pour écraser un plan de sous-graphe existant" } } diff --git a/src/locales/ja/commands.json b/src/locales/ja/commands.json index 243b2d944..9e3756663 100644 --- a/src/locales/ja/commands.json +++ b/src/locales/ja/commands.json @@ -1,4 +1,40 @@ { + "Comfy-Desktop_CheckForUpdates": { + "label": "アップデートを確認" + }, + "Comfy-Desktop_Folders_OpenCustomNodesFolder": { + "label": "カスタムノードフォルダーを開く" + }, + "Comfy-Desktop_Folders_OpenInputsFolder": { + "label": "入力フォルダーを開く" + }, + "Comfy-Desktop_Folders_OpenLogsFolder": { + "label": "ログフォルダーを開く" + }, + "Comfy-Desktop_Folders_OpenModelConfig": { + "label": "extra_model_paths.yaml を開く" + }, + "Comfy-Desktop_Folders_OpenModelsFolder": { + "label": "モデルフォルダーを開く" + }, + "Comfy-Desktop_Folders_OpenOutputsFolder": { + "label": "出力フォルダーを開く" + }, + "Comfy-Desktop_OpenDevTools": { + "label": "開発者ツールを開く" + }, + "Comfy-Desktop_OpenUserGuide": { + "label": "デスクトップユーザーガイド" + }, + "Comfy-Desktop_Quit": { + "label": "終了" + }, + "Comfy-Desktop_Reinstall": { + "label": "再インストール" + }, + "Comfy-Desktop_Restart": { + "label": "再起動" + }, "Comfy_3DViewer_Open3DViewer": { "label": "選択したノードの3Dビューアー(ベータ)を開く" }, @@ -155,18 +191,30 @@ "Comfy_Manager_ShowUpdateAvailablePacks": { "label": "更新を確認" }, - "Comfy_Manager_ToggleManagerProgressDialog": { - "label": "プログレスダイアログの切り替え" - }, "Comfy_MaskEditor_BrushSize_Decrease": { "label": "マスクエディタでブラシサイズを縮小" }, "Comfy_MaskEditor_BrushSize_Increase": { "label": "マスクエディタでブラシサイズを大きくする" }, + "Comfy_MaskEditor_ColorPicker": { + "label": "MaskEditorでカラーピッカーを開く" + }, + "Comfy_MaskEditor_Mirror_Horizontal": { + "label": "マスクエディタで左右反転" + }, + "Comfy_MaskEditor_Mirror_Vertical": { + "label": "マスクエディタで上下反転" + }, "Comfy_MaskEditor_OpenMaskEditor": { "label": "選択したノードのマスクエディタを開く" }, + "Comfy_MaskEditor_Rotate_Left": { + "label": "マスクエディタで左回転" + }, + "Comfy_MaskEditor_Rotate_Right": { + "label": "マスクエディタで右回転" + }, "Comfy_Memory_UnloadModels": { "label": "モデルのアンロード" }, @@ -197,12 +245,18 @@ "Comfy_QueueSelectedOutputNodes": { "label": "選択した出力ノードをキューに追加" }, + "Comfy_Queue_ToggleOverlay": { + "label": "ジョブ履歴を切り替え" + }, "Comfy_Redo": { "label": "やり直す" }, "Comfy_RefreshNodeDefinitions": { "label": "ノード定義を更新" }, + "Comfy_RenameWorkflow": { + "label": "ワークフローの名前を変更" + }, "Comfy_SaveWorkflow": { "label": "ワークフローを保存する" }, @@ -221,6 +275,12 @@ "Comfy_ToggleHelpCenter": { "label": "ヘルプセンター" }, + "Comfy_ToggleLinear": { + "label": "リニアモードを切り替え" + }, + "Comfy_ToggleQPOV2": { + "label": "Queue Panel V2 を切り替え" + }, "Comfy_ToggleTheme": { "label": "テーマの切り替え(ダーク/ライト)" }, diff --git a/src/locales/ja/main.json b/src/locales/ja/main.json index 2d9868d01..22568c7f2 100644 --- a/src/locales/ja/main.json +++ b/src/locales/ja/main.json @@ -1,6 +1,8 @@ { "actionbar": { - "dockToTop": "上部にドッキング" + "dockToTop": "上部にドッキング", + "feedback": "フィードバック", + "feedbackTooltip": "フィードバック" }, "apiNodesCostBreakdown": { "costPerRun": "実行あたりのコスト", @@ -18,23 +20,141 @@ "assetCard": "{name} - {type} アセット", "loadingAsset": "アセットを読み込み中" }, + "assetCollection": "アセットコレクション", "assets": "アセット", "baseModels": "ベースモデル", "browseAssets": "アセットを閲覧", + "byType": "タイプ別", + "checkpoints": "チェックポイント", + "civitaiLinkExample": "{example} {link}", + "civitaiLinkExampleStrong": "例:", + "civitaiLinkExampleUrl": "https://civitai.com/models/10706/luisap-z-image-and-qwen-pixel-art-refiner?modelVersionId=2225295", + "civitaiLinkLabel": "Civitaiモデル{download}リンク", + "civitaiLinkLabelDownload": "ダウンロード", + "civitaiLinkPlaceholder": "ここにリンクを貼り付けてください", + "confirmModelDetails": "モデル詳細を確認", "connectionError": "接続を確認して再試行してください", + "deletion": { + "body": "このモデルはライブラリから完全に削除されます。", + "complete": "{assetName}が削除されました。", + "failed": "{assetName}を削除できませんでした。", + "header": "このモデルを削除しますか?", + "inProgress": "{assetName}を削除中..." + }, + "download": { + "complete": "ダウンロード完了", + "failed": "ダウンロードに失敗しました", + "inProgress": "{assetName}をダウンロード中..." + }, + "emptyImported": { + "canImport": "まだインポートされたモデルはありません。「モデルをインポート」をクリックして追加してください。", + "restricted": "パーソナルモデルはCreator以上のプランでのみ利用可能です。" + }, + "errorFileTooLarge": "ファイルが許可された最大サイズを超えています", + "errorFormatNotAllowed": "SafeTensor形式のみ許可されています", + "errorModelTypeNotSupported": "このモデルタイプはサポートされていません", + "errorUnknown": "予期しないエラーが発生しました", + "errorUnsafePickleScan": "CivitAIがこのファイルに潜在的な危険なコードを検出しました", + "errorUnsafeVirusScan": "CivitAIがこのファイルにマルウェアまたは疑わしい内容を検出しました", + "errorUploadFailed": "アセットのインポートに失敗しました。もう一度お試しください。", "failedToCreateNode": "ノードの作成に失敗しました。再試行するか、詳細はコンソールをご確認ください。", "fileFormats": "ファイル形式", + "fileName": "ファイル名", + "fileSize": "ファイルサイズ", + "filterBy": "フィルター", + "findInLibrary": "モデルライブラリの{type}セクションで見つけることができます。", + "finish": "完了", + "genericLinkPlaceholder": "ここにリンクを貼り付けてください", + "importAnother": "別のファイルをインポート", + "imported": "インポート済み", + "jobId": "ジョブID", "loadingModels": "{type}を読み込み中...", + "maxFileSize": "最大ファイルサイズ:{size}", + "maxFileSizeValue": "1 GB", + "media": { + "audioPlaceholder": "オーディオ", + "threeDModelPlaceholder": "3Dモデル" + }, + "modelAssociatedWithLink": "ご提供いただいたリンクに関連付けられているモデル:", + "modelInfo": { + "addBaseModel": "ベースモデルを追加...", + "addTag": "タグを追加...", + "additionalTags": "追加タグ", + "baseModelUnknown": "ベースモデル不明", + "basicInfo": "基本情報", + "compatibleBaseModels": "互換性のあるベースモデル", + "description": "説明", + "descriptionNotSet": "説明が設定されていません", + "descriptionPlaceholder": "このモデルの説明を追加...", + "displayName": "表示名", + "editDisplayName": "表示名を編集", + "fileName": "ファイル名", + "modelDescription": "モデル説明", + "modelTagging": "モデルタグ付け", + "modelType": "モデルタイプ", + "noAdditionalTags": "追加タグなし", + "selectModelPrompt": "モデルを選択して情報を表示してください", + "selectModelType": "モデルタイプを選択...", + "source": "ソース", + "title": "モデル情報", + "triggerPhrases": "トリガーフレーズ", + "viewOnSource": "{source} で表示" + }, + "modelName": "モデル名", + "modelNamePlaceholder": "このモデルの名前を入力してください", + "modelTypeSelectorLabel": "モデルの種類は何ですか?", + "modelTypeSelectorPlaceholder": "モデルタイプを選択", + "modelUploaded": "モデルが正常にインポートされました。", "noAssetsFound": "アセットが見つかりません", "noModelsInFolder": "このフォルダには{type}がありません", - "searchAssetsPlaceholder": "アセットを検索...", + "noValidSourceDetected": "有効なインポート元が検出されませんでした", + "notSureLeaveAsIs": "分からない場合はそのままにしてください", + "onlyCivitaiUrlsSupported": "CivitaiのURLのみサポートされています", + "ownership": "所有権", + "ownershipAll": "すべて", + "ownershipMyModels": "自分のモデル", + "ownershipPublicModels": "公開モデル", + "processingModel": "ダウンロード開始", + "processingModelDescription": "このダイアログを閉じてもダウンロードはバックグラウンドで続行されます。", + "providerCivitai": "Civitai", + "providerHuggingFace": "Hugging Face", + "rename": { + "failed": "アセットの名前を変更できませんでした。" + }, + "selectFrameworks": "フレームワークを選択", + "selectModelType": "モデルタイプを選択", + "selectProjects": "プロジェクトを選択", "sortAZ": "A-Z", "sortBy": "並び替え", "sortPopular": "人気", "sortRecent": "最新", "sortZA": "Z-A", + "sortingType": "並び替えタイプ", + "tags": "タグ", + "tagsHelp": "タグはカンマで区切ってください", + "tagsPlaceholder": "例:models, checkpoint", "tryAdjustingFilters": "検索やフィルターを調整してみてください", - "unknown": "不明" + "unknown": "不明", + "unsupportedUrlSource": "{sources}からのURLのみサポートされています", + "upgradeFeatureDescription": "この機能はCreatorまたはProプランでのみご利用いただけます。", + "upgradeToUnlockFeature": "アップグレードしてこの機能を利用する", + "upload": "インポート", + "uploadFailed": "インポートに失敗しました", + "uploadModel": "インポート", + "uploadModelDescription1": "Civitaiモデルのダウンロードリンクを貼り付けてライブラリに追加してください。", + "uploadModelDescription1Generic": "モデルのダウンロードリンクを貼り付けてライブラリに追加してください。", + "uploadModelDescription2": "現在サポートされているのは{link}からのリンクのみです", + "uploadModelDescription2Generic": "現在サポートされているプロバイダーのURLのみ利用可能です:", + "uploadModelDescription2Link": "https://civitai.com/models", + "uploadModelDescription3": "最大ファイルサイズ:{size}", + "uploadModelFailedToRetrieveMetadata": "メタデータの取得に失敗しました。リンクを確認して再度お試しください。", + "uploadModelFromCivitai": "Civitaiからモデルをインポート", + "uploadModelGeneric": "モデルをインポート", + "uploadModelHelpFooterText": "URLの探し方が分からない場合は、下記のプロバイダーをクリックして説明動画をご覧ください。", + "uploadModelHelpVideo": "モデルアップロードヘルプ動画", + "uploadModelHowDoIFindThis": "どこで見つけられますか?", + "uploadSuccess": "モデルが正常にインポートされました!", + "uploadingModel": "モデルをインポート中..." }, "auth": { "apiKey": { @@ -148,12 +268,19 @@ "title": "アカウントを作成する" } }, + "boundingBox": { + "height": "高さ", + "width": "幅", + "x": "X", + "y": "Y" + }, "breadcrumbsMenu": { "clearWorkflow": "ワークフローをクリア", "deleteBlueprint": "ブループリントを削除", "deleteWorkflow": "ワークフローを削除", "duplicate": "複製", - "enterNewName": "新しい名前を入力" + "enterNewName": "新しい名前を入力", + "missingNodesWarning": "ワークフローに未対応のノードが含まれています(赤でハイライト)。" }, "clipboard": { "errorMessage": "クリップボードへのコピーに失敗しました", @@ -207,6 +334,7 @@ }, "retry": "再試行", "retrying": "再試行中...", + "skipToCloudApp": "クラウドアプリへスキップ", "start": { "desc": "セットアップ不要。あらゆるデバイスで動作します。", "download": "ComfyUIをダウンロード", @@ -340,6 +468,8 @@ "Edit Subgraph Widgets": "サブグラフウィジェットを編集", "Expand": "展開", "Expand Node": "ノードを展開", + "Extensions": "拡張機能", + "FavoriteWidget": "ウィジェットをお気に入りに追加", "Horizontal": "水平", "Inputs": "入力", "Left": "左", @@ -359,6 +489,7 @@ "Remove": "削除", "Remove Bypass": "バイパスを解除", "Rename": "名前を変更", + "RenameWidget": "ウィジェット名を変更", "Resize": "リサイズ", "Right": "右", "Run Branch": "ブランチを実行", @@ -369,6 +500,7 @@ "Shapes": "形", "Title": "タイトル", "Top": "上", + "UnfavoriteWidget": "ウィジェットのお気に入りを解除", "Unpack Subgraph": "サブグラフを展開", "Unpin": "ピンを解除", "Vertical": "垂直", @@ -382,6 +514,7 @@ "additionalInfo": "追加情報", "apiPricing": "API料金", "credits": "クレジット", + "creditsAvailable": "利用可能なクレジット", "details": "詳細", "eventType": "イベントタイプ", "faqs": "よくある質問", @@ -390,15 +523,46 @@ "messageSupport": "サポートにメッセージ", "model": "モデル", "purchaseCredits": "クレジットを購入", + "refreshes": "{date}にリフレッシュ", "time": "時間", "topUp": { + "addMoreCredits": "クレジットを追加", + "addMoreCreditsToRun": "実行するためにクレジットを追加", + "amountToPayLabel": "支払う金額(ドル)", + "buy": "購入", + "buyCredits": "支払いに進む", "buyNow": "今すぐ購入", + "contactUs": "お問い合わせ", + "creditsDescription": "クレジットはワークフローやパートナーノードの実行に使用されます。", + "creditsPerDollar": "1ドルあたりのクレジット数", + "creditsToReceiveLabel": "受け取るクレジット数", + "howManyCredits": "いくつクレジットを追加しますか?", "insufficientMessage": "このワークフローを実行するのに十分なクレジットがありません。", "insufficientTitle": "クレジット不足", + "insufficientWorkflowMessage": "このワークフローを実行するためのクレジットが足りません。", + "maxAllowed": "最大{credits}クレジットまで。", "maxAmount": "(最大 $1,000 USD)", + "maximumAmount": "最大:${amount}", + "minRequired": "最低{credits}クレジット", + "minimumPurchase": "最低購入額:${amount}({credits}クレジット)", + "needMore": "さらに必要ですか?", + "purchaseError": "購入に失敗しました", + "purchaseErrorDetail": "クレジットの購入に失敗しました: {error}", "quickPurchase": "クイック購入", "seeDetails": "詳細を見る", - "topUp": "チャージ" + "selectAmount": "金額を選択", + "templateNote": "*Wan Fun Control テンプレートで生成", + "topUp": "チャージ", + "unknownError": "不明なエラーが発生しました", + "usdAmount": "${amount}", + "videosEstimate": "約{count}本の動画", + "viewPricing": "料金詳細を見る", + "youGet": "クレジット", + "youPay": "支払金額(USD)" + }, + "unified": { + "message": "クレジットが統合されました", + "tooltip": "Comfy全体で支払いが統一されました。すべてComfyクレジットで動作します:\n- パートナーノード(旧APIノード)\n- クラウドワークフロー\n\n既存のパートナーノード残高はクレジットに変換されました。" }, "yourCreditBalance": "あなたのクレジット残高" }, @@ -414,6 +578,9 @@ "CLIP_VISION": "CLIP_VISION", "CLIP_VISION_OUTPUT": "CLIP_VISION_OUTPUT", "COMBO": "コンボ", + "COMFY_AUTOGROW_V3": "COMFY_AUTOGROW_V3", + "COMFY_DYNAMICCOMBO_V3": "COMFY_DYNAMICCOMBO_V3", + "COMFY_MATCHTYPE_V3": "COMFY_MATCHTYPE_V3", "CONDITIONING": "条件付け", "CONTROL_NET": "コントロールネット", "FLOAT": "浮動小数点", @@ -424,18 +591,21 @@ "HOOKS": "フック", "HOOK_KEYFRAMES": "フックキーフレーム", "IMAGE": "画像", + "IMAGECOMPARE": "画像比較", "INT": "整数", "LATENT": "潜在", "LATENT_OPERATION": "潜在操作", + "LATENT_UPSCALE_MODEL": "LATENT_UPSCALE_MODEL", "LOAD3D_CAMERA": "3Dカメラの読み込み", "LOAD_3D": "3Dをロード", - "LOAD_3D_ANIMATION": "3Dアニメーションをロード", "LORA_MODEL": "LoRAモデル", "LOSS_MAP": "損失マップ", "LUMA_CONCEPTS": "Lumaコンセプト", "LUMA_REF": "Luma参照", "MASK": "マスク", "MESH": "メッシュ", + "MESHY_RIGGED_TASK_ID": "MESHY_RIGGED_TASK_ID", + "MESHY_TASK_ID": "MESHY_TASK_ID", "MODEL": "モデル", "MODEL_PATCH": "モデルパッチ", "MODEL_TASK_ID": "モデルタスクID", @@ -455,6 +625,7 @@ "STYLE_MODEL": "スタイルモデル", "SVG": "SVG", "TIMESTEPS_RANGE": "タイムステップの範囲", + "TRACKS": "TRACKS", "UPSCALE_MODEL": "アップスケールモデル", "VAE": "VAE", "VIDEO": "ビデオ", @@ -523,14 +694,17 @@ "amount": "量", "apply": "適用する", "architecture": "アーキテクチャ", + "asset": "{count} 個のアセット | {count} 個のアセット | {count} 個のアセット", "audioFailedToLoad": "オーディオの読み込みに失敗しました", "audioProgress": "オーディオの進捗", "author": "作者", "back": "戻る", + "batchRename": "一括リネーム", "beta": "ベータ版", "bookmark": "ライブラリに保存", "calculatingDimensions": "寸法を計算中", "cancel": "キャンセル", + "cancelled": "キャンセル済み", "capture": "キャプチャ", "category": "カテゴリ", "chart": "チャート", @@ -540,6 +714,7 @@ "clearAll": "すべてクリア", "clearFilters": "フィルターをクリア", "close": "閉じる", + "closeDialog": "ダイアログを閉じる", "color": "色", "comfy": "Comfy", "comfyOrgLogoAlt": "ComfyOrgロゴ", @@ -556,13 +731,17 @@ "control_before_generate": "生成前の制御", "copied": "コピーしました", "copy": "コピー", + "copyAll": "すべてコピー", "copyJobId": "ジョブIDをコピー", "copyToClipboard": "クリップボードにコピー", "copyURL": "URLをコピー", + "core": "コア", "currentUser": "現在のユーザー", + "custom": "カスタム", "customBackground": "カスタム背景", "customize": "カスタマイズ", "customizeFolder": "フォルダーをカスタマイズ", + "decrement": "減少", "defaultBanner": "デフォルトバナー", "delete": "削除", "deleteAudioFile": "オーディオファイルを削除", @@ -571,27 +750,35 @@ "description": "説明", "devices": "デバイス", "disableAll": "すべて無効にする", + "disableSelected": "選択したものを無効化", + "disableThirdParty": "サードパーティを無効化", "disabling": "無効化", "dismiss": "閉じる", "download": "ダウンロード", "downloadImage": "画像をダウンロード", "downloadVideo": "ビデオをダウンロード", + "downloading": "ダウンロード中", "dropYourFileOr": "ファイルをドロップするか", "duplicate": "複製", "edit": "編集", "editImage": "画像を編集", "editOrMaskImage": "画像を編集またはマスク", + "emDash": "—", "empty": "空", "enableAll": "すべて有効にする", "enableOrDisablePack": "パックを有効/無効にする", + "enableSelected": "選択したものを有効化", "enabled": "有効", "enabling": "有効化", + "enterBaseName": "ベース名を入力", + "enterNewName": "新しい名前を入力", "error": "エラー", "errorLoadingImage": "画像の読み込みエラー", "errorLoadingVideo": "ビデオの読み込みエラー", "experimental": "ベータ", "export": "エクスポート", "extensionName": "拡張機能名", + "failed": "失敗", "failedToCopyJobId": "ジョブIDのコピーに失敗しました", "failedToDownloadImage": "画像のダウンロードに失敗しました", "failedToDownloadVideo": "ビデオのダウンロードに失敗しました", @@ -607,12 +794,15 @@ "goToNode": "ノードに移動", "graphNavigation": "グラフナビゲーション", "halfSpeed": "0.5倍速", + "hideLeftPanel": "左パネルを非表示", + "hideRightPanel": "右パネルを非表示", "icon": "アイコン", "imageFailedToLoad": "画像の読み込みに失敗しました", "imagePreview": "画像プレビュー - 矢印キーで画像を切り替え", "imageUrl": "画像URL", "import": "インポート", "inProgress": "進行中", + "increment": "増加", "info": "ノード情報", "insert": "挿入", "install": "インストール", @@ -620,7 +810,9 @@ "installing": "インストール中", "interrupted": "中断されました", "itemSelected": "{selectedCount}件選択済み", + "itemsCopiedToClipboard": "項目をクリップボードにコピーしました", "itemsSelected": "{selectedCount}件選択済み", + "job": "ジョブ", "jobIdCopied": "ジョブIDがクリップボードにコピーされました", "keybinding": "キーバインディング", "keybindingAlreadyExists": "このキー割り当てはすでに存在します", @@ -638,14 +830,18 @@ "micPermissionDenied": "マイクの許可が拒否されました", "migrate": "移行する", "missing": "不足している", + "more": "もっと見る", "moreOptions": "その他のオプション", "moreWorkflows": "さらに多くのワークフロー", "multiSelectDropdown": "複数選択ドロップダウン", "name": "名前", "newFolder": "新しいフォルダー", "next": "次へ", + "nightly": "NIGHTLY", "no": "いいえ", "noAudioRecorded": "音声が録音されていません", + "noItems": "項目がありません", + "noResults": "結果なし", "noResultsFound": "結果が見つかりません", "noTasksFound": "タスクが見つかりません", "noTasksFoundMessage": "キューにタスクがありません。", @@ -656,26 +852,45 @@ "nodeSlotsError": "ノードスロットエラー", "nodeWidgetsError": "ノードウィジェットエラー", "nodes": "ノード", + "nodesCount": "{count} ノード | {count} ノード | {count} ノード", "nodesRunning": "ノードが実行中", "none": "なし", + "nothingToCopy": "コピーするものがありません", + "nothingToDelete": "削除するものがありません", + "nothingToDuplicate": "複製するものがありません", + "nothingToRename": "リネームするものがありません", "ok": "OK", "openManager": "マネージャーを開く", "openNewIssue": "新しい問題を開く", + "or": "または", "overwrite": "上書き", + "playPause": "再生/一時停止", "playRecording": "録音を再生", "playbackSpeed": "再生速度", "playing": "再生中", "pressKeysForNewBinding": "新しいバインドのキーを押してください", "preview": "プレビュー", + "profile": "プロフィール", "progressCountOf": "の", + "queued": "キュー中", "ready": "準備完了", "reconnected": "再接続されました", "reconnecting": "再接続中", "refresh": "更新", "refreshNode": "ノードを更新", + "relativeTime": { + "daysAgo": "{count}日前", + "hoursAgo": "{count}時間前", + "minutesAgo": "{count}分前", + "monthsAgo": "{count}か月前", + "now": "今", + "weeksAgo": "{count}週間前", + "yearsAgo": "{count}年前" + }, "releaseTitle": "{package} {version} リリース", "reloadToApplyChanges": "変更を適用するには再読み込みしてください", "removeImage": "画像を削除", + "removeTag": "タグを削除", "removeVideo": "ビデオを削除", "rename": "名前を変更", "reportIssue": "報告する", @@ -690,21 +905,31 @@ "resizeFromTopRight": "右上隅からリサイズ", "restart": "再起動", "resultsCount": "{count}件の結果が見つかりました", + "running": "実行中", "save": "保存", "saving": "保存中", + "scrollLeft": "左にスクロール", + "scrollRight": "右にスクロール", "search": "検索", "searchExtensions": "拡張機能を検索", "searchFailedMessage": "検索に一致する設定が見つかりませんでした。検索キーワードを調整してみてください。", "searchKeybindings": "キーバインディングを検索", "searchModels": "モデルを検索", "searchNodes": "ノードを検索", + "searchPlaceholder": "検索...", "searchSettings": "設定を検索", "searchWorkflows": "ワークフローを検索", "seeTutorial": "チュートリアルを見る", + "selectItemsToCopy": "コピーする項目を選択", + "selectItemsToDelete": "削除する項目を選択", + "selectItemsToDuplicate": "複製する項目を選択", + "selectItemsToRename": "リネームする項目を選択", "selectedFile": "選択されたファイル", "setAsBackground": "背景として設定", "settings": "設定", + "showLeftPanel": "左パネルを表示", "showReport": "レポートを表示", + "showRightPanel": "右パネルを表示", "singleSelectDropdown": "単一選択ドロップダウン", "sort": "並び替え", "source": "ソース", @@ -712,12 +937,14 @@ "status": "ステータス", "stopPlayback": "再生を停止", "stopRecording": "録音停止", + "submit": "送信", "success": "成功", "systemInfo": "システム情報", "terminal": "ターミナル", "title": "タイトル", "triggerPhrase": "トリガーフレーズ", "unknownError": "不明なエラー", + "untitled": "無題", "update": "更新", "updateAvailable": "更新が利用可能", "updateFrontend": "フロントエンドを更新", @@ -725,6 +952,7 @@ "updating": "更新中", "upload": "アップロード", "usageHint": "使用ヒント", + "use": "使用", "user": "ユーザー", "versionMismatchWarning": "バージョン互換性の警告", "versionMismatchWarningMessage": "{warning}: {detail} 更新手順については https://docs.comfy.org/installation/update_comfyui#common-update-issues をご覧ください。", @@ -732,11 +960,10 @@ "videoPreview": "ビデオプレビュー - 矢印キーでビデオを切り替え", "viewImageOfTotal": "画像 {index} / {total} を表示", "viewVideoOfTotal": "ビデオ {index} / {total} を表示", - "vitePreloadErrorMessage": "アプリの新しいバージョンがリリースされました。再読み込みしますか?\n再読み込みしない場合、アプリの一部が正しく動作しない可能性があります。\n再読み込み前に進行状況を保存してから拒否することもできます。", - "vitePreloadErrorTitle": "新しいバージョンが利用可能", "volume": "音量", "warning": "警告", - "workflow": "ワークフロー" + "workflow": "ワークフロー", + "you": "あなた" }, "graphCanvasMenu": { "fitView": "ビューに合わせる", @@ -758,12 +985,17 @@ "create": "グループノードを作成", "enterName": "名前を入力" }, + "help": { + "helpCenterMenu": "ヘルプセンターメニュー", + "recentReleases": "最近のリリース" + }, "helpCenter": { "clickToLearnMore": "詳しくはこちらをクリック →", "desktopUserGuide": "デスクトップユーザーガイド", "docs": "ドキュメント", + "feedback": "フィードバックを送信", "github": "Github", - "helpFeedback": "ヘルプとフィードバック", + "help": "ヘルプ&サポート", "loadingReleases": "リリースを読み込み中...", "managerExtension": "Manager Extension", "more": "もっと見る...", @@ -772,6 +1004,12 @@ "recentReleases": "最近のリリース", "reinstall": "再インストール", "updateAvailable": "アップデート", + "updateComfyUI": "ComfyUIをアップデート", + "updateComfyUIFailed": "ComfyUIのアップデートに失敗しました。もう一度お試しください。", + "updateComfyUIStarted": "アップデート開始", + "updateComfyUIStartedDetail": "ComfyUIのアップデートがキューに追加されました。しばらくお待ちください…", + "updateComfyUISuccess": "アップデート完了", + "updateComfyUISuccessDetail": "ComfyUIがアップデートされました。再起動中…", "whatsNew": "新着情報" }, "icon": { @@ -785,6 +1023,18 @@ "inbox": "受信トレイ", "star": "星" }, + "imageCompare": { + "noImages": "比較する画像がありません" + }, + "imageCrop": { + "cropPreviewAlt": "切り抜きプレビュー", + "loading": "読み込み中...", + "noInputImage": "入力画像が接続されていません" + }, + "importFailed": { + "copyError": "コピーエラー", + "title": "インポート失敗" + }, "install": { "appDataLocationTooltip": "ComfyUIのアプリデータディレクトリ。保存内容:\n- ログ\n- サーバー設定", "appPathLocationTooltip": "ComfyUIのアプリ資産ディレクトリ。ComfyUIのコードとアセットを保存します", @@ -798,6 +1048,8 @@ "failedToSelectDirectory": "ディレクトリの選択に失敗しました", "gpu": "GPU", "gpuPicker": { + "amdDescription": "AMD GPUをROCm™アクセラレーションで使用し、最高のパフォーマンスを実現します。", + "amdSubtitle": "AMD ROCm™", "appleMetalDescription": "MacのGPUを活用して、より高速かつ快適な体験を実現します", "cpuDescription": "GPUアクセラレーションが利用できない場合は互換性のためにCPUモードを使用します", "cpuSubtitle": "CPUモード", @@ -824,6 +1076,8 @@ "selectGpuDescription": "所有しているGPUのタイプを選択してください" }, "helpImprove": "ComfyUIの改善にご協力ください", + "insideAppInstallDir": "このフォルダーはComfyUI Desktopアプリケーションバンドル内にあり、アップデート時に削除されます。インストールフォルダー外(例:Documents/ComfyUI)を選択してください。", + "insideUpdaterCache": "このフォルダーはComfyUIアップデーターのキャッシュ内にあり、アップデートごとに消去されます。データ用に別の場所を選択してください。", "installLocation": "インストール先", "installLocationDescription": "ComfyUIのユーザーデータを保存するディレクトリを選択してください。Python環境が選択した場所にインストールされます。選択したディスクに約15GBの空き容量が必要です。", "installLocationTooltip": "ComfyUIのユーザーデータディレクトリ。保存内容:\n- Python環境\n- モデル\n- カスタムノード\n", @@ -898,6 +1152,16 @@ "issueReport": { "helpFix": "これを修正するのを助ける" }, + "linearMode": { + "beta": "ベータ版 - フィードバックを送る", + "downloadAll": "すべてダウンロード", + "dragAndDropImage": "画像をドラッグ&ドロップ", + "graphMode": "グラフモード", + "linearMode": "シンプルモード", + "rerun": "再実行", + "reuseParameters": "パラメータを再利用", + "runCount": "実行回数:" + }, "load3d": { "applyingTexture": "テクスチャを適用中...", "backgroundColor": "背景色", @@ -924,20 +1188,24 @@ "lineart": "線画", "normal": "ノーマル", "original": "オリジナル", + "pointCloud": "ポイントクラウド", "wireframe": "ワイヤーフレーム" }, "model": "モデル", "openIn3DViewer": "3Dビューアで開く", + "panoramaMode": "パノラマ", "previewOutput": "出力のプレビュー", "reloadingModel": "モデルを再読み込み中...", "removeBackgroundImage": "背景画像を削除", "resizeNodeMatchOutput": "ノードを出力に合わせてリサイズ", "scene": "シーン", "showGrid": "グリッドを表示", + "showSkeleton": "スケルトンを表示", "startRecording": "録画開始", "stopRecording": "録画停止", "switchCamera": "カメラを切り替える", "switchingMaterialMode": "マテリアルモードの切り替え中...", + "tiledMode": "タイル", "unsupportedFileType": "サポートされていないファイル形式です(.gltf、.glb、.obj、.fbx、.stl をサポート)", "upDirection": "上方向", "upDirections": { @@ -958,6 +1226,11 @@ "title": "3Dビューア(ベータ)" } }, + "loadWorkflowWarning": { + "coreNodesFromVersion": "バージョン{version}のコアノード:", + "outdatedVersion": "このワークフローは新しいバージョンのComfyUI({version})で作成されました。一部のノードが正しく動作しない場合があります。", + "outdatedVersionGeneric": "このワークフローは新しいバージョンのComfyUIで作成されました。一部のノードが正しく動作しない場合があります。" + }, "maintenance": { "None": "なし", "OK": "OK", @@ -976,7 +1249,15 @@ "showManual": "メンテナンスタスクを表示", "status": "ステータス", "terminalDefaultMessage": "トラブルシューティングコマンドを実行すると、出力はここに表示されます。", - "title": "メンテナンス" + "title": "メンテナンス", + "unsafeMigration": { + "action": "下記の「ベースパス」メンテナンスタスクを使用して、ComfyUIを安全な場所に移動してください。", + "appInstallDir": "ベースパスがComfyUI Desktopアプリケーションバンドル内にあります。このフォルダーはアップデート時に削除または上書きされる可能性があります。Documents/ComfyUIなど、インストールフォルダー外のディレクトリを選択してください。", + "generic": "現在のComfyUIのベースパスは、アップデート時に削除または変更される可能性のある場所にあります。データ損失を防ぐため、安全なフォルダーに移動してください。", + "oneDrive": "ベースパスがOneDrive上にあり、同期の問題やデータ損失が発生する可能性があります。OneDriveで管理されていないローカルフォルダーを選択してください。", + "title": "安全でないインストール場所が検出されました", + "updaterCache": "ベースパスがComfyUIアップデーターキャッシュ内にあります。これは各アップデート時にクリアされます。データ用に別の場所を選択してください。" + } }, "manager": { "allMissingNodesInstalled": "すべての不足しているノードが正常にインストールされました", @@ -1077,6 +1358,8 @@ "totalNodes": "合計ノード数", "tryAgainLater": "後ほど再試行してください。", "tryDifferentSearch": "別の検索クエリを試してみてください。", + "tryUpdate": "アップデートを試す", + "tryUpdateTooltip": "リポジトリから最新の変更を取得します。ナイトリーバージョンでは自動検出できないアップデートがある場合があります。", "uninstall": "アンインストール", "uninstallSelected": "選択したものをアンインストール", "uninstalling": "アンインストール中", @@ -1087,31 +1370,110 @@ "version": "バージョン" }, "maskEditor": { + "activateLayer": "レイヤーを有効化", + "applyToWholeImage": "画像全体に適用", + "baseImageLayer": "ベース画像レイヤー", + "baseLayerPreview": "ベースレイヤープレビュー", + "black": "黒", + "brushSettings": "ブラシ設定", + "brushShape": "ブラシ形状", + "clear": "クリア", + "clickToResetZoom": "クリックしてズームをリセット", + "colorSelectSettings": "色選択設定", + "colorSelector": "カラーピッカー", + "fillOpacity": "塗りつぶし不透明度", + "hardness": "硬さ", + "imageLayer": "画像レイヤー", + "invert": "反転", + "layers": "レイヤー", + "livePreview": "ライブプレビュー", + "maskBlendingOptions": "マスク合成オプション", + "maskLayer": "マスクレイヤー", + "maskOpacity": "マスク不透明度", + "maskTolerance": "マスク許容値", + "method": "方法", + "mirrorHorizontal": "左右反転", + "mirrorVertical": "上下反転", + "negative": "ネガティブ", + "opacity": "不透明度", + "paintBucketSettings": "ペイントバケツ設定", + "paintLayer": "ペイントレイヤー", + "redo": "やり直し", + "resetToDefault": "デフォルトにリセット", + "rotateLeft": "左に回転", + "rotateRight": "右に回転", + "selectionOpacity": "選択範囲の不透明度", + "smoothingPrecision": "スムージング精度", + "stepSize": "ステップサイズ", + "stopAtMask": "マスクで停止", + "thickness": "太さ", + "title": "マスクエディター", + "tolerance": "許容値", + "undo": "元に戻す", + "white": "白" }, "mediaAsset": { + "actions": { + "copyJobId": "ジョブIDをコピー", + "delete": "削除", + "download": "ダウンロード", + "exportWorkflow": "ワークフローをエクスポート", + "insertAsNodeInWorkflow": "ワークフローにノードとして挿入", + "inspect": "アセットを確認", + "more": "その他のオプション", + "moreOptions": "その他のオプション", + "openWorkflow": "新しいタブでワークフローとして開く", + "seeMoreOutputs": "さらに出力を表示", + "zoom": "拡大" + }, "assetDeletedSuccessfully": "アセットが正常に削除されました", "deleteAssetDescription": "このアセットは完全に削除されます。", "deleteAssetTitle": "このアセットを削除しますか?", "deleteSelectedDescription": "{count} 個のアセットが完全に削除されます。", "deleteSelectedTitle": "選択したアセットを削除しますか?", "deletingImportedFilesCloudOnly": "インポートしたファイルの削除はクラウド版でのみサポートされています", + "failedToCreateNode": "ノードの作成に失敗しました", "failedToDeleteAsset": "アセットの削除に失敗しました", + "failedToExportWorkflow": "ワークフローのエクスポートに失敗しました", "jobIdToast": { "copied": "コピーしました", "error": "エラー", "jobIdCopied": "ジョブIDをクリップボードにコピーしました", "jobIdCopyFailed": "ジョブIDのコピーに失敗しました" }, + "noJobIdFound": "このアセットにジョブIDが見つかりません", + "noWorkflowDataFound": "このアセットにワークフローデータが見つかりません", + "nodeAddedToWorkflow": "{nodeType}ノードがワークフローに追加されました", + "nodeTypeNotFound": "ノードタイプ {nodeType} が見つかりません", "selection": { "assetsDeletedSuccessfully": "{count} 個のアセットが正常に削除されました", "deleteSelected": "削除", + "deleteSelectedAll": "すべて削除", "deselectAll": "すべて選択解除", "downloadSelected": "ダウンロード", + "downloadSelectedAll": "すべてダウンロード", "downloadStarted": "{count} ファイルをダウンロード中...", "downloadsStarted": "{count} ファイルのダウンロードを開始しました", + "exportWorkflowAll": "すべてのワークフローをエクスポート", + "failedToAddNodes": "ノードのワークフローへの追加に失敗しました", "failedToDeleteAssets": "選択したアセットの削除に失敗しました", - "selectedCount": "選択されたアセット: {count}" - } + "insertAllAssetsAsNodes": "すべてのアセットをノードとして挿入", + "multipleSelectedAssets": "複数のアセットが選択されています", + "noWorkflowsFound": "選択したアセットにワークフローデータが見つかりません", + "noWorkflowsToExport": "エクスポートするワークフローデータが見つかりません", + "nodesAddedToWorkflow": "{count} 個のノードがワークフローに追加されました", + "openWorkflowAll": "すべてのワークフローを開く", + "partialAddNodesSuccess": "{succeeded} 件が正常に追加され、{failed} 件が失敗しました", + "partialDeleteSuccess": "{succeeded}件の削除に成功、{failed}件の削除に失敗しました", + "partialWorkflowsExported": "{succeeded} 件が正常にエクスポートされ、{failed} 件が失敗しました", + "partialWorkflowsOpened": "{succeeded} 件のワークフローが開かれ、{failed} 件が失敗しました", + "selectedCount": "選択されたアセット: {count}", + "workflowsExported": "{count} 件のワークフローが正常にエクスポートされました", + "workflowsOpened": "{count} 件のワークフローが新しいタブで開かれました" + }, + "unsupportedFileType": "ローダーノードでサポートされていないファイルタイプです", + "workflowExportedSuccessfully": "ワークフローのエクスポートに成功しました", + "workflowOpenedInNewTab": "ワークフローが新しいタブで開かれました" }, "menu": { "autoQueue": "自動キュー", @@ -1119,11 +1481,13 @@ "batchCountTooltip": "ワークフロー生成回数", "clear": "ワークフローをクリア", "clipspace": "クリップスペースを開く", + "customNodesManager": "カスタムノードマネージャー", "dark": "ダーク", "disabled": "無効", "disabledTooltip": "ワークフローは自動的にキューに追加されません", "execute": "実行", "help": "ヘルプ", + "helpAndFeedback": "ヘルプとフィードバック", "hideMenu": "メニューを隠す", "instant": "即時", "instantTooltip": "生成完了後すぐにキューに追加", @@ -1137,6 +1501,7 @@ "resetView": "ビューをリセット", "run": "実行する", "runWorkflow": "ワークフローを実行する (Shiftで先頭にキュー)", + "runWorkflowDisabled": "ワークフローに未対応のノード(赤でハイライト)が含まれています。これらを削除してワークフローを実行してください。", "runWorkflowFront": "ワークフローを実行する (先頭にキュー)", "settings": "設定", "showMenu": "メニューを表示", @@ -1152,6 +1517,7 @@ "Canvas Performance": "キャンバスのパフォーマンス", "Canvas Toggle Lock": "キャンバスのロックを切り替え", "Check for Custom Node Updates": "カスタムノードのアップデートを確認", + "Check for Updates": "アップデートを確認", "Clear Pending Tasks": "保留中のタスクをクリア", "Clear Workflow": "ワークフローをクリア", "Clipspace": "クリップスペース", @@ -1168,13 +1534,14 @@ "Custom Nodes Manager": "カスタムノードマネージャ", "Decrease Brush Size in MaskEditor": "マスクエディタでブラシサイズを小さくする", "Delete Selected Items": "選択したアイテムを削除", + "Desktop User Guide": "デスクトップユーザーガイド", "Duplicate Current Workflow": "現在のワークフローを複製", "Edit": "編集", "Edit Subgraph Widgets": "サブグラフウィジェットを編集", "Exit Subgraph": "サブグラフを終了", "Experimental: Browse Model Assets": "実験的: モデルアセットを閲覧", "Experimental: Enable AssetAPI": "実験的: AssetAPIを有効化", - "Experimental: Enable Vue Nodes": "実験的: Vueノードを有効化", + "Experimental: Enable Nodes 2_0": "実験的: Nodes 2.0を有効化", "Export": "エクスポート", "Export (API)": "エクスポート (API)", "File": "ファイル", @@ -1186,12 +1553,15 @@ "Increase Brush Size in MaskEditor": "マスクエディタでブラシサイズを大きくする", "Install Missing Custom Nodes": "不足しているカスタムノードをインストール", "Interrupt": "中断", + "Job History": "ジョブ履歴", "Load Default Workflow": "デフォルトワークフローを読み込む", "Lock Canvas": "キャンバスをロック", "Manage group nodes": "グループノードを管理", "Manager": "マネージャー", "Manager Menu (Legacy)": "マネージャーメニュー(レガシー)", "Minimap": "ミニマップ", + "Mirror Horizontal in MaskEditor": "マスクエディタで左右反転", + "Mirror Vertical in MaskEditor": "マスクエディタで上下反転", "Model Library": "モデルライブラリ", "Move Selected Nodes Down": "選択したノードを下へ移動", "Move Selected Nodes Left": "選択したノードを左へ移動", @@ -1204,8 +1574,16 @@ "Node Links": "ノードリンク", "Open": "開く", "Open 3D Viewer (Beta) for Selected Node": "選択したノードの3Dビューア(ベータ版)を開く", + "Open Color Picker in MaskEditor": "MaskEditorでカラーピッカーを開く", + "Open Custom Nodes Folder": "カスタムノードフォルダーを開く", + "Open DevTools": "開発者ツールを開く", + "Open Inputs Folder": "入力フォルダーを開く", + "Open Logs Folder": "ログフォルダーを開く", "Open Mask Editor for Selected Node": "選択したノードのマスクエディタを開く", + "Open Models Folder": "モデルフォルダーを開く", + "Open Outputs Folder": "出力フォルダーを開く", "Open Sign In Dialog": "サインインダイアログを開く", + "Open extra_model_paths_yaml": "extra_model_paths.yaml を開く", "Pin/Unpin Selected Items": "選択したアイテムのピン留め/ピン留め解除", "Pin/Unpin Selected Nodes": "選択したノードのピン留め/ピン留め解除", "Previous Opened Workflow": "前に開いたワークフロー", @@ -1213,10 +1591,16 @@ "Queue Prompt": "キューのプロンプト", "Queue Prompt (Front)": "キューのプロンプト (前面)", "Queue Selected Output Nodes": "選択した出力ノードをキューに追加", + "Quit": "終了", "Redo": "やり直す", "Refresh Node Definitions": "ノード定義を更新", + "Reinstall": "再インストール", + "Rename": "名前を変更", "Reset View": "ビューをリセット", "Resize Selected Nodes": "選択したノードのサイズ変更", + "Restart": "再起動", + "Rotate Left in MaskEditor": "マスクエディタで左に回転", + "Rotate Right in MaskEditor": "マスクエディタで右に回転", "Save": "保存", "Save As": "名前を付けて保存", "Show Keybindings Dialog": "キーバインドダイアログを表示", @@ -1225,12 +1609,13 @@ "Sign Out": "サインアウト", "Toggle Essential Bottom Panel": "エッセンシャル下部パネルの切り替え", "Toggle Logs Bottom Panel": "ログ下部パネルの切り替え", + "Toggle Queue Panel V2": "キューパネルV2を切り替え", "Toggle Search Box": "検索ボックスの切り替え", + "Toggle Simple Mode": "シンプルモードを切り替え", "Toggle Terminal Bottom Panel": "ターミナル下部パネルの切り替え", "Toggle Theme (Dark/Light)": "テーマを切り替え(ダーク/ライト)", "Toggle View Controls Bottom Panel": "ビューコントロール下部パネルの切り替え", "Toggle promotion of hovered widget": "ホバー中のウィジェットの昇格を切り替え", - "Toggle the Custom Nodes Manager Progress Bar": "カスタムノードマネージャーの進行状況バーを切り替え", "Undo": "元に戻す", "Ungroup selected group nodes": "選択したグループノードのグループ解除", "Unload Models": "モデルのアンロード", @@ -1255,30 +1640,56 @@ "missingModels": "モデルが見つかりません", "missingModelsMessage": "グラフを読み込む際に、次のモデルが見つかりませんでした" }, + "missingNodes": { + "cloud": { + "description": "このワークフローはCloudバージョンでまだサポートされていないカスタムノードを使用しています。", + "gotIt": "了解", + "learnMore": "詳細はこちら", + "priorityMessage": "これらのノードは自動的にフラグ付けされ、優先的に追加されるようにしています。", + "replacementInstruction": "その間、これらのノード(キャンバス上で赤く表示)をサポートされているノードに置き換えるか、別のワークフローをお試しください。", + "title": "これらのノードはComfy Cloudではまだ利用できません" + }, + "oss": { + "description": "このワークフローは、まだインストールされていないカスタムノードを使用しています。", + "replacementInstruction": "これらのノードをインストールするか、インストール済みの代替ノードに置き換えてください。不足しているノードはキャンバス上で赤く表示されます。", + "title": "このワークフローには不足しているノードがあります" + } + }, + "nightly": { + "badge": { + "label": "プレビュー版", + "tooltip": "現在、ComfyUI のナイトリーバージョンを使用しています。これらの機能についてご意見があれば、フィードバックボタンからお知らせください。" + } + }, "nodeCategories": { + "": "", "3d": "3d", "3d_models": "3Dモデル", "BFL": "BFL", + "Bria": "Bria", "ByteDance": "ByteDance", "Gemini": "Gemini", "Ideogram": "Ideogram", "Kling": "Kling", "LTXV": "LTXV", "Luma": "Luma", + "Meshy": "Meshy", "MiniMax": "MiniMax", "Moonvalley Marey": "Moonvalley Marey", "OpenAI": "OpenAI", - "Pika": "Pika", "PixVerse": "PixVerse", "Recraft": "Recraft", "Rodin": "Rodin", "Runway": "Runway", "Sora": "Sora", "Stability AI": "Stability AI", + "Tencent": "Tencent", + "Topaz": "Topaz", "Tripo": "Tripo", "Veo": "Veo", "Vidu": "Vidu", "Wan": "Wan", + "WaveSpeed": "WaveSpeed", "_for_testing": "_テスト用", "advanced": "高度な機能", "animation": "アニメーション", @@ -1299,6 +1710,7 @@ "controlnet": "コントロールネット", "create": "作成", "custom_sampling": "カスタムサンプリング", + "dataset": "データセット", "debug": "デバッグ", "deprecated": "非推奨", "edit_models": "モデル編集", @@ -1310,8 +1722,10 @@ "image": "画像", "inpaint": "インペイント", "instructpix2pix": "インストラクションピクス2ピクス", + "kandinsky5": "kandinsky5", "latent": "潜在", "loaders": "ローダー", + "logic": "ロジック", "lotus": "lotus", "ltxv": "LTXV", "mask": "マスク", @@ -1345,7 +1759,15 @@ "upscaling": "アップスケーリング", "utils": "ユーティリティ", "video": "ビデオ", - "video_models": "ビデオモデル" + "video_models": "ビデオモデル", + "zimage": "zimage" + }, + "nodeErrors": { + "content": "ノードコンテンツエラー", + "header": "ノードヘッダーエラー", + "render": "ノードレンダリングエラー", + "slots": "ノードスロットエラー", + "widgets": "ノードウィジェットエラー" }, "nodeHelpPage": { "documentationPage": "ドキュメントページ", @@ -1362,6 +1784,7 @@ "notSupported": { "continue": "続ける", "continueTooltip": "私のデバイスはサポートされていると確信しています", + "illustrationAlt": "悲しそうな女の子のイラスト", "learnMore": "詳細を見る", "message": "以下のデバイスのみサポートされています:", "reportIssue": "問題を報告", @@ -1371,12 +1794,136 @@ }, "title": "お使いのデバイスはサポートされていません" }, + "progressToast": { + "allDownloadsCompleted": "すべてのダウンロードが完了しました", + "downloadingModel": "モデルをダウンロード中...", + "downloadsFailed": "{count}件のダウンロードに失敗 | {count}件のダウンロードに失敗 | {count}件のダウンロードに失敗", + "failed": "失敗", + "filter": { + "all": "すべて", + "completed": "完了", + "failed": "失敗" + }, + "finished": "完了", + "importingModels": "モデルをインポート中", + "noImportsInQueue": "キューに{filter}はありません", + "pending": "保留中", + "progressCount": "{total}件中{completed}件" + }, + "queue": { + "completedIn": "{duration}で完了", + "inQueue": "キューに追加中...", + "initializingAlmostReady": "初期化中 - まもなく準備完了", + "jobAddedToQueue": "ジョブがキューに追加されました", + "jobDetails": { + "computeHoursUsed": "使用計算時間", + "errorMessage": "エラーメッセージ", + "estimatedFinishIn": "終了予想時間", + "estimatedStartIn": "開始予想時間", + "eta": { + "minutes": "約{count}分 | 約{count}分", + "minutesRange": "約{lo}~{hi}分", + "seconds": "約{count}秒 | 約{count}秒", + "secondsRange": "約{lo}~{hi}秒" + }, + "failedAfter": "失敗までの時間", + "generatedOn": "生成日時", + "header": "ジョブ詳細", + "jobId": "ジョブID", + "queuePosition": "キュー位置", + "queuePositionValue": "あなたの前に約{count}件のジョブがあります | あなたの前に約{count}件のジョブがあります", + "queuedAt": "キュー追加日時", + "report": "報告", + "timeElapsed": "経過時間", + "totalGenerationTime": "生成時間合計", + "workflow": "ワークフロー" + }, + "jobHistory": "ジョブ履歴", + "jobList": { + "sortComputeHoursUsed": "使用計算時間(多い順)", + "sortMostRecent": "最新順", + "sortTotalGenerationTime": "生成時間合計(長い順)", + "undated": "日付なし" + }, + "jobMenu": { + "addToCurrentWorkflow": "現在のワークフローに追加", + "cancelJob": "ジョブをキャンセル", + "copyErrorMessage": "エラーメッセージをコピー", + "copyJobId": "ジョブIDをコピー", + "delete": "削除", + "deleteAsset": "アセットを削除", + "download": "ダウンロード", + "exportWorkflow": "ワークフローをエクスポート", + "inspectAsset": "アセットを確認", + "openAsWorkflowNewTab": "ワークフローとして新しいタブで開く", + "openWorkflowNewTab": "ワークフローを新しいタブで開く", + "removeJob": "ジョブを削除", + "reportError": "エラーを報告" + }, + "toggleJobHistory": "ジョブ履歴を切り替え" + }, "releaseToast": { + "description": "このアップデートの最新の改善点と機能をご覧ください。", "newVersionAvailable": "新しいバージョンが利用可能です!", "skip": "スキップ", "update": "アップデート", "whatsNew": "新機能" }, + "rightSidePanel": { + "addFavorite": "お気に入りに追加", + "advancedInputs": "詳細入力", + "bypass": "バイパス", + "color": "ノードカラー", + "fallbackGroupTitle": "グループ", + "fallbackNodeTitle": "ノード", + "favorites": "お気に入り入力", + "favoritesNone": "お気に入り入力なし", + "favoritesNoneDesc": "お気に入りに追加した入力がここに表示されます", + "favoritesNoneTooltip": "ウィジェットにスターを付けると、ノードを選択せずにすぐアクセスできます", + "globalSettings": { + "canvas": "キャンバス", + "connectionLinks": "接続リンク", + "gridSpacing": "グリッド間隔", + "linkShape": "リンクの形状", + "nodes": "ノード", + "nodes2": "ノード 2.0", + "searchPlaceholder": "クイック設定を検索...", + "showAdvanced": "詳細パラメータを表示", + "showAdvancedTooltip": "この設定をTRUEにすると、ノードのすべての詳細パラメータが表示されます", + "showConnectedLinks": "接続されたリンクを表示", + "showInfoBadges": "情報バッジを表示", + "showToolbox": "選択時にツールボックスを表示", + "snapNodesToGrid": "ノードをグリッドにスナップ", + "title": "グローバル設定", + "viewAllSettings": "すべての設定を表示" + }, + "groupSettings": "グループ設定", + "groups": "グループ", + "hideAdvancedInputsButton": "詳細入力を非表示", + "hideInput": "入力を非表示", + "info": "情報", + "inputs": "入力", + "inputsNone": "入力なし", + "inputsNoneTooltip": "このノードには入力がありません", + "locateNode": "キャンバス上でノードを探す", + "mute": "ミュート", + "noSelection": "ノードを選択すると、そのプロパティと情報が表示されます。", + "nodeState": "ノードの状態", + "nodes": "ノード", + "nodesNoneDesc": "ノードなし", + "noneSearchDesc": "検索条件に一致する項目がありません", + "normal": "ノーマル", + "parameters": "パラメータ", + "pinned": "ピン留め", + "properties": "プロパティ", + "removeFavorite": "お気に入りを解除", + "settings": "設定", + "showAdvancedInputsButton": "詳細入力を表示", + "showInput": "入力を表示", + "title": "ノードが選択されていません | 1件のノードが選択されています | {count}件のノードが選択されています", + "togglePanel": "プロパティパネルの切り替え", + "workflowOverview": "ワークフロー概要" + }, "selectionToolbox": { "Bypass Group Nodes": "グループノードをバイパス", "Set Group Nodes to Always": "グループノードを常に有効にする", @@ -1389,6 +1936,8 @@ "serverConfig": { "modifiedConfigs": "以下のサーバー設定を変更しました。変更を適用するには再起動してください。", "restart": "再起動", + "restartRequiredToastDetail": "サーバー設定の変更を適用するにはアプリを再起動してください。", + "restartRequiredToastSummary": "再起動が必要です", "revertChanges": "変更を元に戻す" }, "serverConfigCategories": { @@ -1457,6 +2006,10 @@ "enable-cors-header": { "name": "CORSヘッダーを有効にする: \"*\"を使用してすべてのオリジンを許可するか、ドメインを指定する" }, + "enable-manager-legacy-ui": { + "name": "レガシーManager UIを使用", + "tooltip": "新しいUIの代わりに従来のComfyUI-Manager UIを使用します。" + }, "fast": { "name": "未テストの潜在的に品質を低下させる最適化を有効にする。" }, @@ -1558,6 +2111,7 @@ "CustomColorPalettes": "カスタムカラーパレット", "DevMode": "開発モード", "EditTokenWeight": "トークンの重みを編集", + "Execution": "実行", "Extension": "拡張", "General": "一般", "Graph": "グラフ", @@ -1572,12 +2126,14 @@ "Mask Editor": "マスクエディタ", "Menu": "メニュー", "ModelLibrary": "モデルライブラリ", - "NewEditor": "新しいエディタ", "Node": "ノード", "Node Search Box": "ノード検索ボックス", "Node Widget": "ノードウィジェット", "NodeLibrary": "ノードライブラリ", + "Nodes 2_0": "Nodes 2.0", "Notification Preferences": "通知設定", + "Other": "その他", + "PLY": "PLY", "PlanCredits": "プランとクレジット", "Pointer": "ポインタ", "Queue": "キュー", @@ -1596,7 +2152,8 @@ "Vue Nodes": "Vueノード", "VueNodes": "Vueノード", "Window": "ウィンドウ", - "Workflow": "ワークフロー" + "Workflow": "ワークフロー", + "Workspace": "ワークスペース" }, "shape": { "CARD": "カード", @@ -1622,11 +2179,14 @@ "viewControls": "ビューコントロール" }, "sideToolbar": { + "activeJobStatus": "アクティブジョブ: {status}", "assets": "アセット", "backToAssets": "すべてのアセットに戻る", "browseTemplates": "サンプルテンプレートを表示", "downloads": "ダウンロード", + "generatedAssetsHeader": "生成されたアセット", "helpCenter": "ヘルプセンター", + "importedAssetsHeader": "インポート済みアセット", "labels": { "assets": "アセット", "console": "コンソール", @@ -1669,6 +2229,47 @@ }, "openWorkflow": "ローカルでワークフローを開く", "queue": "キュー", + "queueProgressOverlay": { + "activeJobs": "{count}件のアクティブジョブ", + "activeJobsShort": "{count} 件のアクティブ | {count} 件のアクティブ", + "activeJobsSuffix": "アクティブジョブ", + "cancelJobTooltip": "ジョブをキャンセル", + "clearHistory": "ジョブキュー履歴をクリア", + "clearHistoryDialogAssetsNote": "これらのジョブで生成されたアセットは削除されず、アセットパネルからいつでも表示できます。", + "clearHistoryDialogDescription": "以下の完了または失敗したジョブは、このジョブキューパネルから削除されます。", + "clearHistoryDialogTitle": "ジョブキュー履歴をクリアしますか?", + "clearQueueTooltip": "キューをクリア", + "clearQueued": "キュー済みをクリア", + "colonPercent": ": {percent}", + "currentNode": "現在のノード:", + "expandCollapsedQueue": "ジョブキューを展開", + "filterAllWorkflows": "すべてのワークフロー", + "filterBy": "フィルター条件", + "filterCurrentWorkflow": "現在のワークフロー", + "filterJobs": "ジョブをフィルター", + "interruptAll": "すべての実行中ジョブを中断", + "jobQueue": "ジョブキュー", + "jobsCompleted": "{count}件のジョブが完了", + "jobsFailed": "{count}件のジョブが失敗", + "moreOptions": "その他のオプション", + "noActiveJobs": "アクティブなジョブはありません", + "preview": "プレビュー", + "queuedSuffix": "キュー済み", + "running": "実行中", + "showAssets": "アセットを表示", + "showAssetsPanel": "アセットパネルを表示", + "sortBy": "並べ替え条件", + "sortJobs": "ジョブを並べ替え", + "stubClipTextEncode": "CLIPテキストエンコード:", + "title": "キュー進行状況", + "total": "合計: {percent}", + "viewAllJobs": "すべてのジョブを見る", + "viewGrid": "グリッド表示", + "viewJobHistory": "ジョブ履歴を見る", + "viewList": "リスト表示" + }, + "searchAssets": "アセットを検索", + "sidebar": "サイドバー", "templates": "テンプレート", "themeToggle": "テーマを切り替え", "workflowTab": { @@ -1711,24 +2312,58 @@ "subscription": { "addApiCredits": "APIクレジットを追加", "addCredits": "クレジットを追加", + "addCreditsLabel": "いつでもクレジット追加可能", "benefits": { "benefit1": "パートナーノード用月間クレジット — 必要に応じて追加購入可能", "benefit2": "ジョブあたり最大30分の実行時間" }, "beta": "ベータ版", + "billedMonthly": "毎月請求", + "billedYearly": "{total} 年間請求", + "billingComingSoon": { + "message": "チーム向けの請求機能が近日中に追加されます。ワークスペースごとに席数単位でプランに加入できるようになります。今後のアップデートをお待ちください。", + "title": "近日公開" + }, + "cancelSubscription": "サブスクリプションをキャンセル", + "changeTo": "{plan}に変更", "comfyCloud": "Comfy Cloud", + "comfyCloudLogo": "Comfy Cloud ロゴ", + "contactOwnerToSubscribe": "サブスクリプションのためにワークスペースのオーナーに連絡してください", + "contactUs": "お問い合わせ", + "creditsRemainingThisMonth": "今月残りのクレジット", + "creditsRemainingThisYear": "今年残りのクレジット", + "creditsYouveAdded": "追加したクレジット", + "currentPlan": "現在のプラン", + "customLoRAsLabel": "独自のLoRAをインポート", + "description": "あなたに最適なプランを選択してください", "expiresDate": "{date} に期限切れ", + "gpuLabel": "RTX 6000 Pro(96GB VRAM)", + "haveQuestions": "ご質問やエンタープライズについてのお問い合わせはこちら", "invoiceHistory": "請求履歴", "learnMore": "詳細を見る", + "managePayment": "支払いを管理", + "managePlan": "プランを管理", "manageSubscription": "サブスクリプションを管理", + "maxDuration": { + "creator": "30分", + "founder": "30分", + "pro": "1時間", + "standard": "30分" + }, + "maxDurationLabel": "各ワークフロー実行の最大時間", "messageSupport": "サポートに連絡", + "monthly": "月額", "monthlyBonusDescription": "月間クレジットボーナス", + "monthlyCreditsInfo": "これらのクレジットは毎月リフレッシュされ、繰り越しはできません", + "monthlyCreditsLabel": "月間クレジット", "monthlyCreditsRollover": "これらのクレジットは翌月に繰り越されます", + "mostPopular": "最も人気", "nextBillingCycle": "次の請求サイクル", "partnerNodesBalance": "\"パートナーノード\" クレジット残高", "partnerNodesCredits": "パートナーノードクレジット", "partnerNodesDescription": "商用/独自モデルの実行用", "perMonth": "USD / 月", + "plansAndPricing": "プランと価格", "prepaidCreditsInfo": "別途購入した有効期限のないクレジット", "prepaidDescription": "プリペイドクレジット", "renewsDate": "{date} に更新", @@ -1738,14 +2373,46 @@ "waitingForSubscription": "新しいタブで購読を完了してください。完了を自動的に検知します!" }, "subscribeNow": "今すぐ購読", + "subscribeTo": "{plan}に登録", "subscribeToComfyCloud": "Comfy Cloudを購読", "subscribeToRun": "購読する", "subscribeToRunFull": "実行を購読", + "subscriptionRequiredMessage": "クラウドでワークフローを実行するにはメンバーにサブスクリプションが必要です", + "tierNameYearly": "{name} 年間", + "tiers": { + "creator": { + "name": "クリエイター" + }, + "founder": { + "name": "ファウンダーエディション" + }, + "pro": { + "name": "プロ" + }, + "standard": { + "name": "スタンダード" + } + }, "title": "サブスクリプション", "titleUnsubscribed": "Comfy Cloudにサブスクライブ", "totalCredits": "総クレジット", + "upgrade": "アップグレード", + "upgradePlan": "プランをアップグレード", + "upgradeTo": "{plan}にアップグレード", + "usdPerMonth": "USD / 月", + "videoEstimateExplanation": "これらの見積もりは、Wan 2.2 画像から動画テンプレートのデフォルト設定(5秒、640x640、16fps、4ステップサンプリング)に基づいています。", + "videoEstimateHelp": "このテンプレートの詳細", + "videoEstimateLabel": "Wan 2.2 画像から動画テンプレートで生成される約5秒動画数", + "videoEstimateTryTemplate": "このテンプレートを試す", + "videoTemplateBasedCredits": "Wan 2.2 画像から動画で生成された動画", + "viewEnterprise": "エンタープライズを見る", "viewMoreDetails": "詳細を表示", + "viewMoreDetailsPlans": "プランと価格の詳細を見る", "viewUsageHistory": "利用履歴を表示", + "workspaceNotSubscribed": "このワークスペースはサブスクリプションに加入していません", + "yearly": "年額", + "yearlyCreditsLabel": "年間合計クレジット", + "yearlyDiscount": "20%割引", "yourPlanIncludes": "ご利用プランに含まれるもの:" }, "tabMenu": { @@ -1757,8 +2424,14 @@ "duplicateTab": "タブを複製", "removeFromBookmarks": "ブックマークから削除" }, + "templateWidgets": { + "sort": { + "searchPlaceholder": "検索..." + } + }, "templateWorkflows": { "activeFilters": "フィルター:", + "allTemplates": "すべてのテンプレート", "categories": "カテゴリ", "category": { "3D": "3D", @@ -1785,6 +2458,7 @@ "error": { "templateNotFound": "テンプレート「{templateName}」が見つかりません" }, + "licenseFilter": "ライセンス", "loading": "テンプレートを読み込み中...", "loadingMore": "さらにテンプレートを読み込み中...", "modelFilter": "モデルフィルター", @@ -1801,12 +2475,14 @@ "default": "デフォルト", "modelSizeLowToHigh": "モデルサイズ(小さい順)", "newest": "新着順", + "popular": "人気", "recommended": "おすすめ", "searchPlaceholder": "検索...", "vramLowToHigh": "VRAM使用量(少ない順)" }, "sorting": "並び替え", "title": "テンプレートを利用して開始", + "useCaseFilter": "タスク", "useCasesSelected": "{count}件のユースケース" }, "toastMessages": { @@ -1835,9 +2511,22 @@ "failedToLoadModel": "3Dモデルの読み込みに失敗しました", "failedToPurchaseCredits": "クレジットの購入に失敗しました: {error}", "failedToQueue": "キューに追加できませんでした", + "failedToToggleCamera": "カメラの切り替えに失敗しました", + "failedToToggleGrid": "グリッドの切り替えに失敗しました", + "failedToUpdateBackgroundColor": "背景色の更新に失敗しました", + "failedToUpdateBackgroundImage": "背景画像の更新に失敗しました", + "failedToUpdateBackgroundRenderMode": "背景レンダーモードを{mode}に更新できませんでした", + "failedToUpdateEdgeThreshold": "エッジしきい値の更新に失敗しました", + "failedToUpdateFOV": "視野角の更新に失敗しました", + "failedToUpdateLightIntensity": "光の強度の更新に失敗しました", + "failedToUpdateMaterialMode": "マテリアルモードの更新に失敗しました", + "failedToUpdateUpDirection": "上方向の更新に失敗しました", + "failedToUploadBackgroundImage": "背景画像のアップロードに失敗しました", "fileLoadError": "{fileName}でワークフローが見つかりません", + "fileTooLarge": "ファイルが大きすぎます({size} MB)。サポートされている最大サイズは{maxSize} MBです", "fileUploadFailed": "ファイルのアップロードに失敗しました", "interrupted": "実行が中断されました", + "legacyMaskEditorDeprecated": "従来のマスクエディタは非推奨となり、まもなく削除されます。", "migrateToLitegraphReroute": "将来のバージョンではRerouteノードが削除されます。litegraph-native rerouteに移行するにはクリックしてください。", "modelLoadedSuccessfully": "3Dモデルが正常に読み込まれました", "no3dScene": "テクスチャを適用する3Dシーンがありません", @@ -1864,12 +2553,14 @@ "selectUser": "ユーザーを選択" }, "userSettings": { + "accountSettings": "アカウント設定", "email": "メールアドレス", "name": "名前", "notSet": "未設定", "provider": "サインイン方法", "title": "ユーザー設定", - "updatePassword": "パスワードを更新" + "updatePassword": "パスワードを更新", + "workspaceSettings": "ワークスペース設定" }, "validation": { "descriptionRequired": "説明が必要です", @@ -1898,22 +2589,32 @@ "updateFrontend": "フロントエンドを更新" }, "vueNodesBanner": { - "message": "ノードの外観と操作性が新しくなりました", + "desc": "– より柔軟なワークフロー、強力な新ウィジェット、拡張性を重視", + "title": "Nodes 2.0 のご紹介", "tryItOut": "試してみる" }, "vueNodesMigration": { "button": "設定を開く", "message": "クラシックなノードデザインをお好みですか?" }, + "vueNodesMigrationMainMenu": { + "message": "メインメニューからいつでもNodes 2.0に戻せます。" + }, "welcome": { "getStarted": "はじめる", "title": "ComfyUIへようこそ" }, "whatsNewPopup": { + "later": "後で", "learnMore": "詳細はこちら", "noReleaseNotes": "リリースノートはありません。" }, + "widgetFileUpload": { + "browseFiles": "ファイルを選択", + "dropPrompt": "ファイルをドロップするか" + }, "widgets": { + "node2only": "Node 2.0専用", "selectModel": "モデルを選択", "uploadSelect": { "placeholder": "選択...", @@ -1922,6 +2623,26 @@ "placeholderModel": "モデルを選択...", "placeholderUnknown": "メディアを選択...", "placeholderVideo": "動画を選択..." + }, + "valueControl": { + "decrement": "値を減少", + "decrementDesc": "値から1を引くか、前のオプションを選択します", + "editSettings": "制御設定を編集", + "fixed": "固定値", + "fixedDesc": "値を変更しません", + "header": { + "after": "後", + "before": "前", + "postfix": "ワークフローの実行:", + "prefix": "値を自動的に更新" + }, + "increment": "値を増加", + "incrementDesc": "値に1を加えるか、次のオプションを選択します", + "linkToGlobal": "リンク先", + "linkToGlobalDesc": "グローバル値の制御設定にリンクされたユニークな値", + "linkToGlobalSeed": "グローバル値", + "randomize": "値をランダム化", + "randomizeDesc": "各生成後に値をランダムにシャッフルします" } }, "workflowService": { @@ -1929,6 +2650,146 @@ "exportWorkflow": "ワークフローをエクスポート", "saveWorkflow": "ワークフローを保存" }, + "workspace": { + "addedToWorkspace": "{workspaceName}に追加されました", + "inviteAccepted": "招待を承諾しました", + "inviteFailed": "招待の承諾に失敗しました", + "unsavedChanges": { + "message": "未保存の変更があります。破棄してワークスペースを切り替えますか?", + "title": "未保存の変更" + } + }, + "workspaceAuth": { + "errors": { + "accessDenied": "このワークスペースへのアクセス権がありません", + "invalidFirebaseToken": "認証に失敗しました。もう一度ログインしてください。", + "notAuthenticated": "ワークスペースにアクセスするにはログインが必要です", + "tokenExchangeFailed": "ワークスペースの認証に失敗しました: {error}", + "workspaceNotFound": "ワークスペースが見つかりません" + } + }, + "workspacePanel": { + "createWorkspaceDialog": { + "create": "作成", + "message": "ワークスペースはメンバーでクレジットプールを共有できます。作成後、あなたがオーナーになります。", + "nameLabel": "ワークスペース名*", + "namePlaceholder": "ワークスペース名を入力", + "title": "新しいワークスペースを作成" + }, + "dashboard": { + "placeholder": "ダッシュボードのワークスペース設定" + }, + "deleteDialog": { + "message": "未使用のクレジットや保存されていないアセットは失われます。この操作は元に戻せません。", + "messageWithName": "「{name}」を削除しますか?未使用のクレジットや保存されていないアセットは失われます。この操作は元に戻せません。", + "title": "このワークスペースを削除しますか?" + }, + "editWorkspaceDialog": { + "nameLabel": "ワークスペース名", + "save": "保存", + "title": "ワークスペースの詳細を編集" + }, + "invite": "招待", + "inviteLimitReached": "メンバーの上限50人に達しました", + "inviteMember": "メンバーを招待", + "inviteMemberDialog": { + "createLink": "リンクを作成", + "linkCopied": "コピーしました", + "linkCopyFailed": "コピーに失敗しました", + "linkStep": { + "copyLink": "リンクをコピー", + "done": "完了", + "message": "相手のアカウントがこのメールアドレスを使用していることを確認してください。", + "title": "このリンクを相手に送信してください" + }, + "message": "共有可能な招待リンクを作成して送信してください", + "placeholder": "招待する人のメールアドレスを入力", + "title": "このワークスペースに人を招待" + }, + "leaveDialog": { + "leave": "退出", + "message": "ワークスペースのオーナーに連絡しない限り、再参加できません。", + "title": "このワークスペースを退出しますか?" + }, + "members": { + "actions": { + "copyLink": "招待リンクをコピー", + "removeMember": "メンバーを削除", + "revokeInvite": "招待を取り消す" + }, + "columns": { + "expiryDate": "有効期限", + "inviteDate": "招待日", + "joinDate": "参加日" + }, + "createNewWorkspace": "新しいワークスペースを作成してください。", + "membersCount": "{count}/50人のメンバー", + "noInvites": "保留中の招待はありません", + "noMembers": "メンバーがいません", + "pendingInvitesCount": "{count}件の招待保留中", + "personalWorkspaceMessage": "現在、個人用ワークスペースには他のメンバーを招待できません。メンバーを追加するには、", + "tabs": { + "active": "アクティブ", + "pendingCount": "保留中({count})" + } + }, + "menu": { + "deleteWorkspace": "ワークスペースを削除", + "deleteWorkspaceDisabledTooltip": "まずワークスペースの有効なサブスクリプションをキャンセルしてください", + "editWorkspace": "ワークスペースの詳細を編集", + "leaveWorkspace": "ワークスペースを退出" + }, + "removeMemberDialog": { + "error": "メンバーの削除に失敗しました", + "message": "このメンバーはワークスペースから削除されます。使用済みのクレジットは返金されません。", + "remove": "メンバーを削除", + "success": "メンバーを削除しました", + "title": "このメンバーを削除しますか?" + }, + "revokeInviteDialog": { + "message": "このメンバーは今後ワークスペースに参加できなくなります。招待リンクは無効になります。", + "revoke": "招待を取り消す", + "title": "この人の招待を取り消しますか?" + }, + "tabs": { + "dashboard": "ダッシュボード", + "membersCount": "メンバー({count})", + "planCredits": "プランとクレジット" + }, + "toast": { + "failedToCreateWorkspace": "ワークスペースの作成に失敗しました", + "failedToDeleteWorkspace": "ワークスペースの削除に失敗しました", + "failedToFetchWorkspaces": "ワークスペースの読み込みに失敗しました", + "failedToLeaveWorkspace": "ワークスペースの退出に失敗しました", + "failedToUpdateWorkspace": "ワークスペースの更新に失敗しました", + "workspaceCreated": { + "message": "プランに加入し、チームメイトを招待して、コラボレーションを始めましょう。", + "subscribe": "プランに加入", + "title": "ワークスペースを作成しました" + }, + "workspaceDeleted": { + "message": "ワークスペースは完全に削除されました。", + "title": "ワークスペースを削除しました" + }, + "workspaceLeft": { + "message": "ワークスペースから退出しました。", + "title": "ワークスペースから退出しました" + }, + "workspaceUpdated": { + "message": "ワークスペースの詳細が保存されました。", + "title": "ワークスペースが更新されました" + } + } + }, + "workspaceSwitcher": { + "createWorkspace": "新しいワークスペースを作成", + "maxWorkspacesReached": "所有できるワークスペースは10個までです。新しく作成するには1つ削除してください。", + "personal": "個人用", + "roleMember": "メンバー", + "roleOwner": "オーナー", + "subscribe": "サブスクライブ", + "switchWorkspace": "ワークスペースを切り替え" + }, "zoomControls": { "hideMinimap": "ミニマップを非表示", "label": "ズームコントロール", diff --git a/src/locales/ja/nodeDefs.json b/src/locales/ja/nodeDefs.json index e4ba0ab31..c0a6cc687 100644 --- a/src/locales/ja/nodeDefs.json +++ b/src/locales/ja/nodeDefs.json @@ -39,6 +39,87 @@ "sigmas": { "name": "シグマ" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AddTextPrefix": { + "display_name": "テキストの接頭辞を追加", + "inputs": { + "prefix": { + "name": "接頭辞", + "tooltip": "追加する接頭辞。" + }, + "texts": { + "name": "テキスト", + "tooltip": "処理するテキスト。" + } + }, + "outputs": { + "0": { + "name": "テキスト", + "tooltip": "処理済みテキスト" + } + } + }, + "AddTextSuffix": { + "display_name": "テキストの接尾辞を追加", + "inputs": { + "suffix": { + "name": "接尾辞", + "tooltip": "追加する接尾辞。" + }, + "texts": { + "name": "テキスト", + "tooltip": "処理するテキスト。" + } + }, + "outputs": { + "0": { + "name": "テキスト", + "tooltip": "処理済みテキスト" + } + } + }, + "AdjustBrightness": { + "display_name": "明るさを調整", + "inputs": { + "factor": { + "name": "係数", + "tooltip": "明るさの係数。1.0 = 変更なし、<1.0 = 暗く、>1.0 = 明るく。" + }, + "images": { + "name": "画像", + "tooltip": "処理する画像。" + } + }, + "outputs": { + "0": { + "name": "画像", + "tooltip": "処理済み画像" + } + } + }, + "AdjustContrast": { + "display_name": "コントラストを調整", + "inputs": { + "factor": { + "name": "係数", + "tooltip": "コントラストの係数。1.0 = 変更なし、<1.0 = コントラスト減少、>1.0 = コントラスト増加。" + }, + "images": { + "name": "画像", + "tooltip": "処理する画像。" + } + }, + "outputs": { + "0": { + "name": "画像", + "tooltip": "処理済み画像" + } } }, "AlignYourStepsScheduler": { @@ -70,6 +151,11 @@ "name": "音量", "tooltip": "デシベル(dB)単位での音量調整。0 = 変更なし、+6 = 2倍、-6 = 半分、など" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "AudioConcat": { @@ -86,6 +172,11 @@ "name": "方向", "tooltip": "audio2をaudio1の後ろに追加するか前に追加するか。" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "AudioEncoderEncode": { @@ -131,6 +222,11 @@ "name": "結合方法", "tooltip": "オーディオ波形を結合するために使用する方法。" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BasicGuider": { @@ -142,6 +238,11 @@ "model": { "name": "モデル" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BasicScheduler": { @@ -159,6 +260,50 @@ "steps": { "name": "ステップ" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchImagesNode": { + "display_name": "画像をバッチ処理", + "inputs": { + "images": { + "name": "画像" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchLatentsNode": { + "display_name": "latentをバッチ処理", + "inputs": { + "latents": { + "name": "latent" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchMasksNode": { + "display_name": "マスクをバッチ処理", + "inputs": { + "masks": { + "name": "マスク" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BetaSamplingScheduler": { @@ -176,6 +321,73 @@ "steps": { "name": "ステップ" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BriaImageEditNode": { + "description": "Briaの最新モデルを使って画像を編集します", + "display_name": "Bria画像編集", + "inputs": { + "control_after_generate": { + "name": "生成後コントロール" + }, + "guidance_scale": { + "name": "ガイダンススケール", + "tooltip": "値が高いほどプロンプトに忠実な画像になります。" + }, + "image": { + "name": "画像" + }, + "mask": { + "name": "マスク", + "tooltip": "省略した場合、編集は画像全体に適用されます。" + }, + "model": { + "name": "model" + }, + "moderation": { + "name": "モデレーション", + "tooltip": "モデレーション設定" + }, + "moderation_prompt_content_moderation": { + "name": "プロンプト内容モデレーション" + }, + "moderation_visual_input_moderation": { + "name": "入力画像モデレーション" + }, + "moderation_visual_output_moderation": { + "name": "出力画像モデレーション" + }, + "negative_prompt": { + "name": "ネガティブプロンプト" + }, + "prompt": { + "name": "プロンプト", + "tooltip": "画像編集の指示" + }, + "seed": { + "name": "シード" + }, + "steps": { + "name": "ステップ数" + }, + "structured_prompt": { + "name": "構造化プロンプト", + "tooltip": "JSON形式の構造化編集プロンプトを含む文字列。より正確でプログラム的な制御が必要な場合は通常のプロンプトの代わりに使用してください。" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "構造化プロンプト", + "tooltip": null + } } }, "ByteDanceFirstLastFrameNode": { @@ -201,13 +413,16 @@ "name": "最初のフレーム", "tooltip": "動画に使用する最初のフレーム。" }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "このパラメータは、seedance-1-5-pro 以外のモデルでは無視されます。" + }, "last_frame": { "name": "最後のフレーム", "tooltip": "動画に使用する最後のフレーム。" }, "model": { - "name": "モデル", - "tooltip": "モデル名" + "name": "モデル" }, "prompt": { "name": "プロンプト", @@ -248,8 +463,7 @@ "tooltip": "編集するベース画像" }, "model": { - "name": "モデル", - "tooltip": "モデル名" + "name": "モデル" }, "prompt": { "name": "プロンプト", @@ -286,8 +500,7 @@ "tooltip": "画像のカスタム高さ。`size_preset`が`Custom`に設定されている場合のみ有効" }, "model": { - "name": "モデル", - "tooltip": "モデル名" + "name": "モデル" }, "prompt": { "name": "プロンプト", @@ -336,8 +549,7 @@ "tooltip": "1〜4枚の画像" }, "model": { - "name": "モデル", - "tooltip": "モデル名" + "name": "モデル" }, "prompt": { "name": "プロンプト", @@ -381,13 +593,16 @@ "name": "duration", "tooltip": "出力動画の長さ(秒単位)。" }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "このパラメータは、seedance-1-5-pro 以外のモデルでは無視されます。" + }, "image": { "name": "image", "tooltip": "動画の最初のフレームとして使用する画像。" }, "model": { - "name": "model", - "tooltip": "モデル名" + "name": "model" }, "prompt": { "name": "prompt", @@ -489,9 +704,12 @@ "name": "長さ", "tooltip": "出力動画の長さ(秒単位)。" }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "このパラメータは、seedance-1-5-pro 以外のモデルでは無視されます。" + }, "model": { - "name": "モデル", - "tooltip": "モデル名" + "name": "モデル" }, "prompt": { "name": "プロンプト", @@ -531,6 +749,11 @@ "positive": { "name": "ポジティブ" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "CFGNorm": { @@ -769,6 +992,25 @@ } } }, + "CLIPTextEncodeKandinsky5": { + "display_name": "CLIPTextEncodeKandinsky5", + "inputs": { + "clip": { + "name": "clip" + }, + "clip_l": { + "name": "clip_l" + }, + "qwen25_7b": { + "name": "qwen25_7b" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "CLIPTextEncodeLumina2": { "description": "CLIPモデルを使用してシステムプロンプトとユーザープロンプトをエンコードし、特定の画像の生成をガイドするために使用できる埋め込みを生成します。", "display_name": "CLIP Text Encode for Lumina2", @@ -959,6 +1201,29 @@ } } }, + "CenterCropImages": { + "display_name": "中央で画像を切り抜き", + "inputs": { + "height": { + "name": "高さ", + "tooltip": "切り抜き高さ。" + }, + "images": { + "name": "画像", + "tooltip": "処理する画像。" + }, + "width": { + "name": "幅", + "tooltip": "切り抜き幅。" + } + }, + "outputs": { + "0": { + "name": "画像", + "tooltip": "処理済み画像" + } + } + }, "CheckpointLoader": { "display_name": "設定でチェックポイントを読み込む(非推奨)", "inputs": { @@ -1095,6 +1360,26 @@ } } }, + "ComfySwitchNode": { + "display_name": "スイッチ", + "inputs": { + "on_false": { + "name": "偽の場合" + }, + "on_true": { + "name": "真の場合" + }, + "switch": { + "name": "スイッチ" + } + }, + "outputs": { + "0": { + "name": "出力", + "tooltip": null + } + } + }, "ConditioningAverage": { "display_name": "条件付け平均", "inputs": { @@ -1327,14 +1612,14 @@ "name": "秒_合計" } }, - "outputs": { - "0": { - "name": "ポジティブ" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "ネガティブ" + { + "tooltip": null } - } + ] }, "ConditioningTimestepsRange": { "display_name": "タイムステップ範囲", @@ -1391,6 +1676,10 @@ "name": "dim", "tooltip": "コンテキストウィンドウを適用する次元。" }, + "freenoise": { + "name": "フリーノイズ", + "tooltip": "FreeNoiseノイズシャッフルを適用するかどうか。ウィンドウのブレンドを改善します。" + }, "fuse_method": { "name": "fuse_method", "tooltip": "コンテキストウィンドウを融合するために使用する方法。" @@ -1791,8 +2080,32 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, + "CustomCombo": { + "display_name": "カスタムコンボ", + "inputs": { + "choice": { + "name": "選択" + }, + "index": { + }, + "option1": { + } + }, + "outputs": [ + null, + { + "name": "インデックス", + "tooltip": null + } + ] + }, "DiffControlNetLoader": { "display_name": "ControlNetモデルを読み込む(diff)", "inputs": { @@ -1829,7 +2142,12 @@ } }, "DisableNoise": { - "display_name": "ノイズを無効にする" + "display_name": "ノイズを無効にする", + "outputs": { + "0": { + "tooltip": null + } + } }, "DualCFGGuider": { "display_name": "デュアルCFGガイダー", @@ -1855,6 +2173,11 @@ "style": { "name": "style" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "DualCLIPLoader": { @@ -1938,6 +2261,11 @@ "name": "sample_rate", "tooltip": "空のオーディオクリップのサンプルレート。" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyChromaRadianceLatentImage": { @@ -1981,6 +2309,25 @@ } } }, + "EmptyFlux2LatentImage": { + "display_name": "Empty Flux 2 Latent", + "inputs": { + "batch_size": { + "name": "バッチサイズ" + }, + "height": { + "name": "高さ" + }, + "width": { + "name": "幅" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptyHunyuanImageLatent": { "display_name": "EmptyHunyuanImageLatent", "inputs": { @@ -2022,6 +2369,28 @@ } } }, + "EmptyHunyuanVideo15Latent": { + "display_name": "Empty HunyuanVideo 1.5 Latent", + "inputs": { + "batch_size": { + "name": "バッチサイズ" + }, + "height": { + "name": "高さ" + }, + "length": { + "name": "長さ" + }, + "width": { + "name": "幅" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptyImage": { "display_name": "空の画像", "inputs": { @@ -2071,6 +2440,11 @@ "seconds": { "name": "秒" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyLatentHunyuan3Dv2": { @@ -2083,6 +2457,11 @@ "resolution": { "name": "解像度" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyLatentImage": { @@ -2130,6 +2509,28 @@ } } }, + "EmptyQwenImageLayeredLatentImage": { + "display_name": "Empty Qwen Image Layered Latent", + "inputs": { + "batch_size": { + "name": "バッチサイズ" + }, + "height": { + "name": "高さ" + }, + "layers": { + "name": "レイヤー" + }, + "width": { + "name": "幅" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptySD3LatentImage": { "display_name": "空のSD3潜在画像", "inputs": { @@ -2177,6 +2578,11 @@ "steps": { "name": "ステップ" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ExtendIntermediateSigmas": { @@ -2197,6 +2603,11 @@ "steps": { "name": "ステップ数" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FeatherMask": { @@ -2217,6 +2628,11 @@ "top": { "name": "上" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FlipSigmas": { @@ -2225,6 +2641,102 @@ "sigmas": { "name": "sigmas" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2MaxImageNode": { + "description": "プロンプトと解像度に基づいて同期的に画像を生成します。", + "display_name": "Flux.2 [max] 画像", + "inputs": { + "control_after_generate": { + "name": "生成後のコントロール" + }, + "height": { + "name": "高さ" + }, + "images": { + "name": "画像", + "tooltip": "参照用として最大9枚の画像を使用できます。" + }, + "prompt": { + "name": "プロンプト", + "tooltip": "画像生成または編集のためのプロンプト" + }, + "prompt_upsampling": { + "name": "プロンプトアップサンプリング", + "tooltip": "プロンプトのアップサンプリングを行うかどうか。有効にすると、より創造的な生成のためにプロンプトが自動的に修正されます。" + }, + "seed": { + "name": "シード", + "tooltip": "ノイズ生成に使用されるランダムシード。" + }, + "width": { + "name": "幅" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2ProImageNode": { + "description": "プロンプトと解像度に基づいて同期的に画像を生成します。", + "display_name": "Flux.2 [pro] 画像", + "inputs": { + "control_after_generate": { + "name": "生成後のコントロール" + }, + "height": { + "name": "高さ" + }, + "images": { + "name": "画像", + "tooltip": "参照用として最大9枚の画像を使用できます。" + }, + "prompt": { + "name": "プロンプト", + "tooltip": "画像生成または編集のためのプロンプト" + }, + "prompt_upsampling": { + "name": "プロンプトアップサンプリング", + "tooltip": "プロンプトのアップサンプリングを行うかどうか。有効にすると、より創造的な生成のためにプロンプトが自動的に修正されます。" + }, + "seed": { + "name": "シード", + "tooltip": "ノイズ生成に使用されるランダムシード。" + }, + "width": { + "name": "幅" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2Scheduler": { + "display_name": "Flux2Scheduler", + "inputs": { + "height": { + "name": "高さ" + }, + "steps": { + "name": "ステップ数" + }, + "width": { + "name": "幅" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FluxDisableGuidance": { @@ -2547,6 +3059,11 @@ "s2": { "name": "s2" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FreeU_V2": { @@ -2567,6 +3084,11 @@ "s2": { "name": "s2" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "GITSScheduler": { @@ -2625,6 +3147,58 @@ } } }, + "GeminiImage2Node": { + "description": "Google Vertex API を通じて画像を同期的に生成または編集します。", + "display_name": "Nano Banana Pro(Google Gemini Image)", + "inputs": { + "aspect_ratio": { + "name": "アスペクト比", + "tooltip": "「auto」に設定すると入力画像のアスペクト比に合わせます。画像が指定されていない場合、通常は16:9の正方形が生成されます。" + }, + "control_after_generate": { + "name": "生成後のコントロール" + }, + "files": { + "name": "ファイル", + "tooltip": "モデルのコンテキストとして使用するオプションのファイル。Gemini Generate Content Input Filesノードからの入力を受け付けます。" + }, + "images": { + "name": "画像", + "tooltip": "オプションの参照画像。複数画像を含める場合はBatch Imagesノードを使用してください(最大14枚まで)。" + }, + "model": { + "name": "モデル" + }, + "prompt": { + "name": "プロンプト", + "tooltip": "生成する画像や適用する編集内容を説明するテキストプロンプト。モデルが従うべき制約、スタイル、詳細も含めてください。" + }, + "resolution": { + "name": "解像度", + "tooltip": "出力の目標解像度。2K/4Kの場合はGeminiのネイティブアップスケーラーが使用されます。" + }, + "response_modalities": { + "name": "応答モダリティ", + "tooltip": "「IMAGE」を選択すると画像のみ出力、「IMAGE+TEXT」を選択すると生成画像とテキスト応答の両方を返します。" + }, + "seed": { + "name": "シード", + "tooltip": "シード値を特定の値に固定すると、モデルは繰り返しリクエストに対して同じ応答を提供するよう最善を尽くしますが、決定的な出力は保証されません。また、モデルやパラメータ設定(例:temperature)を変更すると、同じシード値でも応答が変化する場合があります。デフォルトではランダムなシード値が使用されます。" + }, + "system_prompt": { + "name": "システムプロンプト", + "tooltip": "AIの挙動を決定する基本的な指示。" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "tooltip": null + } + } + }, "GeminiImageNode": { "description": "Google API経由で画像を同期的に編集します。", "display_name": "Google Gemini 画像", @@ -2652,9 +3226,17 @@ "name": "プロンプト", "tooltip": "生成用のテキストプロンプト" }, + "response_modalities": { + "name": "応答モダリティ", + "tooltip": "「IMAGE」を選択すると画像のみ出力、「IMAGE+TEXT」を選択すると生成画像とテキスト応答の両方を返します。" + }, "seed": { "name": "シード", "tooltip": "シードが特定の値に固定されている場合、モデルは繰り返しリクエストに対して同じ応答を提供するよう最善を尽くします。決定的な出力は保証されません。また、モデルや温度などのパラメータ設定を変更すると、同じシード値を使用しても応答にばらつきが生じることがあります。デフォルトでは、ランダムなシード値が使用されます。" + }, + "system_prompt": { + "name": "システムプロンプト", + "tooltip": "AIの挙動を決定する基本的な指示。" } }, "outputs": { @@ -2716,6 +3298,10 @@ "name": "シード", "tooltip": "シードを特定の値に固定すると、モデルは繰り返しリクエストに対して同じ応答を提供するよう最善を尽くします。確定的な出力は保証されません。また、モデルや温度などのパラメータ設定を変更すると、同じシード値を使用しても応答にばらつきが生じることがあります。デフォルトではランダムなシード値が使用されます。" }, + "system_prompt": { + "name": "システムプロンプト", + "tooltip": "AIの挙動を決定する基本的な指示。" + }, "video": { "name": "動画", "tooltip": "モデルのコンテキストとして使用するオプションの動画。" @@ -2727,6 +3313,72 @@ } } }, + "GenerateTracks": { + "display_name": "GenerateTracks", + "inputs": { + "bezier": { + "name": "ベジエ", + "tooltip": "中間点を制御点としてベジエ曲線パスを有効にします。" + }, + "end_x": { + "name": "終了X", + "tooltip": "終了位置の正規化されたX座標(0-1)。" + }, + "end_y": { + "name": "終了Y", + "tooltip": "終了位置の正規化されたY座標(0-1)。" + }, + "height": { + "name": "高さ" + }, + "interpolation": { + "name": "補間", + "tooltip": "パスに沿った動きのタイミング/速度を制御します。" + }, + "mid_x": { + "name": "中間X", + "tooltip": "ベジエ曲線の正規化されたX制御点。「bezier」が有効な場合のみ使用されます。" + }, + "mid_y": { + "name": "中間Y", + "tooltip": "ベジエ曲線の正規化されたY制御点。「bezier」が有効な場合のみ使用されます。" + }, + "num_frames": { + "name": "フレーム数" + }, + "num_tracks": { + "name": "トラック数" + }, + "start_x": { + "name": "開始X", + "tooltip": "開始位置の正規化されたX座標(0-1)。" + }, + "start_y": { + "name": "開始Y", + "tooltip": "開始位置の正規化されたY座標(0-1)。" + }, + "track_mask": { + "name": "トラックマスク", + "tooltip": "表示フレームを示すオプションのマスク。" + }, + "track_spread": { + "name": "トラック間隔", + "tooltip": "トラック間の正規化された距離。トラックは動きの方向に対して垂直に広がります。" + }, + "width": { + "name": "幅" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "トラック長", + "tooltip": null + } + } + }, "GetImageSize": { "description": "画像の幅と高さを返し、変更せずに通過させます。", "display_name": "画像サイズ取得", @@ -2735,17 +3387,17 @@ "name": "画像" } }, - "outputs": { - "0": { - "name": "幅" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "高さ" + { + "tooltip": null }, - "2": { - "name": "バッチサイズ" + { + "tooltip": null } - } + ] }, "GetVideoComponents": { "description": "ビデオからすべてのコンポーネント(フレーム、オーディオ、フレームレート)を抽出します。", @@ -2783,6 +3435,11 @@ "tapered_corners": { "name": "テーパードコーナー" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Hunyuan3Dv2Conditioning": { @@ -2792,14 +3449,14 @@ "name": "clip_vision_output" } }, - "outputs": { - "0": { - "name": "ポジティブ" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "ネガティブ" + { + "tooltip": null } - } + ] }, "Hunyuan3Dv2ConditioningMultiView": { "display_name": "Hunyuan3Dv2ConditioningMultiView", @@ -2817,14 +3474,14 @@ "name": "右" } }, - "outputs": { - "0": { - "name": "ポジティブ" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "ネガティブ" + { + "tooltip": null } - } + ] }, "HunyuanImageToVideo": { "display_name": "HunyuanImageToVideo", @@ -2896,6 +3553,120 @@ } } }, + "HunyuanVideo15ImageToVideo": { + "display_name": "HunyuanVideo15ImageToVideo", + "inputs": { + "batch_size": { + "name": "バッチサイズ" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "height": { + "name": "高さ" + }, + "length": { + "name": "長さ" + }, + "negative": { + "name": "ネガティブ" + }, + "positive": { + "name": "ポジティブ" + }, + "start_image": { + "name": "開始画像" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "幅" + } + }, + "outputs": { + "0": { + "name": "ポジティブ", + "tooltip": null + }, + "1": { + "name": "ネガティブ", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "HunyuanVideo15LatentUpscaleWithModel": { + "display_name": "Hunyuan Video 15 Latent Upscale With Model", + "inputs": { + "crop": { + "name": "切り抜き" + }, + "height": { + "name": "高さ" + }, + "model": { + "name": "モデル" + }, + "samples": { + "name": "サンプル" + }, + "upscale_method": { + "name": "アップスケール方法" + }, + "width": { + "name": "幅" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "HunyuanVideo15SuperResolution": { + "display_name": "HunyuanVideo15SuperResolution", + "inputs": { + "clip_vision_output": { + "name": "clipビジョン出力" + }, + "latent": { + "name": "潜在" + }, + "negative": { + "name": "ネガティブ" + }, + "noise_augmentation": { + "name": "ノイズ拡張" + }, + "positive": { + "name": "ポジティブ" + }, + "start_image": { + "name": "開始画像" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "ポジティブ", + "tooltip": null + }, + "1": { + "name": "ネガティブ", + "tooltip": null + }, + "2": { + "name": "潜在", + "tooltip": null + } + } + }, "HyperTile": { "display_name": "ハイパータイル", "inputs": { @@ -3100,6 +3871,11 @@ "strength": { "name": "強度" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageBatch": { @@ -3163,6 +3939,26 @@ "image": { "name": "画像" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageCompare": { + "description": "2枚の画像をスライダーで並べて比較します。", + "display_name": "画像比較", + "inputs": { + "compare_view": { + "name": "compare_view" + }, + "image_a": { + "name": "image_a" + }, + "image_b": { + "name": "image_b" + } } }, "ImageCompositeMasked": { @@ -3186,6 +3982,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageCrop": { @@ -3206,6 +4007,30 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageDeduplication": { + "display_name": "画像の重複排除", + "inputs": { + "images": { + "name": "画像", + "tooltip": "処理する画像のリスト。" + }, + "similarity_threshold": { + "name": "類似度しきい値", + "tooltip": "類似度しきい値(0-1)。値が高いほど類似しています。このしきい値を超える画像は重複とみなされます。" + } + }, + "outputs": { + "0": { + "name": "画像", + "tooltip": "処理済み画像" + } } }, "ImageFlip": { @@ -3217,6 +4042,11 @@ "image": { "name": "画像" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageFromBatch": { @@ -3231,6 +4061,42 @@ "length": { "name": "長さ" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageGrid": { + "display_name": "画像グリッド", + "inputs": { + "cell_height": { + "name": "セル高さ", + "tooltip": "グリッド内の各セルの高さ。" + }, + "cell_width": { + "name": "セル幅", + "tooltip": "グリッド内の各セルの幅。" + }, + "columns": { + "name": "列数", + "tooltip": "グリッドの列数。" + }, + "images": { + "name": "画像", + "tooltip": "処理する画像のリスト。" + }, + "padding": { + "name": "余白", + "tooltip": "画像間の余白。" + } + }, + "outputs": { + "0": { + "name": "画像", + "tooltip": "処理済み画像" + } } }, "ImageInvert": { @@ -3339,6 +4205,11 @@ "rotation": { "name": "回転" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageScale": { @@ -3387,6 +4258,11 @@ "upscale_method": { "name": "アップスケール方法" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageScaleToTotalPixels": { @@ -3398,6 +4274,9 @@ "megapixels": { "name": "メガピクセル" }, + "resolution_steps": { + "name": "解像度ステップ" + }, "upscale_method": { "name": "拡大方法" } @@ -3452,6 +4331,11 @@ "spacing_width": { "name": "間隔の幅" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageToMask": { @@ -3463,6 +4347,11 @@ "image": { "name": "画像" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageUpscaleWithModel": { @@ -3572,6 +4461,29 @@ "mask": { "name": "マスク" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "JoinAudioChannels": { + "description": "左右のモノラルオーディオチャンネルをステレオオーディオに結合します。", + "display_name": "オーディオチャンネル結合", + "inputs": { + "audio_left": { + "name": "audio_left" + }, + "audio_right": { + "name": "audio_right" + } + }, + "outputs": { + "0": { + "name": "audio", + "tooltip": null + } } }, "JoinImageWithAlpha": { @@ -3697,6 +4609,58 @@ "sampler_name": { "name": "サンプラー名" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Kandinsky5ImageToVideo": { + "display_name": "Kandinsky5ImageToVideo", + "inputs": { + "batch_size": { + "name": "バッチサイズ" + }, + "height": { + "name": "高さ" + }, + "length": { + "name": "長さ" + }, + "negative": { + "name": "ネガティブ" + }, + "positive": { + "name": "ポジティブ" + }, + "start_image": { + "name": "開始画像" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "幅" + } + }, + "outputs": { + "0": { + "name": "ポジティブ", + "tooltip": null + }, + "1": { + "name": "ネガティブ", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": "空のビデオlatent" + }, + "3": { + "name": "cond_latent", + "tooltip": "ノイズのないエンコード済み開始画像。モデル出力のlatentのノイズの多い開始部分を置き換えるために使用されます" + } } }, "KarrasScheduler": { @@ -3714,6 +4678,11 @@ "steps": { "name": "ステップ" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "KlingCameraControlI2VNode": { @@ -3869,7 +4838,6 @@ } }, "KlingImage2VideoNode": { - "description": "Kling 画像から動画ノード", "display_name": "Kling 画像から動画へ", "inputs": { "aspect_ratio": { @@ -3957,6 +4925,35 @@ } } }, + "KlingImageToVideoWithAudio": { + "display_name": "Kling画像(最初のフレーム)からビデオと音声へ", + "inputs": { + "duration": { + "name": "継続時間" + }, + "generate_audio": { + "name": "音声を生成" + }, + "mode": { + "name": "モード" + }, + "model_name": { + "name": "モデル名" + }, + "prompt": { + "name": "プロンプト", + "tooltip": "ポジティブなテキストプロンプト。" + }, + "start_frame": { + "name": "開始フレーム" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingLipSyncAudioToVideoNode": { "description": "Klingリップシンク音声から動画ノード。動画ファイル内の口の動きを音声ファイルの内容に同期します。", "display_name": "Klingリップシンク:音声付き動画", @@ -4018,6 +5015,227 @@ } } }, + "KlingMotionControl": { + "display_name": "Klingモーションコントロール", + "inputs": { + "character_orientation": { + "name": "キャラクターの向き", + "tooltip": "キャラクターの向き/オリエンテーションの参照元を制御します。\nビデオ: 動き、表情、カメラの動き、向きは参照ビデオに従います(他の詳細はプロンプトで指定)。\n画像: 動きや表情は参照ビデオに従いますが、キャラクターの向きは参照画像に合わせます(カメラや他の詳細はプロンプトで指定)。" + }, + "keep_original_sound": { + "name": "元の音声を保持" + }, + "mode": { + "name": "モード" + }, + "prompt": { + "name": "プロンプト" + }, + "reference_image": { + "name": "参照画像" + }, + "reference_video": { + "name": "参照ビデオ", + "tooltip": "動きの参照ビデオは動きや表情を制御します。\nキャラクターの向きによる制限:\n - 画像: 3~10秒(最大10秒)\n - ビデオ: 3~30秒(最大30秒)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProEditVideoNode": { + "description": "Klingの最新モデルで既存のビデオを編集します。", + "display_name": "Kling Omniビデオ編集(Pro)", + "inputs": { + "keep_original_sound": { + "name": "元の音声を保持" + }, + "model_name": { + "name": "モデル名" + }, + "prompt": { + "name": "プロンプト", + "tooltip": "ビデオ内容を説明するテキストプロンプト。ポジティブ・ネガティブ両方の説明を含めることができます。" + }, + "reference_images": { + "name": "参照画像", + "tooltip": "最大4枚まで追加の参照画像。" + }, + "resolution": { + "name": "解像度" + }, + "video": { + "name": "ビデオ", + "tooltip": "編集するビデオ。出力ビデオの長さは同じになります。" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProFirstLastFrameNode": { + "description": "開始フレーム、オプションの終了フレーム、またはリファレンス画像を使用して最新のKlingモデルでビデオを生成します。", + "display_name": "Kling Omni ファースト・ラストフレームからビデオへ (Pro)", + "inputs": { + "duration": { + "name": "継続時間" + }, + "end_frame": { + "name": "終了フレーム", + "tooltip": "ビデオのオプションの終了フレームです。「reference_images」と同時に使用することはできません。" + }, + "first_frame": { + "name": "開始フレーム" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "プロンプト", + "tooltip": "ビデオ内容を説明するテキストプロンプトです。肯定的・否定的な説明の両方を含めることができます。" + }, + "reference_images": { + "name": "リファレンス画像", + "tooltip": "最大6枚までの追加リファレンス画像。" + }, + "resolution": { + "name": "解像度" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageNode": { + "description": "Klingの最新モデルで画像を作成または編集します。", + "display_name": "Kling Omni 画像 (Pro)", + "inputs": { + "aspect_ratio": { + "name": "アスペクト比" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "プロンプト", + "tooltip": "画像内容を説明するテキストプロンプトです。肯定的・否定的な説明の両方を含めることができます。" + }, + "reference_images": { + "name": "リファレンス画像", + "tooltip": "最大10枚までの追加リファレンス画像。" + }, + "resolution": { + "name": "解像度" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageToVideoNode": { + "description": "最大7枚のリファレンス画像を使って最新のKlingモデルでビデオを生成します。", + "display_name": "Kling Omni 画像からビデオへ (Pro)", + "inputs": { + "aspect_ratio": { + "name": "アスペクト比" + }, + "duration": { + "name": "継続時間" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "プロンプト", + "tooltip": "ビデオ内容を説明するテキストプロンプトです。肯定的・否定的な説明の両方を含めることができます。" + }, + "reference_images": { + "name": "リファレンス画像", + "tooltip": "最大7枚までのリファレンス画像。" + }, + "resolution": { + "name": "解像度" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProTextToVideoNode": { + "description": "テキストプロンプトを使って最新のKlingモデルでビデオを生成します。", + "display_name": "Kling Omni テキストからビデオへ (Pro)", + "inputs": { + "aspect_ratio": { + "name": "アスペクト比" + }, + "duration": { + "name": "継続時間" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "プロンプト", + "tooltip": "ビデオ内容を説明するテキストプロンプトです。肯定的・否定的な説明の両方を含めることができます。" + }, + "resolution": { + "name": "解像度" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProVideoToVideoNode": { + "description": "ビデオと最大4枚の参照画像を使用して、最新のKlingモデルでビデオを生成します。", + "display_name": "Kling Omni ビデオからビデオ (Pro)", + "inputs": { + "aspect_ratio": { + "name": "アスペクト比" + }, + "duration": { + "name": "長さ" + }, + "keep_original_sound": { + "name": "元の音声を保持" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "プロンプト", + "tooltip": "ビデオ内容を説明するテキストプロンプト。肯定的および否定的な説明の両方を含めることができます。" + }, + "reference_images": { + "name": "参照画像", + "tooltip": "追加で最大4枚の参照画像。" + }, + "reference_video": { + "name": "参照ビデオ", + "tooltip": "参照として使用するビデオ。" + }, + "resolution": { + "name": "解像度" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingSingleImageVideoEffectNode": { "description": "effect_scene に基づいてビデオを生成する際に、さまざまな特殊効果を実現します。", "display_name": "Kling ビデオエフェクト", @@ -4132,6 +5350,35 @@ } } }, + "KlingTextToVideoWithAudio": { + "display_name": "Kling テキストからビデオ(音声付き)", + "inputs": { + "aspect_ratio": { + "name": "アスペクト比" + }, + "duration": { + "name": "長さ" + }, + "generate_audio": { + "name": "音声を生成" + }, + "mode": { + "name": "モード" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "プロンプト", + "tooltip": "肯定的なテキストプロンプト。" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingVideoExtendNode": { "description": "Kling Video Extendノード。他のKlingノードで作成された動画を拡張します。video_idは他のKlingノードを使用して作成されます。", "display_name": "Kling Video Extend", @@ -4186,6 +5433,26 @@ } } }, + "LTXAVTextEncoderLoader": { + "description": "[レシピ]\n\nltxav: gemma 3 12B", + "display_name": "LTXV オーディオテキストエンコーダーローダー", + "inputs": { + "ckpt_name": { + "name": "ckpt_name" + }, + "device": { + "name": "device" + }, + "text_encoder": { + "name": "text_encoder" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LTXVAddGuide": { "display_name": "LTXVAddGuide", "inputs": { @@ -4228,6 +5495,76 @@ } } }, + "LTXVAudioVAEDecode": { + "display_name": "LTXV オーディオVAEデコード", + "inputs": { + "audio_vae": { + "name": "audio_vae", + "tooltip": "latent のデコードに使用する Audio VAE モデル。" + }, + "samples": { + "name": "samples", + "tooltip": "デコードする latent。" + } + }, + "outputs": { + "0": { + "name": "Audio", + "tooltip": null + } + } + }, + "LTXVAudioVAEEncode": { + "display_name": "LTXV オーディオVAEエンコード", + "inputs": { + "audio": { + "name": "audio", + "tooltip": "エンコードするオーディオ。" + }, + "audio_vae": { + "name": "audio_vae", + "tooltip": "エンコードに使用する Audio VAE モデル。" + } + }, + "outputs": { + "0": { + "name": "Audio Latent", + "tooltip": null + } + } + }, + "LTXVAudioVAELoader": { + "display_name": "LTXV オーディオVAEローダー", + "inputs": { + "ckpt_name": { + "name": "ckpt_name", + "tooltip": "読み込む Audio VAE チェックポイント。" + } + }, + "outputs": { + "0": { + "name": "Audio VAE", + "tooltip": null + } + } + }, + "LTXVConcatAVLatent": { + "display_name": "LTXVConcatAVLatent", + "inputs": { + "audio_latent": { + "name": "audio_latent" + }, + "video_latent": { + "name": "video_latent" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, "LTXVConditioning": { "display_name": "LTXV条件付け", "inputs": { @@ -4280,6 +5617,33 @@ } } }, + "LTXVEmptyLatentAudio": { + "display_name": "LTXV 空のラテントオーディオ", + "inputs": { + "audio_vae": { + "name": "audio_vae", + "tooltip": "設定を取得するための Audio VAE モデル。" + }, + "batch_size": { + "name": "batch_size", + "tooltip": "バッチ内の latent オーディオサンプル数。" + }, + "frame_rate": { + "name": "frame_rate", + "tooltip": "1秒あたりのフレーム数。" + }, + "frames_number": { + "name": "frames_number", + "tooltip": "フレーム数。" + } + }, + "outputs": { + "0": { + "name": "Latent", + "tooltip": null + } + } + }, "LTXVImgToVideo": { "display_name": "LTXV画像からビデオへ", "inputs": { @@ -4326,6 +5690,47 @@ } } }, + "LTXVImgToVideoInplace": { + "display_name": "LTXVImgToVideoInplace", + "inputs": { + "bypass": { + "name": "バイパス", + "tooltip": "コンディショニングをバイパスします。" + }, + "image": { + "name": "画像" + }, + "latent": { + "name": "latent" + }, + "strength": { + "name": "強度" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, + "LTXVLatentUpsampler": { + "display_name": "LTXVLatentUpsampler", + "inputs": { + "samples": { + "name": "サンプル" + }, + "upscale_model": { + "name": "アップスケールモデル" + }, + "vae": { + "name": "vae" + } + } + }, "LTXVPreprocess": { "display_name": "LTXVPreprocess", "inputs": { @@ -4374,6 +5779,25 @@ } } }, + "LTXVSeparateAVLatent": { + "description": "LTXV Separate AV Latent", + "display_name": "LTXVSeparateAVLatent", + "inputs": { + "av_latent": { + "name": "av_latent" + } + }, + "outputs": { + "0": { + "name": "ビデオlatent", + "tooltip": null + }, + "1": { + "name": "オーディオlatent", + "tooltip": null + } + } + }, "LaplaceScheduler": { "display_name": "ラプラススケジューラー", "inputs": { @@ -4392,6 +5816,11 @@ "steps": { "name": "ステップ" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LatentAdd": { @@ -4529,6 +5958,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LatentConcat": { @@ -4592,6 +6026,25 @@ } } }, + "LatentCutToBatch": { + "display_name": "LatentCutToBatch", + "inputs": { + "dim": { + "name": "次元" + }, + "samples": { + "name": "サンプル" + }, + "slice_size": { + "name": "スライスサイズ" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LatentFlip": { "display_name": "潜在反転", "inputs": { @@ -4745,6 +6198,19 @@ } } }, + "LatentUpscaleModelLoader": { + "display_name": "Latent アップスケールモデルを読み込む", + "inputs": { + "model_name": { + "name": "model_name" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LazyCache": { "description": "EasyCacheの自家製バージョン - EasyCacheをさらに「簡単に」実装するバージョン。全体的にはEasyCacheより劣りますが、一部の稀なケースでは優れており、ComfyUIのすべてとの普遍的な互換性があります。", "display_name": "LazyCache", @@ -4792,70 +6258,32 @@ }, "upload 3d model": { }, - "width": { - "name": "幅" - } - }, - "outputs": { - "0": { - "name": "画像" - }, - "1": { - "name": "マスク" - }, - "2": { - "name": "メッシュパス" - }, - "3": { - "name": "法線" - }, - "4": { - "name": "線画" - }, - "5": { - "name": "カメラ情報" - }, - "6": { - "name": "録画中の動画" - } - } - }, - "Load3DAnimation": { - "display_name": "3D読み込み - アニメーション", - "inputs": { - "height": { - "name": "高さ" - }, - "image": { - "name": "画像" - }, - "model_file": { - "name": "モデルファイル" + "upload extra resources": { }, "width": { "name": "幅" } }, - "outputs": { - "0": { - "name": "画像" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "マスク" + { + "tooltip": null }, - "2": { - "name": "メッシュパス" + { + "tooltip": null }, - "3": { - "name": "法線" + { + "tooltip": null }, - "4": { - "name": "カメラ情報" + { + "tooltip": null }, - "5": { - "name": "録画中の動画" + { + "tooltip": null } - } + ] }, "LoadAudio": { "display_name": "音声を読み込む", @@ -4869,6 +6297,11 @@ "upload": { "name": "アップロードするファイルを選択" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LoadImage": { @@ -4882,6 +6315,21 @@ } } }, + "LoadImageDataSetFromFolder": { + "display_name": "フォルダから画像データセットを読み込む", + "inputs": { + "folder": { + "name": "フォルダ", + "tooltip": "画像を読み込むフォルダ。" + } + }, + "outputs": { + "0": { + "name": "画像", + "tooltip": "読み込まれた画像のリスト" + } + } + }, "LoadImageMask": { "display_name": "画像を読み込む(マスクとして)", "inputs": { @@ -4900,6 +6348,8 @@ "description": "出力フォルダから画像を読み込みます。更新ボタンをクリックすると、ノードは画像リストを更新し、自動的に最初の画像を選択します。これにより、簡単に反復処理が可能になります。", "display_name": "画像の読み込み(出力から)", "inputs": { + "Auto-refresh after generation": { + }, "image": { "name": "画像" }, @@ -4910,41 +6360,22 @@ } } }, - "LoadImageSetFromFolderNode": { - "description": "トレーニング用にディレクトリから画像のバッチを読み込みます。", - "display_name": "フォルダから画像データセットを読み込み", + "LoadImageTextDataSetFromFolder": { + "display_name": "フォルダから画像とテキストデータセットを読み込む", "inputs": { "folder": { "name": "フォルダ", "tooltip": "画像を読み込むフォルダ。" - }, - "resize_method": { - "name": "リサイズ方法" } - } - }, - "LoadImageTextSetFromFolderNode": { - "description": "トレーニング用にディレクトリから画像とキャプションのバッチを読み込みます。", - "display_name": "フォルダから画像とテキストデータセットを読み込み", - "inputs": { - "clip": { - "name": "CLIP", - "tooltip": "テキストのエンコードに使用されるCLIPモデル。" + }, + "outputs": { + "0": { + "name": "画像", + "tooltip": "読み込まれた画像のリスト" }, - "folder": { - "name": "フォルダ", - "tooltip": "画像を読み込むフォルダ。" - }, - "height": { - "name": "高さ", - "tooltip": "画像をリサイズする高さ。-1は元の高さを使用することを意味します。" - }, - "resize_method": { - "name": "リサイズ方法" - }, - "width": { - "name": "幅", - "tooltip": "画像をリサイズする幅。-1は元の幅を使用することを意味します。" + "1": { + "name": "テキスト", + "tooltip": "テキストキャプションのリスト" } } }, @@ -4956,6 +6387,25 @@ } } }, + "LoadTrainingDataset": { + "display_name": "トレーニングデータセットの読み込み", + "inputs": { + "folder_name": { + "name": "folder_name", + "tooltip": "保存されたデータセットが含まれるフォルダー名(出力ディレクトリ内)。" + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "latent 辞書のリスト" + }, + "1": { + "name": "conditioning", + "tooltip": "conditioning リストのリスト" + } + } + }, "LoadVideo": { "display_name": "ビデオを読み込む", "inputs": { @@ -5027,7 +6477,6 @@ } }, "LoraModelLoader": { - "description": "Train LoRAノードからトレーニング済みLoRAの重みを読み込みます。", "display_name": "LoRAモデルを読み込み", "inputs": { "lora": { @@ -5043,11 +6492,11 @@ "tooltip": "拡散モデルを変更する強度。この値は負の値も可能です。" } }, - "outputs": { - "0": { - "tooltip": "変更された拡散モデル。" + "outputs": [ + { + "name": "model" } - } + ] }, "LoraSave": { "display_name": "LoRAを抽出して保存", @@ -5075,14 +6524,15 @@ } }, "LossGraphNode": { - "description": "損失グラフをプロットし、出力ディレクトリに保存します。", "display_name": "損失グラフをプロット", "inputs": { "filename_prefix": { - "name": "ファイル名プレフィックス" + "name": "ファイル名プレフィックス", + "tooltip": "保存される損失グラフ画像のファイル名プレフィックス。" }, "loss": { - "name": "損失" + "name": "損失", + "tooltip": "トレーニングノードからの損失マップ。" } } }, @@ -5388,6 +6838,50 @@ } } }, + "MakeTrainingDataset": { + "display_name": "トレーニングデータセットを作成", + "inputs": { + "clip": { + "name": "clip", + "tooltip": "テキストをコンディショニングにエンコードするためのCLIPモデル。" + }, + "images": { + "name": "画像", + "tooltip": "エンコードする画像のリスト。" + }, + "texts": { + "name": "テキスト", + "tooltip": "テキストキャプションのリスト。長さn(画像と一致)、1(全てに繰り返し)、または省略(空文字列を使用)できます。" + }, + "vae": { + "name": "vae", + "tooltip": "画像をlatentにエンコードするためのVAEモデル。" + } + }, + "outputs": { + "0": { + "name": "latentリスト", + "tooltip": "latent辞書のリスト" + }, + "1": { + "name": "コンディショニング", + "tooltip": "コンディショニングリストのリスト" + } + } + }, + "ManualSigmas": { + "display_name": "マニュアルシグマ", + "inputs": { + "sigmas": { + "name": "シグマ" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "MaskComposite": { "display_name": "マスク合成", "inputs": { @@ -5406,6 +6900,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "MaskPreview": { @@ -5423,6 +6922,315 @@ "mask": { "name": "マスク" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MergeImageLists": { + "display_name": "画像リストを結合", + "inputs": { + "images": { + "name": "画像", + "tooltip": "処理する画像のリスト。" + } + }, + "outputs": { + "0": { + "name": "画像", + "tooltip": "処理済み画像" + } + } + }, + "MergeTextLists": { + "display_name": "テキストリストを結合", + "inputs": { + "texts": { + "name": "テキスト", + "tooltip": "処理するテキストのリスト。" + } + }, + "outputs": { + "0": { + "name": "テキスト", + "tooltip": "処理済みテキスト" + } + } + }, + "MeshyAnimateModelNode": { + "description": "事前にリギングされたキャラクターに特定のアニメーションアクションを適用します。", + "display_name": "Meshy:アニメートモデル", + "inputs": { + "action_id": { + "name": "action_id", + "tooltip": "利用可能な値の一覧は https://docs.meshy.ai/en/api/animation-library をご覧ください。" + }, + "rig_task_id": { + "name": "rig_task_id" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + } + } + }, + "MeshyImageToModelNode": { + "display_name": "Meshy:画像からモデルへ", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "image": { + "name": "image" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "生成されるモデルのポーズモードを指定します。" + }, + "seed": { + "name": "seed", + "tooltip": "シードはノードの再実行を制御しますが、シードに関係なく結果は非決定的です。" + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "falseに設定すると、未処理の三角メッシュが返されます。" + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "should_texture": { + "name": "should_texture", + "tooltip": "テクスチャを生成するかどうかを決定します。falseに設定するとテクスチャ工程をスキップし、テクスチャなしのメッシュが返されます。" + }, + "should_texture_enable_pbr": { + "name": "enable_pbr" + }, + "should_texture_texture_prompt": { + "name": "texture_prompt" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyMultiImageToModelNode": { + "display_name": "Meshy:複数画像からモデルへ", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "生成されるモデルのポーズモードを指定します。" + }, + "seed": { + "name": "seed", + "tooltip": "シードはノードの再実行を制御しますが、シードに関係なく結果は非決定的です。" + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "falseに設定すると、未処理の三角メッシュが返されます。" + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "should_texture": { + "name": "should_texture", + "tooltip": "テクスチャを生成するかどうかを決定します。falseに設定するとテクスチャ工程をスキップし、テクスチャなしのメッシュが返されます。" + }, + "should_texture_enable_pbr": { + "name": "enable_pbr" + }, + "should_texture_texture_prompt": { + "name": "texture_prompt" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRefineNode": { + "description": "以前に作成したドラフトモデルをリファインします。", + "display_name": "Meshy:ドラフトモデルのリファイン", + "inputs": { + "enable_pbr": { + "name": "enable_pbr", + "tooltip": "ベースカラーに加えてPBRマップ(金属度、粗さ、法線)を生成します。注意:Sculptureスタイルを使用する場合はfalseに設定してください。Sculptureスタイルは独自のPBRマップを生成します。" + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "model": { + "name": "model" + }, + "texture_image": { + "name": "texture_image", + "tooltip": "「texture_image」と「texture_prompt」は同時に使用できません。" + }, + "texture_prompt": { + "name": "texture_prompt", + "tooltip": "テクスチャリングプロセスをガイドするテキストプロンプトを入力してください。最大600文字。「texture_image」と同時に使用することはできません。" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRigModelNode": { + "description": "標準フォーマットでリギング済みキャラクターを提供します。自動リギングは、テクスチャのないメッシュ、非ヒューマノイドアセット、または手足や体の構造が不明瞭なヒューマノイドアセットには適していません。", + "display_name": "Meshy:モデルのリギング", + "inputs": { + "height_meters": { + "name": "height_meters", + "tooltip": "キャラクターモデルのおおよその高さ(メートル単位)。スケーリングやリギングの精度向上に役立ちます。" + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "texture_image": { + "name": "texture_image", + "tooltip": "モデルのUV展開されたベースカラーテクスチャ画像です。" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "rig_task_id", + "tooltip": null + } + } + }, + "MeshyTextToModelNode": { + "display_name": "Meshy:テキストからモデル生成", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "生成されるモデルのポーズモードを指定します。" + }, + "prompt": { + "name": "prompt" + }, + "seed": { + "name": "seed", + "tooltip": "シードはノードの再実行を制御しますが、シードに関わらず結果は非決定的です。" + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "falseに設定すると、未処理の三角メッシュを返します。" + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "style": { + "name": "style" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyTextureNode": { + "display_name": "Meshy:テクスチャモデル", + "inputs": { + "enable_original_uv": { + "name": "元のUVを使用", + "tooltip": "新しいUVを生成する代わりにモデルの元のUVを使用します。有効にすると、Meshyはアップロードされたモデルの既存のテクスチャを保持します。モデルに元のUVがない場合、出力の品質が低下する可能性があります。" + }, + "image_style": { + "name": "イメージスタイル", + "tooltip": "テクスチャリングプロセスをガイドする2D画像です。'text_style_prompt'と同時に使用することはできません。" + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "model": { + "name": "モデル" + }, + "pbr": { + "name": "PBR" + }, + "text_style_prompt": { + "name": "テキストスタイルプロンプト", + "tooltip": "オブジェクトの希望するテクスチャスタイルをテキストで説明してください。最大600文字。'image_style'と同時に使用することはできません。" + } + }, + "outputs": { + "0": { + "name": "モデルファイル", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } } }, "MinimaxHailuoVideoNode": { @@ -7866,6 +9674,52 @@ } } }, + "NormalizeImages": { + "display_name": "画像を正規化", + "inputs": { + "images": { + "name": "画像", + "tooltip": "処理する画像。" + }, + "mean": { + "name": "平均値", + "tooltip": "正規化のための平均値。" + }, + "std": { + "name": "標準偏差", + "tooltip": "正規化のための標準偏差。" + } + }, + "outputs": { + "0": { + "name": "画像", + "tooltip": "処理済み画像" + } + } + }, + "NormalizeVideoLatentStart": { + "description": "ビデオのlatentの初期フレームを、後続の参照フレームの平均値と標準偏差に合わせて正規化します。開始フレームとビデオの残り部分との違いを減らすのに役立ちます。", + "display_name": "NormalizeVideoLatentStart", + "inputs": { + "latent": { + "name": "latent" + }, + "reference_frame_count": { + "name": "reference_frame_count", + "tooltip": "開始フレームの後に参照として使用するlatentフレーム数" + }, + "start_frame_count": { + "name": "start_frame_count", + "tooltip": "最初から正規化するlatentフレーム数" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, "OpenAIChatConfig": { "description": "OpenAIチャットノードの詳細設定オプションを指定できます。", "display_name": "OpenAI ChatGPT 詳細オプション", @@ -8015,6 +9869,9 @@ "name": "マスク", "tooltip": "インペインティング用のオプションマスク(白い部分が置き換えられます)" }, + "model": { + "name": "model" + }, "n": { "name": "生成数", "tooltip": "生成する画像の枚数" @@ -8373,265 +10230,6 @@ } } }, - "PikaImageToVideoNode2_2": { - "description": "画像とプロンプトをPika API v2.2に送信して動画を生成します。", - "display_name": "Pika画像から動画へ", - "inputs": { - "control_after_generate": { - "name": "生成後のコントロール" - }, - "duration": { - "name": "再生時間" - }, - "image": { - "name": "画像", - "tooltip": "動画に変換する画像" - }, - "negative_prompt": { - "name": "ネガティブプロンプト" - }, - "prompt_text": { - "name": "プロンプトテキスト" - }, - "resolution": { - "name": "解像度" - }, - "seed": { - "name": "シード" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaScenesV2_2": { - "description": "複数の画像を組み合わせて、そこに含まれるオブジェクトを使ったビデオを作成します。複数の画像を素材としてアップロードし、それらすべてを取り入れた高品質なビデオを生成します。", - "display_name": "Pika Scenes(ビデオ画像合成)", - "inputs": { - "aspect_ratio": { - "name": "アスペクト比", - "tooltip": "アスペクト比(幅 / 高さ)" - }, - "control_after_generate": { - "name": "生成後のコントロール" - }, - "duration": { - "name": "再生時間" - }, - "image_ingredient_1": { - "name": "画像素材1", - "tooltip": "ビデオ作成の素材として使用する画像です。" - }, - "image_ingredient_2": { - "name": "画像素材2", - "tooltip": "ビデオ作成の素材として使用する画像です。" - }, - "image_ingredient_3": { - "name": "画像素材3", - "tooltip": "ビデオ作成の素材として使用する画像です。" - }, - "image_ingredient_4": { - "name": "画像素材4", - "tooltip": "ビデオ作成の素材として使用する画像です。" - }, - "image_ingredient_5": { - "name": "画像素材5", - "tooltip": "ビデオ作成の素材として使用する画像です。" - }, - "ingredients_mode": { - "name": "素材モード" - }, - "negative_prompt": { - "name": "ネガティブプロンプト" - }, - "prompt_text": { - "name": "プロンプト" - }, - "resolution": { - "name": "解像度" - }, - "seed": { - "name": "シード" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaStartEndFrameNode2_2": { - "description": "最初と最後のフレームを組み合わせて動画を生成します。2枚の画像をアップロードして開始点と終了点を定義し、AIがその間を滑らかに遷移させます。", - "display_name": "Pika 開始・終了フレームから動画生成", - "inputs": { - "control_after_generate": { - "name": "生成後のコントロール" - }, - "duration": { - "name": "duration" - }, - "image_end": { - "name": "image_end", - "tooltip": "組み合わせる最後の画像。" - }, - "image_start": { - "name": "image_start", - "tooltip": "組み合わせる最初の画像。" - }, - "negative_prompt": { - "name": "negative_prompt" - }, - "prompt_text": { - "name": "prompt_text" - }, - "resolution": { - "name": "resolution" - }, - "seed": { - "name": "seed" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaTextToVideoNode2_2": { - "description": "テキストプロンプトをPika API v2.2に送信してビデオを生成します。", - "display_name": "Pika テキストからビデオへ", - "inputs": { - "aspect_ratio": { - "name": "アスペクト比", - "tooltip": "アスペクト比(幅 / 高さ)" - }, - "control_after_generate": { - "name": "生成後のコントロール" - }, - "duration": { - "name": "再生時間" - }, - "negative_prompt": { - "name": "ネガティブプロンプト" - }, - "prompt_text": { - "name": "プロンプトテキスト" - }, - "resolution": { - "name": "解像度" - }, - "seed": { - "name": "シード" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikadditions": { - "description": "任意のオブジェクトや画像をビデオに追加できます。ビデオをアップロードし、追加したい内容を指定して、シームレスに統合された結果を作成します。", - "display_name": "Pikadditions(ビデオオブジェクト挿入)", - "inputs": { - "control_after_generate": { - "name": "生成後のコントロール" - }, - "image": { - "name": "画像", - "tooltip": "ビデオに追加する画像です。" - }, - "negative_prompt": { - "name": "ネガティブプロンプト" - }, - "prompt_text": { - "name": "プロンプトテキスト" - }, - "seed": { - "name": "シード" - }, - "video": { - "name": "ビデオ", - "tooltip": "画像を追加するビデオです。" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikaffects": { - "description": "特定のPikaffectを使ってビデオを生成します。対応しているPikaffect:Cake-ify、Crumble、Crush、Decapitate、Deflate、Dissolve、Explode、Eye-pop、Inflate、Levitate、Melt、Peel、Poke、Squish、Ta-da、Tear", - "display_name": "Pikaffects(ビデオエフェクト)", - "inputs": { - "control_after_generate": { - "name": "生成後のコントロール" - }, - "image": { - "name": "画像", - "tooltip": "Pikaffectを適用する参照画像。" - }, - "negative_prompt": { - "name": "ネガティブプロンプト" - }, - "pikaffect": { - "name": "Pikaffect" - }, - "prompt_text": { - "name": "プロンプトテキスト" - }, - "seed": { - "name": "シード" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikaswaps": { - "description": "ビデオ内の任意のオブジェクトや領域を新しい画像やオブジェクトで置き換えます。置換する領域はマスクまたは座標で指定できます。", - "display_name": "Pika Swaps(ビデオオブジェクト置換)", - "inputs": { - "control_after_generate": { - "name": "生成後のコントロール" - }, - "image": { - "name": "image", - "tooltip": "ビデオ内のマスクされたオブジェクトを置き換えるために使用する画像。" - }, - "mask": { - "name": "mask", - "tooltip": "ビデオ内で置換する領域を定義するためのマスクを使用します" - }, - "negative_prompt": { - "name": "negative_prompt" - }, - "prompt_text": { - "name": "prompt_text" - }, - "region_to_modify": { - "name": "変更対象領域", - "tooltip": "変更するオブジェクト/領域の平文での説明。" - }, - "seed": { - "name": "seed" - }, - "video": { - "name": "video", - "tooltip": "オブジェクトを置換するビデオ。" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, "PixverseImageToVideoNode": { "description": "プロンプトと出力サイズに基づいて同期的に動画を生成します。", "display_name": "PixVerse 画像から動画へ", @@ -8786,6 +10384,11 @@ "steps": { "name": "ステップ" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "PorterDuffImageComposite": { @@ -8819,6 +10422,9 @@ "Preview3D": { "display_name": "3Dプレビュー", "inputs": { + "bg_image": { + "name": "bg_image" + }, "camera_info": { "name": "カメラ情報" }, @@ -8830,22 +10436,13 @@ } } }, - "Preview3DAnimation": { - "display_name": "プレビュー 3D - アニメーション", - "inputs": { - "camera_info": { - "name": "カメラ情報" - }, - "model_file": { - "name": "モデルファイル" - } - } - }, "PreviewAny": { "display_name": "プレビュー任意", "inputs": { "preview": { }, + "previewMode": { + }, "source": { "name": "ソース" } @@ -8985,6 +10582,36 @@ } } }, + "RandomCropImages": { + "display_name": "ランダムクロップ画像", + "inputs": { + "control_after_generate": { + "name": "生成後のコントロール" + }, + "height": { + "name": "height", + "tooltip": "クロップ高さ。" + }, + "images": { + "name": "images", + "tooltip": "処理する画像。" + }, + "seed": { + "name": "seed", + "tooltip": "ランダムシード。" + }, + "width": { + "name": "width", + "tooltip": "クロップ幅。" + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "処理済み画像" + } + } + }, "RandomNoise": { "display_name": "ランダムノイズ", "inputs": { @@ -8994,6 +10621,11 @@ "noise_seed": { "name": "ノイズシード" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RebatchImages": { @@ -9034,6 +10666,11 @@ "audio": { "name": "オーディオ" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RecraftColorRGB": { @@ -9538,6 +11175,11 @@ "image": { "name": "画像" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RepeatLatentBatch": { @@ -9551,6 +11193,51 @@ } } }, + "ReplaceText": { + "display_name": "テキスト置換", + "inputs": { + "find": { + "name": "find", + "tooltip": "検索するテキスト。" + }, + "replace": { + "name": "replace", + "tooltip": "置換後のテキスト。" + }, + "texts": { + "name": "texts", + "tooltip": "処理するテキスト。" + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "処理済みテキスト" + } + } + }, + "ReplaceVideoLatentFrames": { + "display_name": "ReplaceVideoLatentFrames", + "inputs": { + "destination": { + "name": "destination", + "tooltip": "フレームが置き換えられる宛先latent。" + }, + "index": { + "name": "index", + "tooltip": "宛先latent内でソースlatentフレームを配置する開始フレームインデックス。負の値は末尾からカウントします。" + }, + "source": { + "name": "source", + "tooltip": "宛先latentに挿入するフレームを提供するソースlatent。指定しない場合、宛先latentは変更されません。" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "RescaleCFG": { "display_name": "CFGを再スケール", "inputs": { @@ -9580,6 +11267,104 @@ "target_width": { "name": "ターゲット幅" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ResizeImageMaskNode": { + "description": "さまざまなスケーリング方法で画像またはマスクをリサイズします。", + "display_name": "画像/マスクのリサイズ", + "inputs": { + "input": { + "name": "input" + }, + "resize_type": { + "name": "resize_type", + "tooltip": "リサイズ方法を選択します:正確な寸法、スケール係数、他の画像に合わせるなど。" + }, + "resize_type_crop": { + "name": "切り抜き" + }, + "resize_type_height": { + "name": "高さ" + }, + "resize_type_width": { + "name": "幅" + }, + "scale_method": { + "name": "scale_method", + "tooltip": "補間アルゴリズム。「area」は縮小に最適、「lanczos」は拡大に最適、「nearest-exact」はドット絵に最適です。" + } + }, + "outputs": { + "0": { + "name": "resized", + "tooltip": null + } + } + }, + "ResizeImagesByLongerEdge": { + "display_name": "長辺で画像をリサイズ", + "inputs": { + "images": { + "name": "images", + "tooltip": "処理する画像。" + }, + "longer_edge": { + "name": "longer_edge", + "tooltip": "長辺の目標長さ。" + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "処理済み画像" + } + } + }, + "ResizeImagesByShorterEdge": { + "display_name": "短辺で画像をリサイズ", + "inputs": { + "images": { + "name": "images", + "tooltip": "処理する画像。" + }, + "shorter_edge": { + "name": "shorter_edge", + "tooltip": "短辺の目標長さ。" + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "処理済み画像" + } + } + }, + "ResolutionBucket": { + "display_name": "解像度バケット", + "inputs": { + "conditioning": { + "name": "conditioning", + "tooltip": "conditioningリストのリスト(latentsの長さと一致する必要があります)。" + }, + "latents": { + "name": "latents", + "tooltip": "解像度ごとにバケット化するlatent辞書のリスト。" + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "解像度バケットごとのバッチ化されたlatent辞書のリスト。" + }, + "1": { + "name": "conditioning", + "tooltip": "解像度バケットごとのconditionリスト。" + } } }, "Rodin3D_Detail": { @@ -9833,6 +11618,11 @@ "steps": { "name": "ステップ" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SD_4XUpscale_Conditioning": { @@ -9986,14 +11776,14 @@ "name": "シグマ" } }, - "outputs": { - "0": { - "name": "出力" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "デノイズ出力" + { + "tooltip": null } - } + ] }, "SamplerCustomAdvanced": { "display_name": "カスタムサンプラー(高度)", @@ -10014,14 +11804,14 @@ "name": "シグマ" } }, - "outputs": { - "0": { - "name": "出力" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "デノイズ出力" + { + "tooltip": null } - } + ] }, "SamplerDPMAdaptative": { "display_name": "サンプラーDPM適応", @@ -10056,6 +11846,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_2M_SDE": { @@ -10073,6 +11868,11 @@ "solver_type": { "name": "ソルバータイプ" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_2S_Ancestral": { @@ -10084,6 +11884,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_3M_SDE": { @@ -10098,6 +11903,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_SDE": { @@ -10115,6 +11925,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerER_SDE": { @@ -10133,6 +11948,11 @@ "solver_type": { "name": "solver_type" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerAncestral": { @@ -10144,6 +11964,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerAncestralCFGPP": { @@ -10155,6 +11980,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerCFGpp": { @@ -10195,6 +12025,11 @@ "order": { "name": "順序" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerSASolver": { @@ -10227,6 +12062,37 @@ "use_pece": { "name": "use_pece" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerSEEDS2": { + "description": "このサンプラーノードは複数のサンプラーを表すことができます:\n\nseeds_2\n- デフォルト設定\n\nexp_heun_2_x0\n- solver_type=phi_2, r=1.0, eta=0.0\n\nexp_heun_2_x0_sde\n- solver_type=phi_2, r=1.0, eta=1.0, s_noise=1.0", + "display_name": "SamplerSEEDS2", + "inputs": { + "eta": { + "name": "eta", + "tooltip": "ストカスティック強度" + }, + "r": { + "name": "r", + "tooltip": "中間段階の相対ステップサイズ(c2ノード)" + }, + "s_noise": { + "name": "s_noise", + "tooltip": "SDEノイズ倍率" + }, + "solver_type": { + "name": "solver_type" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplingPercentToSigma": { @@ -10243,11 +12109,11 @@ "name": "sampling_percent" } }, - "outputs": { - "0": { - "name": "sigma_value" + "outputs": [ + { + "tooltip": null } - } + ] }, "SaveAnimatedPNG": { "display_name": "アニメーションPNGを保存", @@ -10365,6 +12231,44 @@ } } }, + "SaveImageDataSetToFolder": { + "display_name": "画像データセットをフォルダに保存", + "inputs": { + "filename_prefix": { + "name": "filename_prefix", + "tooltip": "保存する画像ファイル名の接頭辞。" + }, + "folder_name": { + "name": "folder_name", + "tooltip": "画像を保存するフォルダ名(出力ディレクトリ内)。" + }, + "images": { + "name": "images", + "tooltip": "保存する画像のリスト。" + } + } + }, + "SaveImageTextDataSetToFolder": { + "display_name": "画像とテキストのデータセットをフォルダに保存", + "inputs": { + "filename_prefix": { + "name": "filename_prefix", + "tooltip": "保存する画像ファイル名の接頭辞。" + }, + "folder_name": { + "name": "folder_name", + "tooltip": "画像を保存するフォルダ名(出力ディレクトリ内)。" + }, + "images": { + "name": "images", + "tooltip": "保存する画像のリスト。" + }, + "texts": { + "name": "texts", + "tooltip": "保存するテキストキャプションのリスト。" + } + } + }, "SaveImageWebsocket": { "display_name": "画像を保存するWebSocket", "inputs": { @@ -10384,20 +12288,20 @@ } } }, - "SaveLoRANode": { - "display_name": "LoRA重み保存", + "SaveLoRA": { + "display_name": "LoRA重みを保存", "inputs": { "lora": { "name": "lora", - "tooltip": "保存するLoRAモデル。LoRAレイヤーを持つモデルは使用しないでください。" + "tooltip": "保存するLoRAモデル。LoRAレイヤー付きのモデルは使用しないでください。" }, "prefix": { "name": "prefix", - "tooltip": "保存するLoRAファイルに使用するプレフィックス。" + "tooltip": "保存するLoRAファイルの接頭辞。" }, "steps": { - "name": "ステップ数", - "tooltip": "オプション: LoRAが学習されたステップ数。保存ファイルの命名に使用されます。" + "name": "steps", + "tooltip": "オプション:LoRAが学習されたステップ数。保存ファイル名に使用されます。" } } }, @@ -10414,6 +12318,27 @@ } } }, + "SaveTrainingDataset": { + "display_name": "学習データセットを保存", + "inputs": { + "conditioning": { + "name": "conditioning", + "tooltip": "MakeTrainingDatasetからのconditioningリストのリスト。" + }, + "folder_name": { + "name": "folder_name", + "tooltip": "データセットを保存するフォルダ名(出力ディレクトリ内)。" + }, + "latents": { + "name": "latents", + "tooltip": "MakeTrainingDatasetからのlatent辞書のリスト。" + }, + "shard_size": { + "name": "shard_size", + "tooltip": "シャードファイルごとのサンプル数。" + } + } + }, "SaveVideo": { "description": "入力画像をComfyUIの出力ディレクトリに保存します。", "display_name": "ビデオを保存", @@ -10534,6 +12459,11 @@ "sigmas": { "name": "シグマ" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SetHookKeyframes": { @@ -10574,6 +12504,58 @@ } } }, + "ShuffleDataset": { + "display_name": "画像データセットをシャッフル", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images", + "tooltip": "処理する画像のリスト。" + }, + "seed": { + "name": "seed", + "tooltip": "ランダムシード。" + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "処理済み画像" + } + } + }, + "ShuffleImageTextDataset": { + "display_name": "画像・テキストデータセットをシャッフル", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images", + "tooltip": "シャッフルする画像のリスト。" + }, + "seed": { + "name": "seed", + "tooltip": "ランダムシード。" + }, + "texts": { + "name": "texts", + "tooltip": "シャッフルするテキストのリスト。" + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "シャッフルされた画像" + }, + "1": { + "name": "texts", + "tooltip": "シャッフルされたテキスト" + } + } + }, "SkipLayerGuidanceDiT": { "description": "すべてのDiTモデルで使用できるSkipLayerGuidanceノードの一般的なバージョン。", "display_name": "SkipLayerGuidanceDiT", @@ -10670,6 +12652,11 @@ "width": { "name": "幅" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SplitAudioChannels": { @@ -10680,14 +12667,14 @@ "name": "オーディオ" } }, - "outputs": { - "0": { - "name": "左チャンネル" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "右チャンネル" + { + "tooltip": null } - } + ] }, "SplitImageWithAlpha": { "display_name": "アルファで画像を分割", @@ -10715,14 +12702,14 @@ "name": "ステップ" } }, - "outputs": { - "0": { - "name": "高シグマ" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "低シグマ" + { + "tooltip": null } - } + ] }, "SplitSigmasDenoise": { "display_name": "シグマを分割してノイズ除去", @@ -10734,14 +12721,14 @@ "name": "シグマ" } }, - "outputs": { - "0": { - "name": "高シグマ" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "低シグマ" + { + "tooltip": null } - } + ] }, "StabilityAudioInpaint": { "description": "テキスト指示を使用して既存のオーディオサンプルの一部を変換します。", @@ -11343,6 +13330,21 @@ } } }, + "StripWhitespace": { + "display_name": "空白文字の削除", + "inputs": { + "texts": { + "name": "テキスト", + "tooltip": "処理するテキスト。" + } + }, + "outputs": { + "0": { + "name": "テキスト", + "tooltip": "処理済みテキスト" + } + } + }, "StyleModelApply": { "display_name": "スタイルモデルを適用", "inputs": { @@ -11428,6 +13430,84 @@ } } }, + "TencentImageToModelNode": { + "display_name": "Hunyuan3D: 画像からモデルへ (Pro)", + "inputs": { + "control_after_generate": { + "name": "生成後のコントロール" + }, + "face_count": { + "name": "面数" + }, + "generate_type": { + "name": "生成タイプ" + }, + "generate_type_pbr": { + "name": "PBR" + }, + "image": { + "name": "画像" + }, + "image_back": { + "name": "背面画像" + }, + "image_left": { + "name": "左画像" + }, + "image_right": { + "name": "右画像" + }, + "model": { + "name": "モデル", + "tooltip": "`3.1`モデルではLowPolyオプションは利用できません。" + }, + "seed": { + "name": "シード", + "tooltip": "シードはノードの再実行を制御しますが、シードに関わらず結果は非決定的です。" + } + }, + "outputs": { + "0": { + "name": "モデルファイル", + "tooltip": null + } + } + }, + "TencentTextToModelNode": { + "display_name": "Hunyuan3D: テキストからモデルへ (Pro)", + "inputs": { + "control_after_generate": { + "name": "生成後のコントロール" + }, + "face_count": { + "name": "面数" + }, + "generate_type": { + "name": "生成タイプ" + }, + "generate_type_pbr": { + "name": "PBR" + }, + "model": { + "name": "モデル", + "tooltip": "`3.1`モデルではLowPolyオプションは利用できません。" + }, + "prompt": { + "name": "プロンプト", + "tooltip": "最大1024文字まで対応しています。" + }, + "seed": { + "name": "シード", + "tooltip": "シードはノードの再実行を制御しますが、シードに関わらず結果は非決定的です。" + } + }, + "outputs": { + "0": { + "name": "モデルファイル", + "tooltip": null + } + } + }, "TextEncodeAceStepAudio": { "display_name": "TextEncodeAceStepAudio", "inputs": { @@ -11523,6 +13603,70 @@ } } }, + "TextEncodeZImageOmni": { + "display_name": "TextEncodeZImageOmni", + "inputs": { + "auto_resize_images": { + "name": "画像自動リサイズ" + }, + "clip": { + "name": "clip" + }, + "image1": { + "name": "画像1" + }, + "image2": { + "name": "画像2" + }, + "image3": { + "name": "画像3" + }, + "image_encoder": { + "name": "画像エンコーダ" + }, + "prompt": { + "name": "プロンプト" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TextToLowercase": { + "display_name": "テキストを小文字に変換", + "inputs": { + "texts": { + "name": "テキスト", + "tooltip": "処理するテキスト。" + } + }, + "outputs": { + "0": { + "name": "テキスト", + "tooltip": "処理済みテキスト" + } + } + }, + "TextToUppercase": { + "display_name": "テキストを大文字に変換", + "inputs": { + "texts": { + "name": "テキスト", + "tooltip": "処理するテキスト。" + } + }, + "outputs": { + "0": { + "name": "テキスト", + "tooltip": "処理済みテキスト" + } + } + }, "ThresholdMask": { "display_name": "しきい値マスク", "inputs": { @@ -11532,6 +13676,11 @@ "value": { "name": "値" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "TomePatchModel": { @@ -11550,6 +13699,118 @@ } } }, + "TopazImageEnhance": { + "description": "業界標準のアップスケーリングと画像強化。", + "display_name": "Topaz画像強化", + "inputs": { + "color_preservation": { + "name": "色の保持", + "tooltip": "元の色を保持します。" + }, + "creativity": { + "name": "クリエイティビティ" + }, + "crop_to_fill": { + "name": "クロップしてフィル", + "tooltip": "デフォルトでは、出力アスペクト比が異なる場合はレターボックス化されます。有効にすると、画像をクロップして出力サイズに合わせます。" + }, + "face_enhancement": { + "name": "顔強化", + "tooltip": "処理中に顔(存在する場合)を強化します。" + }, + "face_enhancement_creativity": { + "name": "顔強化クリエイティビティ", + "tooltip": "顔強化の創造性レベルを設定します。" + }, + "face_enhancement_strength": { + "name": "顔強化の強度", + "tooltip": "強化された顔のシャープさを背景と比較して調整します。" + }, + "face_preservation": { + "name": "顔の保持", + "tooltip": "被写体の顔の特徴を保持します。" + }, + "image": { + "name": "画像" + }, + "model": { + "name": "モデル" + }, + "output_height": { + "name": "出力高さ", + "tooltip": "0の場合は元の高さまたは出力幅と同じ高さで出力します。" + }, + "output_width": { + "name": "出力幅", + "tooltip": "0の場合は自動計算(通常は元のサイズまたはoutput_heightが指定されていればそれを使用)。" + }, + "prompt": { + "name": "プロンプト", + "tooltip": "創造的なアップスケーリングのガイダンス用のオプションテキストプロンプト。" + }, + "subject_detection": { + "name": "被写体検出" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TopazVideoEnhance": { + "description": "強力なアップスケーリングと復元技術で動画に新たな命を吹き込む。", + "display_name": "Topaz Video Enhance", + "inputs": { + "dynamic_compression_level": { + "name": "動的圧縮レベル", + "tooltip": "CQPレベル。" + }, + "interpolation_duplicate": { + "name": "重複フレーム除去", + "tooltip": "入力動画の重複フレームを解析し、削除します。" + }, + "interpolation_duplicate_threshold": { + "name": "重複検出感度", + "tooltip": "重複フレームの検出感度。" + }, + "interpolation_enabled": { + "name": "補間有効" + }, + "interpolation_frame_rate": { + "name": "補間フレームレート", + "tooltip": "出力フレームレート。" + }, + "interpolation_model": { + "name": "補間モデル" + }, + "interpolation_slowmo": { + "name": "スローモーション補間", + "tooltip": "入力動画に適用されるスローモーション係数。例:2の場合、出力は2倍遅くなり、再生時間も2倍になります。" + }, + "upscaler_creativity": { + "name": "アップスケーラー創造性", + "tooltip": "創造性レベル(Starlight (Astra) Creative のみ適用されます)。" + }, + "upscaler_enabled": { + "name": "アップスケーラー有効" + }, + "upscaler_model": { + "name": "アップスケーラーモデル" + }, + "upscaler_resolution": { + "name": "アップスケーラー解像度" + }, + "video": { + "name": "動画" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "TorchCompileModel": { "display_name": "Torchコンパイルモデル", "inputs": { @@ -11577,6 +13838,10 @@ "name": "バッチサイズ", "tooltip": "学習に使用するバッチサイズ。" }, + "bucket_mode": { + "name": "バケットモード", + "tooltip": "解像度バケットモードを有効にします。有効時は、ResolutionBucketノードから事前にバケット化されたlatentを期待します。" + }, "control_after_generate": { "name": "control after generate" }, @@ -11637,20 +13902,20 @@ "tooltip": "トレーニングに使用するデータ型。" } }, - "outputs": { - "0": { - "name": "model_with_lora" + "outputs": [ + { + "tooltip": "LoRA適用済みモデル" }, - "1": { - "name": "lora" + { + "tooltip": "LoRA重み" }, - "2": { - "name": "loss" + { + "tooltip": "損失履歴" }, - "3": { - "name": "steps" + { + "tooltip": "総トレーニングステップ数" } - } + ] }, "TrimAudioDuration": { "description": "オーディオテンソルを選択した時間範囲でトリミングします。", @@ -11667,6 +13932,11 @@ "name": "start_index", "tooltip": "開始時間(秒)。負の値を指定すると末尾からカウント(小数点以下対応)。" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "TrimVideoLatent": { @@ -11708,23 +13978,62 @@ "TripoConversionNode": { "display_name": "Tripo: モデル変換", "inputs": { + "animate_in_place": { + "name": "その場でアニメーション" + }, + "bake": { + "name": "ベイク" + }, + "export_orientation": { + "name": "エクスポート方向" + }, + "export_vertex_colors": { + "name": "頂点カラーをエクスポート" + }, "face_limit": { "name": "face_limit" }, + "fbx_preset": { + "name": "FBXプリセット" + }, + "flatten_bottom": { + "name": "底面を平坦化" + }, + "flatten_bottom_threshold": { + "name": "底面平坦化しきい値" + }, + "force_symmetry": { + "name": "対称性を強制" + }, "format": { "name": "format" }, "original_model_task_id": { "name": "original_model_task_id" }, + "pack_uv": { + "name": "UVパック" + }, + "part_names": { + "name": "パーツ名" + }, + "pivot_to_center_bottom": { + "name": "ピボットを底面中央へ" + }, "quad": { "name": "quad" }, + "scale_factor": { + "name": "スケール係数" + }, "texture_format": { "name": "texture_format" }, "texture_size": { "name": "texture_size" + }, + "with_animation": { + "name": "アニメーション付き" } } }, @@ -11734,6 +14043,9 @@ "face_limit": { "name": "face_limit" }, + "geometry_quality": { + "name": "ジオメトリ品質" + }, "image": { "name": "image" }, @@ -11786,6 +14098,9 @@ "face_limit": { "name": "face_limit" }, + "geometry_quality": { + "name": "ジオメトリ品質" + }, "image": { "name": "image" }, @@ -11903,6 +14218,9 @@ "face_limit": { "name": "face_limit" }, + "geometry_quality": { + "name": "ジオメトリ品質" + }, "image_seed": { "name": "image_seed" }, @@ -11981,6 +14299,25 @@ } } }, + "TruncateText": { + "display_name": "テキストを切り詰める", + "inputs": { + "max_length": { + "name": "最大長", + "tooltip": "テキストの最大長。" + }, + "texts": { + "name": "テキスト", + "tooltip": "処理するテキスト。" + } + }, + "outputs": { + "0": { + "name": "テキスト", + "tooltip": "処理済みテキスト" + } + } + }, "UNETLoader": { "display_name": "拡散モデルを読み込む", "inputs": { @@ -12122,6 +14459,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEDecodeHunyuan3D": { @@ -12139,6 +14481,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEDecodeTiled": { @@ -12186,6 +14533,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEEncodeForInpaint": { @@ -12264,6 +14616,63 @@ "steps": { "name": "ステップ" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Veo3FirstLastFrameNode": { + "description": "プロンプトと最初・最後のフレームを使って動画を生成します。", + "display_name": "Google Veo 3 ファースト・ラストフレームから動画生成", + "inputs": { + "aspect_ratio": { + "name": "アスペクト比", + "tooltip": "出力動画のアスペクト比" + }, + "control_after_generate": { + "name": "生成後のコントロール" + }, + "duration": { + "name": "長さ", + "tooltip": "出力動画の長さ(秒)" + }, + "first_frame": { + "name": "最初のフレーム", + "tooltip": "開始フレーム" + }, + "generate_audio": { + "name": "音声生成", + "tooltip": "動画用の音声を生成します。" + }, + "last_frame": { + "name": "最後のフレーム", + "tooltip": "終了フレーム" + }, + "model": { + "name": "モデル" + }, + "negative_prompt": { + "name": "ネガティブプロンプト", + "tooltip": "動画で避けたい内容を指定するネガティブテキストプロンプト" + }, + "prompt": { + "name": "プロンプト", + "tooltip": "動画のテキスト説明" + }, + "resolution": { + "name": "解像度" + }, + "seed": { + "name": "シード値", + "tooltip": "動画生成用のシード値" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Veo3VideoGenerationNode": { @@ -12392,6 +14801,166 @@ } } }, + "Vidu2ImageToVideoNode": { + "description": "画像とオプションのプロンプトから動画を生成します。", + "display_name": "Vidu2 画像から動画生成", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "duration": { + "name": "duration" + }, + "image": { + "name": "image", + "tooltip": "生成される動画の開始フレームとして使用する画像です。" + }, + "model": { + "name": "model" + }, + "movement_amplitude": { + "name": "movement_amplitude", + "tooltip": "フレーム内のオブジェクトの動きの振幅。" + }, + "prompt": { + "name": "prompt", + "tooltip": "動画生成用のオプションテキストプロンプト(最大2000文字)。" + }, + "resolution": { + "name": "resolution" + }, + "seed": { + "name": "seed" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2ReferenceVideoNode": { + "description": "複数の参照画像とプロンプトから動画を生成します。", + "display_name": "Vidu2 参照画像から動画生成", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "audio": { + "name": "audio", + "tooltip": "有効にすると、プロンプトに基づいた音声とBGMが動画に含まれます。" + }, + "control_after_generate": { + "name": "control after generate" + }, + "duration": { + "name": "duration" + }, + "model": { + "name": "model" + }, + "movement_amplitude": { + "name": "movement_amplitude", + "tooltip": "フレーム内のオブジェクトの動きの振幅。" + }, + "prompt": { + "name": "prompt", + "tooltip": "有効にすると、プロンプトに基づいた音声とBGMが動画に含まれます。" + }, + "resolution": { + "name": "resolution" + }, + "seed": { + "name": "seed" + }, + "subjects": { + "name": "subjects", + "tooltip": "各被写体ごとに最大3枚の参照画像を指定してください(全被写体で合計7枚まで)。プロンプト内で@subject{subject_id}で参照できます。" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2StartEndToVideoNode": { + "description": "開始フレーム、終了フレーム、およびプロンプトから動画を生成します。", + "display_name": "Vidu2 開始/終了フレームからの動画生成", + "inputs": { + "control_after_generate": { + "name": "生成後に制御" + }, + "duration": { + "name": "duration" + }, + "end_frame": { + "name": "end_frame" + }, + "first_frame": { + "name": "first_frame" + }, + "model": { + "name": "model" + }, + "movement_amplitude": { + "name": "movement_amplitude", + "tooltip": "フレーム内のオブジェクトの動きの振幅。" + }, + "prompt": { + "name": "prompt", + "tooltip": "プロンプトの説明(最大2000文字)。" + }, + "resolution": { + "name": "resolution" + }, + "seed": { + "name": "seed" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2TextToVideoNode": { + "description": "テキストプロンプトから動画を生成します", + "display_name": "Vidu2 テキストから動画生成", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "background_music": { + "name": "background_music", + "tooltip": "生成された動画にBGMを追加するかどうか。" + }, + "control_after_generate": { + "name": "生成後に制御" + }, + "duration": { + "name": "duration" + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "動画生成用のテキスト説明(最大2000文字)。" + }, + "resolution": { + "name": "resolution" + }, + "seed": { + "name": "seed" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "ViduImageToVideoNode": { "description": "画像とオプションのプロンプトからビデオを生成", "display_name": "Vidu 画像からビデオ生成", @@ -12580,6 +15149,11 @@ "voxel": { "name": "voxel" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VoxelToMeshBasic": { @@ -12591,6 +15165,11 @@ "voxel": { "name": "ボクセル" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Wan22FunControlToVideo": { @@ -12870,6 +15449,10 @@ "name": "コンテキストストライド", "tooltip": "コンテキストウィンドウのストライド。均一スケジュールにのみ適用されます。" }, + "freenoise": { + "name": "フリーノイズ", + "tooltip": "FreeNoiseノイズシャッフルを適用するかどうか。ウィンドウのブレンドを改善します。" + }, "fuse_method": { "name": "融合方法", "tooltip": "コンテキストウィンドウを融合するために使用する方法。" @@ -13210,6 +15793,10 @@ "name": "シード", "tooltip": "生成に使用するシード値。" }, + "shot_type": { + "name": "ショットタイプ", + "tooltip": "生成される動画のショットタイプ(単一連続ショットかカットを含む複数ショットか)を指定します。このパラメータはprompt_extendがTrueの時のみ有効です。" + }, "watermark": { "name": "透かし", "tooltip": "結果に「AI生成」の透かしを追加するかどうか。" @@ -13221,6 +15808,196 @@ } } }, + "WanInfiniteTalkToVideo": { + "display_name": "WanInfiniteTalkToVideo", + "inputs": { + "audio_encoder_output_1": { + "name": "オーディオエンコーダ出力1" + }, + "audio_scale": { + "name": "オーディオスケール" + }, + "clip_vision_output": { + "name": "clipビジョン出力" + }, + "height": { + "name": "高さ" + }, + "length": { + "name": "長さ" + }, + "mode": { + "name": "モード" + }, + "model": { + "name": "モデル" + }, + "model_patch": { + "name": "モデルパッチ" + }, + "motion_frame_count": { + "name": "モーションフレーム数", + "tooltip": "動きのコンテキストとして使用する前のフレーム数。" + }, + "negative": { + "name": "ネガティブ" + }, + "positive": { + "name": "ポジティブ" + }, + "previous_frames": { + "name": "前のフレーム" + }, + "start_image": { + "name": "開始画像" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "幅" + } + }, + "outputs": { + "0": { + "name": "モデル", + "tooltip": null + }, + "1": { + "name": "ポジティブ", + "tooltip": null + }, + "2": { + "name": "ネガティブ", + "tooltip": null + }, + "3": { + "name": "潜在", + "tooltip": null + }, + "4": { + "name": "トリム画像", + "tooltip": null + } + } + }, + "WanMoveConcatTrack": { + "display_name": "WanMoveConcatTrack", + "inputs": { + "tracks_1": { + "name": "トラック1" + }, + "tracks_2": { + "name": "トラック2" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanMoveTrackToVideo": { + "display_name": "WanMoveTrackToVideo", + "inputs": { + "batch_size": { + "name": "バッチサイズ" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "height": { + "name": "高さ" + }, + "length": { + "name": "長さ" + }, + "negative": { + "name": "negative" + }, + "positive": { + "name": "positive" + }, + "start_image": { + "name": "開始画像" + }, + "strength": { + "name": "強度", + "tooltip": "トラックコンディショニングの強度。" + }, + "tracks": { + "name": "トラック" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "幅" + } + }, + "outputs": { + "0": { + "name": "positive", + "tooltip": null + }, + "1": { + "name": "negative", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "WanMoveTracksFromCoords": { + "display_name": "WanMoveTracksFromCoords", + "inputs": { + "track_coords": { + "name": "トラック座標" + }, + "track_mask": { + "name": "トラックマスク" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "トラック長", + "tooltip": null + } + } + }, + "WanMoveVisualizeTracks": { + "display_name": "WanMoveVisualizeTracks", + "inputs": { + "circle_size": { + "name": "円のサイズ" + }, + "images": { + "name": "画像" + }, + "line_resolution": { + "name": "線の解像度" + }, + "line_width": { + "name": "線の太さ" + }, + "opacity": { + "name": "不透明度" + }, + "tracks": { + "name": "トラック" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WanPhantomSubjectToVideo": { "display_name": "WanPhantomSubjectToVideo", "inputs": { @@ -13268,6 +16045,51 @@ } } }, + "WanReferenceVideoApi": { + "description": "入力動画のキャラクターと声を使用し、プロンプトと組み合わせてキャラクターの一貫性を保った新しい動画を生成します。", + "display_name": "Wan Reference to Video", + "inputs": { + "control_after_generate": { + "name": "生成後のコントロール" + }, + "duration": { + "name": "長さ" + }, + "model": { + "name": "モデル" + }, + "negative_prompt": { + "name": "ネガティブプロンプト", + "tooltip": "避けたい内容を説明するネガティブプロンプト。" + }, + "prompt": { + "name": "プロンプト", + "tooltip": "要素やビジュアル特徴を説明するプロンプト。英語と中国語に対応。`character1`や`character2`などの識別子で参照キャラクターを指定できます。" + }, + "reference_videos": { + "name": "参照動画" + }, + "seed": { + "name": "シード" + }, + "shot_type": { + "name": "ショットタイプ", + "tooltip": "生成される動画のショットタイプ(単一連続ショットか複数カットか)を指定します。" + }, + "size": { + "name": "サイズ" + }, + "watermark": { + "name": "ウォーターマーク", + "tooltip": "AI生成のウォーターマークを結果に追加するかどうか。" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WanSoundImageToVideo": { "display_name": "WanSoundImageToVideo", "inputs": { @@ -13446,6 +16268,10 @@ "name": "seed", "tooltip": "生成に使用するシード値。" }, + "shot_type": { + "name": "ショットタイプ", + "tooltip": "生成される動画のショットタイプ(単一連続ショットか複数カットか)を指定します。このパラメータはprompt_extendがTrueの時のみ有効です。" + }, "size": { "name": "size" }, @@ -13571,6 +16397,43 @@ } } }, + "WavespeedFlashVSRNode": { + "description": "低解像度やぼやけた映像の解像度を向上させ、鮮明さを復元する高速・高品質なビデオアップスケーラーです。", + "display_name": "FlashVSRビデオ高解像度化", + "inputs": { + "target_resolution": { + "name": "目標解像度" + }, + "video": { + "name": "ビデオ" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WavespeedImageUpscaleNode": { + "description": "画像の解像度と品質を向上させ、写真を4Kや8Kにアップスケールしてシャープで詳細な結果を得られます。", + "display_name": "WaveSpeed画像高解像度化", + "inputs": { + "image": { + "name": "画像" + }, + "model": { + "name": "model" + }, + "target_resolution": { + "name": "目標解像度" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WebcamCapture": { "display_name": "ウェブカメラキャプチャ", "inputs": { @@ -13590,6 +16453,32 @@ } } }, + "ZImageFunControlnet": { + "display_name": "ZImageFunControlnet", + "inputs": { + "image": { + "name": "画像" + }, + "inpaint_image": { + "name": "インペイント画像" + }, + "mask": { + "name": "マスク" + }, + "model": { + "name": "モデル" + }, + "model_patch": { + "name": "モデルパッチ" + }, + "strength": { + "name": "強度" + }, + "vae": { + "name": "vae" + } + } + }, "unCLIPCheckpointLoader": { "display_name": "unCLIPチェックポイントローダー", "inputs": { @@ -13614,5 +16503,19 @@ "name": "強度" } } + }, + "wanBlockSwap": { + "description": "NOP", + "display_name": "wanBlockSwap", + "inputs": { + "model": { + "name": "モデル" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } } } diff --git a/src/locales/ja/settings.json b/src/locales/ja/settings.json index d0c473512..2bfd0fe22 100644 --- a/src/locales/ja/settings.json +++ b/src/locales/ja/settings.json @@ -29,12 +29,26 @@ "name": "キャンバス背景画像", "tooltip": "キャンバスの背景画像のURLです。出力パネルで画像を右クリックし、「背景として設定」を選択すると使用できます。" }, + "Comfy_Canvas_LeftMouseClickBehavior": { + "name": "左クリックの動作", + "options": { + "Panning": "パン(移動)", + "Select": "選択" + } + }, + "Comfy_Canvas_MouseWheelScroll": { + "name": "マウスホイールスクロール", + "options": { + "Panning": "パン(移動)", + "Zoom in/out": "ズームイン/アウト" + } + }, "Comfy_Canvas_NavigationMode": { "name": "キャンバスナビゲーションモード", "options": { + "Custom": "カスタム", "Drag Navigation": "ドラッグナビゲーション", - "Standard (New)": "標準(新)", - "Custom": "カスタム" + "Standard (New)": "標準(新)" } }, "Comfy_Canvas_SelectionToolbox": { @@ -65,6 +79,17 @@ "Comfy_EnableWorkflowViewRestore": { "name": "ワークフロー内のキャンバスの位置とズームレベルを保存および復元する" }, + "Comfy_Execution_PreviewMethod": { + "name": "ライブプレビューメソッド", + "options": { + "auto": "auto", + "default": "default", + "latent2rgb": "latent2rgb", + "none": "none", + "taesd": "taesd" + }, + "tooltip": "画像生成中のライブプレビューメソッド。「default」はサーバーのCLI設定を使用します。" + }, "Comfy_FloatRoundingPrecision": { "name": "浮動小数点ウィジェットの丸め小数点数 [0 = 自動]。", "tooltip": "(ページの再読み込みが必要)" @@ -86,6 +111,10 @@ "None": "なし" } }, + "Comfy_Graph_LiveSelection": { + "name": "ライブ選択", + "tooltip": "有効にすると、他のデザインツールのように選択範囲をドラッグする際、ノードがリアルタイムで選択/解除されます。" + }, "Comfy_Graph_ZoomSpeed": { "name": "キャンバスズーム速度" }, @@ -152,6 +181,15 @@ "name": "光の強度の最小値", "tooltip": "3Dシーンで許可される光の強度の最小値を設定します。これは、3Dウィジェットで照明を調整する際に設定できる明るさの下限を定義します。" }, + "Comfy_Load3D_PLYEngine": { + "name": "PLYエンジン", + "options": { + "fastply": "fastply", + "sparkjs": "sparkjs", + "threejs": "threejs" + }, + "tooltip": "PLYファイルを読み込むエンジンを選択します。「threejs」はネイティブのThree.js PLYLoaderを使用します(メッシュPLYファイルに最適)。「fastply」はASCIIポイントクラウドPLYファイル用の最適化されたローダーです。「sparkjs」は3D Gaussian Splatting PLYファイル用のSpark.jsを使用します。" + }, "Comfy_Load3D_ShowGrid": { "name": "グリッドを表示", "tooltip": "デフォルトでグリッドを表示するには切り替えます" @@ -167,10 +205,6 @@ "name": "ブラシ調整を優先軸に固定する", "tooltip": "有効にすると、ブラシの調整は、どの方向に多く動かすかに基づいて、サイズまたは硬さのいずれかにのみ影響します。" }, - "Comfy_MaskEditor_UseNewEditor": { - "name": "新しいマスクエディタを使用する", - "tooltip": "新しいマスクエディタインターフェースに切り替えます。" - }, "Comfy_ModelLibrary_AutoLoadAll": { "name": "すべてのモデルフォルダーを自動的に読み込む", "tooltip": "trueの場合、モデルライブラリを開くとすぐにすべてのフォルダーが読み込まれます(これにより読み込み中に遅延が発生する可能性があります)。falseの場合、ルートレベルのモデルフォルダーはクリックするまで読み込まれません。" @@ -238,6 +272,10 @@ "Comfy_Node_AllowImageSizeDraw": { "name": "画像プレビューの下に幅×高さを表示する" }, + "Comfy_Node_AlwaysShowAdvancedWidgets": { + "name": "すべてのノードで常に高度なウィジェットを表示", + "tooltip": "有効にすると、すべてのノードで高度なウィジェットが個別に展開しなくても常に表示されます。" + }, "Comfy_Node_AutoSnapLinkToSlot": { "name": "ノードスロットにリンクを自動スナップ", "tooltip": "ノードの上にリンクをドラッグすると、リンクがノードの有効な入力スロットに自動的にスナップします" @@ -298,6 +336,10 @@ "name": "キュー履歴サイズ", "tooltip": "キュー履歴に表示されるタスクの最大数。" }, + "Comfy_Queue_QPOV2": { + "name": "アセットサイドパネルで統一ジョブキューを使用", + "tooltip": "フローティングジョブキューパネルを、アセットサイドパネルに埋め込まれた同等のジョブキューに置き換えます。無効にすると、フローティングパネルのレイアウトに戻ります。" + }, "Comfy_Sidebar_Location": { "name": "サイドバーの位置", "options": { @@ -312,6 +354,13 @@ "small": "小" } }, + "Comfy_Sidebar_Style": { + "name": "サイドバーのスタイル", + "options": { + "connected": "接続", + "floating": "フローティング" + } + }, "Comfy_Sidebar_UnifiedWidth": { "name": "サイドバーの幅を統一" }, @@ -328,6 +377,14 @@ "Comfy_TreeExplorer_ItemPadding": { "name": "ツリーエクスプローラーアイテムのパディング" }, + "Comfy_UI_TabBarLayout": { + "name": "タブバーのレイアウト", + "options": { + "Default": "デフォルト", + "Integrated": "統合" + }, + "tooltip": "タブバーのレイアウトを制御します。「統合」を選択すると、ヘルプとユーザーコントロールがタブバーエリアに移動します。" + }, "Comfy_UseNewMenu": { "name": "新しいメニューを使用", "options": { @@ -339,6 +396,14 @@ "Comfy_Validation_Workflows": { "name": "ワークフローを検証" }, + "Comfy_VueNodes_AutoScaleLayout": { + "name": "自動スケールレイアウト(Vueノード)", + "tooltip": "Vueレンダリングに切り替えた際にノードの重なりを防ぐため、自動的にノード位置をスケーリングします" + }, + "Comfy_VueNodes_Enabled": { + "name": "モダンノードデザイン(Vueノード)", + "tooltip": "モダン:DOMベースのレンダリングで、操作性の向上、ネイティブブラウザ機能、最新のビジュアルデザインを実現。クラシック:従来のキャンバスレンダリング。" + }, "Comfy_WidgetControlMode": { "name": "ウィジェット制御モード", "options": { @@ -376,6 +441,9 @@ "Comfy_Workflow_SortNodeIdOnSave": { "name": "ワークフローを保存する際にノードIDをソート" }, + "Comfy_Workflow_WarnBlueprintOverwrite": { + "name": "既存のサブグラフブループリントを上書きする際に確認を要求" + }, "Comfy_Workflow_WorkflowTabsPosition": { "name": "開いているワークフローの位置", "options": { @@ -407,37 +475,5 @@ }, "pysssss_SnapToGrid": { "name": "常にグリッドにスナップ" - }, - "Comfy_Canvas_LeftMouseClickBehavior": { - "name": "左クリックの動作", - "options": { - "Panning": "パン(移動)", - "Select": "選択" - } - }, - "Comfy_Canvas_MouseWheelScroll": { - "name": "マウスホイールスクロール", - "options": { - "Panning": "パン(移動)", - "Zoom in/out": "ズームイン/アウト" - } - }, - "Comfy_Sidebar_Style": { - "name": "サイドバーのスタイル", - "options": { - "floating": "フローティング", - "connected": "接続" - } - }, - "Comfy_VueNodes_AutoScaleLayout": { - "name": "自動スケールレイアウト(Vueノード)", - "tooltip": "Vueレンダリングに切り替えた際にノードの重なりを防ぐため、自動的にノード位置をスケーリングします" - }, - "Comfy_VueNodes_Enabled": { - "name": "モダンノードデザイン(Vueノード)", - "tooltip": "モダン:DOMベースのレンダリングで、操作性の向上、ネイティブブラウザ機能、最新のビジュアルデザインを実現。クラシック:従来のキャンバスレンダリング。" - }, - "Comfy_Workflow_WarnBlueprintOverwrite": { - "name": "既存のサブグラフブループリントを上書きする際に確認を要求" } } diff --git a/src/locales/ko/commands.json b/src/locales/ko/commands.json index d084ece39..c3c801373 100644 --- a/src/locales/ko/commands.json +++ b/src/locales/ko/commands.json @@ -1,4 +1,40 @@ { + "Comfy-Desktop_CheckForUpdates": { + "label": "업데이트 확인" + }, + "Comfy-Desktop_Folders_OpenCustomNodesFolder": { + "label": "커스텀 노드 폴더 열기" + }, + "Comfy-Desktop_Folders_OpenInputsFolder": { + "label": "입력 폴더 열기" + }, + "Comfy-Desktop_Folders_OpenLogsFolder": { + "label": "로그 폴더 열기" + }, + "Comfy-Desktop_Folders_OpenModelConfig": { + "label": "extra_model_paths.yaml 열기" + }, + "Comfy-Desktop_Folders_OpenModelsFolder": { + "label": "모델 폴더 열기" + }, + "Comfy-Desktop_Folders_OpenOutputsFolder": { + "label": "출력 폴더 열기" + }, + "Comfy-Desktop_OpenDevTools": { + "label": "개발자 도구 열기" + }, + "Comfy-Desktop_OpenUserGuide": { + "label": "데스크톱 사용자 가이드" + }, + "Comfy-Desktop_Quit": { + "label": "종료" + }, + "Comfy-Desktop_Reinstall": { + "label": "재설치" + }, + "Comfy-Desktop_Restart": { + "label": "재시작" + }, "Comfy_3DViewer_Open3DViewer": { "label": "선택한 노드에 대해 3D 뷰어(베타) 열기" }, @@ -155,18 +191,30 @@ "Comfy_Manager_ShowUpdateAvailablePacks": { "label": "업데이트 확인" }, - "Comfy_Manager_ToggleManagerProgressDialog": { - "label": "진행 상황 대화 상자 전환" - }, "Comfy_MaskEditor_BrushSize_Decrease": { "label": "마스크 편집기에서 브러시 크기 줄이기" }, "Comfy_MaskEditor_BrushSize_Increase": { "label": "마스크 편집기에서 브러시 크기 늘리기" }, + "Comfy_MaskEditor_ColorPicker": { + "label": "MaskEditor에서 색상 선택기 열기" + }, + "Comfy_MaskEditor_Mirror_Horizontal": { + "label": "마스크 에디터에서 수평 반전" + }, + "Comfy_MaskEditor_Mirror_Vertical": { + "label": "마스크 에디터에서 수직 반전" + }, "Comfy_MaskEditor_OpenMaskEditor": { "label": "선택한 노드 마스크 편집기 열기" }, + "Comfy_MaskEditor_Rotate_Left": { + "label": "마스크 에디터에서 왼쪽으로 회전" + }, + "Comfy_MaskEditor_Rotate_Right": { + "label": "마스크 에디터에서 오른쪽으로 회전" + }, "Comfy_Memory_UnloadModels": { "label": "모델 언로드" }, @@ -197,12 +245,18 @@ "Comfy_QueueSelectedOutputNodes": { "label": "선택한 출력 노드 대기열에 추가" }, + "Comfy_Queue_ToggleOverlay": { + "label": "작업 기록 토글" + }, "Comfy_Redo": { "label": "다시 실행" }, "Comfy_RefreshNodeDefinitions": { "label": "노드 정의 새로 고침" }, + "Comfy_RenameWorkflow": { + "label": "워크플로우 이름 변경" + }, "Comfy_SaveWorkflow": { "label": "워크플로 저장" }, @@ -221,6 +275,12 @@ "Comfy_ToggleHelpCenter": { "label": "도움말 센터" }, + "Comfy_ToggleLinear": { + "label": "선형 모드 토글" + }, + "Comfy_ToggleQPOV2": { + "label": "Queue Panel V2 토글" + }, "Comfy_ToggleTheme": { "label": "밝기 테마 전환 (어두운/밝은)" }, diff --git a/src/locales/ko/main.json b/src/locales/ko/main.json index 599093784..1b0c68ec1 100644 --- a/src/locales/ko/main.json +++ b/src/locales/ko/main.json @@ -1,6 +1,8 @@ { "actionbar": { - "dockToTop": "상단에 도킹" + "dockToTop": "상단에 도킹", + "feedback": "피드백", + "feedbackTooltip": "피드백" }, "apiNodesCostBreakdown": { "costPerRun": "실행 당 비용", @@ -18,23 +20,141 @@ "assetCard": "{name} - {type} 에셋", "loadingAsset": "에셋 로드 중" }, + "assetCollection": "에셋 컬렉션", "assets": "에셋", "baseModels": "베이스 모델", "browseAssets": "에셋 탐색", + "byType": "유형별", + "checkpoints": "체크포인트", + "civitaiLinkExample": "{example} {link}", + "civitaiLinkExampleStrong": "예시:", + "civitaiLinkExampleUrl": "https://civitai.com/models/10706/luisap-z-image-and-qwen-pixel-art-refiner?modelVersionId=2225295", + "civitaiLinkLabel": "Civitai 모델 {download} 링크", + "civitaiLinkLabelDownload": "다운로드", + "civitaiLinkPlaceholder": "여기에 링크를 붙여넣으세요", + "confirmModelDetails": "모델 세부 정보 확인", "connectionError": "연결을 확인하고 다시 시도하세요", + "deletion": { + "body": "이 모델은 라이브러리에서 영구적으로 삭제됩니다.", + "complete": "{assetName}이(가) 삭제되었습니다.", + "failed": "{assetName}을(를) 삭제할 수 없습니다.", + "header": "이 모델을 삭제하시겠습니까?", + "inProgress": "{assetName} 삭제 중..." + }, + "download": { + "complete": "다운로드 완료", + "failed": "다운로드 실패", + "inProgress": "{assetName} 다운로드 중..." + }, + "emptyImported": { + "canImport": "아직 가져온 모델이 없습니다. \"모델 가져오기\"를 클릭하여 직접 추가하세요.", + "restricted": "개인 모델은 Creator 등급 이상에서만 사용할 수 있습니다." + }, + "errorFileTooLarge": "파일이 허용된 최대 크기 제한을 초과했습니다", + "errorFormatNotAllowed": "SafeTensor 형식만 허용됩니다", + "errorModelTypeNotSupported": "이 모델 유형은 지원되지 않습니다", + "errorUnknown": "예기치 않은 오류가 발생했습니다", + "errorUnsafePickleScan": "CivitAI에서 이 파일에 잠재적으로 안전하지 않은 코드를 감지했습니다", + "errorUnsafeVirusScan": "CivitAI에서 이 파일에 악성코드 또는 의심스러운 내용을 감지했습니다", + "errorUploadFailed": "에셋 가져오기에 실패했습니다. 다시 시도해 주세요.", "failedToCreateNode": "노드 생성에 실패했습니다. 다시 시도하거나 콘솔에서 세부 정보를 확인하세요.", "fileFormats": "파일 형식", + "fileName": "파일 이름", + "fileSize": "파일 크기", + "filterBy": "필터 기준", + "findInLibrary": "모델 라이브러리의 {type} 섹션에서 찾을 수 있습니다.", + "finish": "완료", + "genericLinkPlaceholder": "여기에 링크를 붙여넣으세요", + "importAnother": "다른 항목 가져오기", + "imported": "가져온 항목", + "jobId": "작업 ID", "loadingModels": "{type} 불러오는 중...", + "maxFileSize": "최대 파일 크기: {size}", + "maxFileSizeValue": "1 GB", + "media": { + "audioPlaceholder": "오디오", + "threeDModelPlaceholder": "3D 모델" + }, + "modelAssociatedWithLink": "제공하신 링크와 연결된 모델:", + "modelInfo": { + "addBaseModel": "베이스 모델 추가...", + "addTag": "태그 추가...", + "additionalTags": "추가 태그", + "baseModelUnknown": "베이스 모델 알 수 없음", + "basicInfo": "기본 정보", + "compatibleBaseModels": "호환 가능한 베이스 모델", + "description": "설명", + "descriptionNotSet": "설정된 설명 없음", + "descriptionPlaceholder": "이 모델에 대한 설명을 추가하세요...", + "displayName": "표시 이름", + "editDisplayName": "표시 이름 편집", + "fileName": "파일 이름", + "modelDescription": "모델 설명", + "modelTagging": "모델 태깅", + "modelType": "모델 유형", + "noAdditionalTags": "추가 태그 없음", + "selectModelPrompt": "모델을 선택하여 정보를 확인하세요", + "selectModelType": "모델 유형 선택...", + "source": "소스", + "title": "모델 정보", + "triggerPhrases": "트리거 문구", + "viewOnSource": "{source}에서 보기" + }, + "modelName": "모델 이름", + "modelNamePlaceholder": "이 모델의 이름을 입력하세요", + "modelTypeSelectorLabel": "모델 유형은 무엇인가요?", + "modelTypeSelectorPlaceholder": "모델 유형 선택", + "modelUploaded": "모델이 성공적으로 가져와졌습니다.", "noAssetsFound": "에셋을 찾을 수 없습니다", "noModelsInFolder": "이 폴더에 사용 가능한 {type}이(가) 없습니다", - "searchAssetsPlaceholder": "에셋 검색...", + "noValidSourceDetected": "유효한 가져오기 소스를 감지하지 못했습니다", + "notSureLeaveAsIs": "잘 모르겠다면 그대로 두세요", + "onlyCivitaiUrlsSupported": "Civitai URL만 지원됩니다", + "ownership": "소유권", + "ownershipAll": "전체", + "ownershipMyModels": "내 모델", + "ownershipPublicModels": "공개 모델", + "processingModel": "다운로드 시작됨", + "processingModelDescription": "이 대화 상자를 닫아도 됩니다. 다운로드는 백그라운드에서 계속됩니다.", + "providerCivitai": "Civitai", + "providerHuggingFace": "Hugging Face", + "rename": { + "failed": "에셋 이름을 변경할 수 없습니다." + }, + "selectFrameworks": "프레임워크 선택", + "selectModelType": "모델 유형 선택", + "selectProjects": "프로젝트 선택", "sortAZ": "가나다순", "sortBy": "정렬 기준", "sortPopular": "인기순", "sortRecent": "최근", "sortZA": "가나다 역순", + "sortingType": "정렬 방식", + "tags": "태그", + "tagsHelp": "태그는 쉼표로 구분하세요", + "tagsPlaceholder": "예: models, checkpoint", "tryAdjustingFilters": "검색 또는 필터를 조정해 보세요", - "unknown": "알 수 없음" + "unknown": "알 수 없음", + "unsupportedUrlSource": "{sources}의 URL만 지원됩니다", + "upgradeFeatureDescription": "이 기능은 Creator 또는 Pro 요금제에서만 사용할 수 있습니다.", + "upgradeToUnlockFeature": "이 기능을 사용하려면 업그레이드하세요", + "upload": "가져오기", + "uploadFailed": "가져오기 실패", + "uploadModel": "가져오기", + "uploadModelDescription1": "Civitai 모델 다운로드 링크를 붙여넣어 라이브러리에 추가하세요.", + "uploadModelDescription1Generic": "모델 다운로드 링크를 붙여넣어 라이브러리에 추가하세요.", + "uploadModelDescription2": "현재는 {link}의 링크만 지원됩니다", + "uploadModelDescription2Generic": "다음 제공업체의 URL만 지원됩니다:", + "uploadModelDescription2Link": "https://civitai.com/models", + "uploadModelDescription3": "최대 파일 크기: {size}", + "uploadModelFailedToRetrieveMetadata": "메타데이터를 가져오지 못했습니다. 링크를 확인하고 다시 시도해 주세요.", + "uploadModelFromCivitai": "Civitai에서 모델 가져오기", + "uploadModelGeneric": "모델 가져오기", + "uploadModelHelpFooterText": "URL 찾는 방법이 필요하신가요? 아래 제공업체를 클릭하면 안내 영상을 볼 수 있습니다.", + "uploadModelHelpVideo": "모델 업로드 도움말 영상", + "uploadModelHowDoIFindThis": "이것을 어떻게 찾나요?", + "uploadSuccess": "모델이 성공적으로 가져와졌습니다!", + "uploadingModel": "모델 가져오는 중..." }, "auth": { "apiKey": { @@ -148,12 +268,19 @@ "title": "계정 생성" } }, + "boundingBox": { + "height": "높이", + "width": "너비", + "x": "X", + "y": "Y" + }, "breadcrumbsMenu": { "clearWorkflow": "워크플로 내용 지우기", "deleteBlueprint": "블루프린트 삭제", "deleteWorkflow": "워크플로 삭제", "duplicate": "복제", - "enterNewName": "새 이름 입력" + "enterNewName": "새 이름 입력", + "missingNodesWarning": "워크플로우에 지원되지 않는 노드가 포함되어 있습니다(빨간색으로 표시됨)." }, "clipboard": { "errorMessage": "클립보드에 복사하지 못했습니다", @@ -207,6 +334,7 @@ }, "retry": "다시 시도", "retrying": "재시도 중...", + "skipToCloudApp": "클라우드 앱으로 건너뛰기", "start": { "desc": "설정 불필요. 모든 기기에서 작동합니다.", "download": "ComfyUI 다운로드", @@ -340,6 +468,8 @@ "Edit Subgraph Widgets": "서브그래프 위젯 편집", "Expand": "확장", "Expand Node": "노드 확장", + "Extensions": "확장 프로그램", + "FavoriteWidget": "위젯 즐겨찾기", "Horizontal": "수평", "Inputs": "입력", "Left": "왼쪽", @@ -359,6 +489,7 @@ "Remove": "제거", "Remove Bypass": "우회 제거", "Rename": "이름 변경", + "RenameWidget": "위젯 이름 변경", "Resize": "크기 조정", "Right": "오른쪽", "Run Branch": "분기 실행", @@ -369,6 +500,7 @@ "Shapes": "형태", "Title": "제목", "Top": "위", + "UnfavoriteWidget": "위젯 즐겨찾기 해제", "Unpack Subgraph": "서브그래프 풀기", "Unpin": "고정 해제", "Vertical": "수직", @@ -382,6 +514,7 @@ "additionalInfo": "추가 정보", "apiPricing": "API 가격", "credits": "크레딧", + "creditsAvailable": "사용 가능한 크레딧", "details": "세부 정보", "eventType": "이벤트 유형", "faqs": "자주 묻는 질문", @@ -390,15 +523,46 @@ "messageSupport": "지원 문의", "model": "모델", "purchaseCredits": "크레딧 구매", + "refreshes": "{date}에 새로고침", "time": "시간", "topUp": { + "addMoreCredits": "크레딧 추가하기", + "addMoreCreditsToRun": "실행을 위해 크레딧 추가하기", + "amountToPayLabel": "결제할 금액(달러)", + "buy": "구매", + "buyCredits": "결제 계속하기", "buyNow": "지금 구매", + "contactUs": "문의하기", + "creditsDescription": "크레딧은 워크플로우 또는 파트너 노드 실행에 사용됩니다.", + "creditsPerDollar": "달러당 크레딧", + "creditsToReceiveLabel": "받을 크레딧", + "howManyCredits": "얼마나 많은 크레딧을 추가하시겠습니까?", "insufficientMessage": "이 워크플로를 실행하기에 크레딧이 부족합니다.", "insufficientTitle": "크레딧 부족", + "insufficientWorkflowMessage": "이 워크플로우를 실행할 크레딧이 부족합니다.", + "maxAllowed": "최대 {credits} 크레딧", "maxAmount": "(최대 $1,000 USD)", + "maximumAmount": "최대 ${amount}", + "minRequired": "최소 {credits} 크레딧", + "minimumPurchase": "최소 ${amount} ({credits} 크레딧)", + "needMore": "더 필요하신가요?", + "purchaseError": "구매 실패", + "purchaseErrorDetail": "크레딧 구매 실패: {error}", "quickPurchase": "빠른 구매", "seeDetails": "자세히 보기", - "topUp": "충전하기" + "selectAmount": "금액 선택", + "templateNote": "*Wan Fun Control 템플릿으로 생성됨", + "topUp": "충전하기", + "unknownError": "알 수 없는 오류가 발생했습니다", + "usdAmount": "${amount}", + "videosEstimate": "~{count}개 비디오", + "viewPricing": "요금 세부정보 보기", + "youGet": "크레딧", + "youPay": "결제 금액 (USD)" + }, + "unified": { + "message": "크레딧이 통합되었습니다", + "tooltip": "Comfy 전반에 결제가 통합되었습니다. 이제 모든 것이 Comfy 크레딧으로 실행됩니다:\n- 파트너 노드(이전 API 노드)\n- 클라우드 워크플로우\n\n기존 파트너 노드 잔액이 크레딧으로 전환되었습니다." }, "yourCreditBalance": "보유 크레딧 잔액" }, @@ -414,6 +578,9 @@ "CLIP_VISION": "CLIP_VISION", "CLIP_VISION_OUTPUT": "CLIP_VISION 출력", "COMBO": "콤보", + "COMFY_AUTOGROW_V3": "COMFY_AUTOGROW_V3", + "COMFY_DYNAMICCOMBO_V3": "COMFY_DYNAMICCOMBO_V3", + "COMFY_MATCHTYPE_V3": "COMFY_MATCHTYPE_V3", "CONDITIONING": "조건", "CONTROL_NET": "컨트롤넷", "FLOAT": "실수", @@ -424,18 +591,21 @@ "HOOKS": "후크", "HOOK_KEYFRAMES": "후크 키프레임", "IMAGE": "이미지", + "IMAGECOMPARE": "이미지 비교", "INT": "정수", "LATENT": "잠재 데이터", "LATENT_OPERATION": "잠재 연산", + "LATENT_UPSCALE_MODEL": "LATENT_UPSCALE_MODEL", "LOAD3D_CAMERA": "3D 카메라 불러오기", "LOAD_3D": "3D 로드", - "LOAD_3D_ANIMATION": "3D 애니메이션 로드", "LORA_MODEL": "LORA_MODEL", "LOSS_MAP": "LOSS_MAP", "LUMA_CONCEPTS": "Luma 컨셉", "LUMA_REF": "Luma 참조", "MASK": "마스크", "MESH": "메시", + "MESHY_RIGGED_TASK_ID": "MESHY_RIGGED_TASK_ID", + "MESHY_TASK_ID": "MESHY_TASK_ID", "MODEL": "모델", "MODEL_PATCH": "모델 패치", "MODEL_TASK_ID": "모델 작업 ID", @@ -455,6 +625,7 @@ "STYLE_MODEL": "스타일 모델", "SVG": "SVG", "TIMESTEPS_RANGE": "타임스텝 범위", + "TRACKS": "TRACKS", "UPSCALE_MODEL": "업스케일 모델", "VAE": "VAE", "VIDEO": "비디오", @@ -523,14 +694,17 @@ "amount": "수량", "apply": "적용", "architecture": "아키텍처", + "asset": "{count}개 에셋 | {count}개 에셋 | {count}개 에셋", "audioFailedToLoad": "오디오를 불러오지 못했습니다", "audioProgress": "오디오 진행률", "author": "작성자", "back": "뒤로", + "batchRename": "일괄 이름 변경", "beta": "베타", "bookmark": "라이브러리에 저장", "calculatingDimensions": "크기 계산 중", "cancel": "취소", + "cancelled": "취소됨", "capture": "캡처", "category": "카테고리", "chart": "차트", @@ -540,6 +714,7 @@ "clearAll": "모두 지우기", "clearFilters": "필터 지우기", "close": "닫기", + "closeDialog": "대화 상자 닫기", "color": "색상", "comfy": "Comfy", "comfyOrgLogoAlt": "ComfyOrg 로고", @@ -556,13 +731,17 @@ "control_before_generate": "생성 전 제어", "copied": "복사됨", "copy": "복사", + "copyAll": "모두 복사", "copyJobId": "작업 ID 복사", "copyToClipboard": "클립보드에 복사", "copyURL": "URL 복사", + "core": "코어", "currentUser": "현재 사용자", + "custom": "커스텀", "customBackground": "맞춤 배경", "customize": "사용자 정의", "customizeFolder": "폴더 사용자 정의", + "decrement": "감소", "defaultBanner": "기준 배너", "delete": "삭제", "deleteAudioFile": "오디오 파일 삭제", @@ -571,27 +750,35 @@ "description": "설명", "devices": "장치", "disableAll": "모두 비활성화", + "disableSelected": "선택 항목 비활성화", + "disableThirdParty": "서드파티 비활성화", "disabling": "비활성화 중", "dismiss": "닫기", "download": "다운로드", "downloadImage": "이미지 다운로드", "downloadVideo": "비디오 다운로드", + "downloading": "다운로드 중", "dropYourFileOr": "파일을 드롭하거나", "duplicate": "복제", "edit": "편집", "editImage": "이미지 편집", "editOrMaskImage": "이미지 편집 또는 마스크", + "emDash": "—", "empty": "비어 있음", "enableAll": "모두 활성화", "enableOrDisablePack": "팩 활성화 또는 비활성화", + "enableSelected": "선택 항목 활성화", "enabled": "활성화됨", "enabling": "활성화 중", + "enterBaseName": "기본 이름 입력", + "enterNewName": "새 이름 입력", "error": "오류", "errorLoadingImage": "이미지 로드 오류", "errorLoadingVideo": "비디오 로드 오류", "experimental": "베타", "export": "내보내기", "extensionName": "확장 이름", + "failed": "실패", "failedToCopyJobId": "작업 ID 복사 실패", "failedToDownloadImage": "이미지 다운로드 실패", "failedToDownloadVideo": "비디오 다운로드 실패", @@ -607,12 +794,15 @@ "goToNode": "노드로 이동", "graphNavigation": "그래프 탐색", "halfSpeed": "0.5배속", + "hideLeftPanel": "왼쪽 패널 숨기기", + "hideRightPanel": "오른쪽 패널 숨기기", "icon": "아이콘", "imageFailedToLoad": "이미지를 로드하지 못했습니다.", "imagePreview": "이미지 미리보기 - 화살표 키를 사용하여 이미지 간 이동", "imageUrl": "이미지 URL", "import": "가져오기", "inProgress": "진행 중", + "increment": "증가", "info": "노드 정보", "insert": "삽입", "install": "설치", @@ -620,7 +810,9 @@ "installing": "설치 중", "interrupted": "중단됨", "itemSelected": "{selectedCount}개 선택됨", + "itemsCopiedToClipboard": "항목이 클립보드에 복사되었습니다", "itemsSelected": "{selectedCount}개 선택됨", + "job": "작업", "jobIdCopied": "작업 ID가 클립보드에 복사됨", "keybinding": "키 바인딩", "keybindingAlreadyExists": "단축키가 이미 존재합니다", @@ -638,14 +830,18 @@ "micPermissionDenied": "마이크 권한이 거부되었습니다", "migrate": "이전(migrate)", "missing": "누락됨", + "more": "더보기", "moreOptions": "추가 옵션", "moreWorkflows": "더 많은 워크플로", "multiSelectDropdown": "다중 선택 드롭다운", "name": "이름", "newFolder": "새 폴더", "next": "다음", + "nightly": "NIGHTLY", "no": "아니오", "noAudioRecorded": "녹음된 오디오가 없습니다", + "noItems": "항목 없음", + "noResults": "결과 없음", "noResultsFound": "결과를 찾을 수 없습니다.", "noTasksFound": "작업을 찾을 수 없습니다.", "noTasksFoundMessage": "대기열에 작업이 없습니다.", @@ -656,26 +852,45 @@ "nodeSlotsError": "노드 슬롯 오류", "nodeWidgetsError": "노드 위젯 오류", "nodes": "노드", + "nodesCount": "{count}개 노드 | {count}개 노드 | {count}개 노드", "nodesRunning": "노드 실행 중", "none": "없음", + "nothingToCopy": "복사할 항목 없음", + "nothingToDelete": "삭제할 항목 없음", + "nothingToDuplicate": "복제할 항목 없음", + "nothingToRename": "이름을 변경할 항목 없음", "ok": "확인", "openManager": "관리자 열기", "openNewIssue": "새 문제 열기", + "or": "또는", "overwrite": "덮어쓰기", + "playPause": "재생/일시정지", "playRecording": "녹음 재생", "playbackSpeed": "재생 속도", "playing": "재생 중", "pressKeysForNewBinding": "새 바인딩을 위한 키 입력", "preview": "미리보기", + "profile": "프로필", "progressCountOf": "중", + "queued": "대기 중", "ready": "준비됨", "reconnected": "재연결됨", "reconnecting": "재연결 중", "refresh": "새로 고침", "refreshNode": "노드 새로고침", + "relativeTime": { + "daysAgo": "{count}일 전", + "hoursAgo": "{count}시간 전", + "minutesAgo": "{count}분 전", + "monthsAgo": "{count}개월 전", + "now": "지금", + "weeksAgo": "{count}주 전", + "yearsAgo": "{count}년 전" + }, "releaseTitle": "{package} {version} 릴리스", "reloadToApplyChanges": "변경 사항을 적용하려면 새로 고침하세요.", "removeImage": "이미지 제거", + "removeTag": "태그 제거", "removeVideo": "비디오 제거", "rename": "이름 바꾸기", "reportIssue": "보고서 보내기", @@ -690,21 +905,31 @@ "resizeFromTopRight": "오른쪽 위 모서리에서 크기 조정", "restart": "재시작", "resultsCount": "{count} 개의 결과를 찾았습니다", + "running": "실행 중", "save": "저장", "saving": "저장 중", + "scrollLeft": "왼쪽으로 스크롤", + "scrollRight": "오른쪽으로 스크롤", "search": "검색", "searchExtensions": "확장 검색", "searchFailedMessage": "검색어와 일치하는 설정을 찾을 수 없습니다. 검색어를 조정해 보세요.", "searchKeybindings": "키 바인딩 검색", "searchModels": "모델 검색", "searchNodes": "노드 검색", + "searchPlaceholder": "검색...", "searchSettings": "설정 검색", "searchWorkflows": "워크플로 검색", "seeTutorial": "튜토리얼 보기", + "selectItemsToCopy": "복사할 항목 선택", + "selectItemsToDelete": "삭제할 항목 선택", + "selectItemsToDuplicate": "복제할 항목 선택", + "selectItemsToRename": "이름을 변경할 항목 선택", "selectedFile": "선택된 파일", "setAsBackground": "배경으로 설정", "settings": "설정", + "showLeftPanel": "왼쪽 패널 표시", "showReport": "보고서 보기", + "showRightPanel": "오른쪽 패널 표시", "singleSelectDropdown": "단일 선택 드롭다운", "sort": "정렬", "source": "소스", @@ -712,12 +937,14 @@ "status": "상태", "stopPlayback": "재생 중지", "stopRecording": "녹음 중지", + "submit": "제출", "success": "성공", "systemInfo": "시스템 정보", "terminal": "터미널", "title": "제목", "triggerPhrase": "트리거 문구", "unknownError": "알 수 없는 오류", + "untitled": "제목 없음", "update": "업데이트", "updateAvailable": "업데이트 가능", "updateFrontend": "프론트엔드 업데이트", @@ -725,6 +952,7 @@ "updating": "업데이트 중", "upload": "업로드", "usageHint": "사용 힌트", + "use": "사용", "user": "사용자", "versionMismatchWarning": "버전 호환성 경고", "versionMismatchWarningMessage": "{warning}: {detail} 업데이트 지침은 https://docs.comfy.org/installation/update_comfyui#common-update-issues 를 방문하세요.", @@ -732,11 +960,10 @@ "videoPreview": "비디오 미리보기 - 화살표 키를 사용하여 비디오 간 이동", "viewImageOfTotal": "이미지 {index}/{total} 보기", "viewVideoOfTotal": "비디오 {index}/{total} 보기", - "vitePreloadErrorMessage": "앱의 새 버전이 출시되었습니다. 새로고침하시겠습니까?\n그렇지 않으면 앱의 일부 기능이 예상대로 작동하지 않을 수 있습니다.\n새로고침하기 전에 진행 상황을 저장하고 거부하는 것도 가능합니다.", - "vitePreloadErrorTitle": "새 버전 사용 가능", "volume": "볼륨", "warning": "경고", - "workflow": "워크플로" + "workflow": "워크플로", + "you": "당신" }, "graphCanvasMenu": { "fitView": "보기 맞춤", @@ -758,12 +985,17 @@ "create": "그룹 노드 만들기", "enterName": "이름 입력" }, + "help": { + "helpCenterMenu": "도움말 센터 메뉴", + "recentReleases": "최근 릴리스" + }, "helpCenter": { "clickToLearnMore": "자세히 알아보기 →", "desktopUserGuide": "데스크톱 사용자 가이드", "docs": "문서", + "feedback": "피드백 보내기", "github": "Github", - "helpFeedback": "도움말 및 피드백", + "help": "도움말 및 지원", "loadingReleases": "릴리즈 불러오는 중...", "managerExtension": "관리자 확장", "more": "더보기...", @@ -772,6 +1004,12 @@ "recentReleases": "최신 릴리스", "reinstall": "재설치", "updateAvailable": "업데이트", + "updateComfyUI": "ComfyUI 업데이트", + "updateComfyUIFailed": "ComfyUI 업데이트에 실패했습니다. 다시 시도해 주세요.", + "updateComfyUIStarted": "업데이트 시작됨", + "updateComfyUIStartedDetail": "ComfyUI 업데이트가 대기열에 추가되었습니다. 잠시만 기다려 주세요...", + "updateComfyUISuccess": "업데이트 완료", + "updateComfyUISuccessDetail": "ComfyUI가 업데이트되었습니다. 재시작 중...", "whatsNew": "새로운 소식?" }, "icon": { @@ -785,6 +1023,18 @@ "inbox": "받은 편지함", "star": "별" }, + "imageCompare": { + "noImages": "비교할 이미지가 없습니다" + }, + "imageCrop": { + "cropPreviewAlt": "자르기 미리보기", + "loading": "로딩 중...", + "noInputImage": "입력 이미지가 연결되지 않았습니다" + }, + "importFailed": { + "copyError": "복사 오류", + "title": "가져오기 실패" + }, "install": { "appDataLocationTooltip": "ComfyUI의 앱 데이터 디렉토리. 저장소:\n- 로그\n- 서버 구성", "appPathLocationTooltip": "ComfyUI의 앱 에셋 디렉토리. ComfyUI 코드 및 에셋을 저장합니다.", @@ -798,6 +1048,8 @@ "failedToSelectDirectory": "디렉토리 선택 실패", "gpu": "GPU", "gpuPicker": { + "amdDescription": "최상의 성능을 위해 AMD GPU를 ROCm™ 가속과 함께 사용하세요.", + "amdSubtitle": "AMD ROCm™", "appleMetalDescription": "Mac의 GPU를 활용하여 더 빠른 속도와 더 나은 전반적인 경험을 제공합니다", "cpuDescription": "GPU 가속을 사용할 수 없을 때 호환성을 위해 CPU 모드를 사용하세요", "cpuSubtitle": "CPU 모드", @@ -824,6 +1076,8 @@ "selectGpuDescription": "소유한 GPU 유형을 선택하세요" }, "helpImprove": "ComfyUI 개선에 도움을 주세요", + "insideAppInstallDir": "이 폴더는 ComfyUI Desktop 애플리케이션 번들 내부에 있으며, 업데이트 시 삭제됩니다. Documents/ComfyUI와 같은 설치 폴더 외부의 디렉터리를 선택하세요.", + "insideUpdaterCache": "이 폴더는 ComfyUI 업데이트 캐시 내부에 있으며, 매번 업데이트 시 삭제됩니다. 데이터 저장을 위해 다른 위치를 선택하세요.", "installLocation": "설치 위치", "installLocationDescription": "ComfyUI의 사용자 데이터 디렉토리를 선택하십시오. 선택한 위치에 Python 환경이 설치됩니다. 선택한 디스크에 충분한 공간(~15GB)이 남아 있는지 확인하십시오.", "installLocationTooltip": "ComfyUI의 사용자 데이터 디렉토리. 저장소:\n- Python 환경\n- 모델\n- 커스텀 노드\n", @@ -898,6 +1152,16 @@ "issueReport": { "helpFix": "이 문제 해결에 도움을 주세요" }, + "linearMode": { + "beta": "베타 - 피드백 보내기", + "downloadAll": "모두 다운로드", + "dragAndDropImage": "이미지를 드래그 앤 드롭하세요", + "graphMode": "그래프 모드", + "linearMode": "간단 모드", + "rerun": "다시 실행", + "reuseParameters": "파라미터 재사용", + "runCount": "실행 횟수:" + }, "load3d": { "applyingTexture": "텍스처 적용 중...", "backgroundColor": "배경색", @@ -924,20 +1188,24 @@ "lineart": "라인아트", "normal": "노멀(normal)", "original": "원본", + "pointCloud": "포인트 클라우드", "wireframe": "와이어프레임" }, "model": "모델", "openIn3DViewer": "3D 뷰어에서 열기", + "panoramaMode": "파노라마", "previewOutput": "출력 미리보기", "reloadingModel": "모델 다시 로드 중...", "removeBackgroundImage": "배경 이미지 제거", "resizeNodeMatchOutput": "노드 크기를 출력에 맞추기", "scene": "장면", "showGrid": "그리드 표시", + "showSkeleton": "스켈레톤 표시", "startRecording": "녹화 시작", "stopRecording": "녹화 중지", "switchCamera": "카메라 전환", "switchingMaterialMode": "재질 모드 전환 중...", + "tiledMode": "타일드", "unsupportedFileType": "지원되지 않는 파일 형식 (.gltf, .glb, .obj, .fbx, .stl 지원)", "upDirection": "위 방향", "upDirections": { @@ -958,6 +1226,11 @@ "title": "3D 뷰어 (베타)" } }, + "loadWorkflowWarning": { + "coreNodesFromVersion": "버전 {version}의 코어 노드:", + "outdatedVersion": "이 워크플로우는 더 최신 버전의 ComfyUI({version})에서 생성되었습니다. 일부 노드는 제대로 작동하지 않을 수 있습니다.", + "outdatedVersionGeneric": "이 워크플로우는 더 최신 버전의 ComfyUI에서 생성되었습니다. 일부 노드는 제대로 작동하지 않을 수 있습니다." + }, "maintenance": { "None": "없음", "OK": "확인", @@ -976,7 +1249,15 @@ "showManual": "유지 보수 작업 보기", "status": "상태", "terminalDefaultMessage": "문제 해결 명령을 실행하면 출력이 여기에 표시됩니다.", - "title": "유지 보수" + "title": "유지 보수", + "unsafeMigration": { + "action": "아래의 \"기본 경로\" 유지 관리 작업을 사용하여 ComfyUI를 안전한 위치로 이동하세요.", + "appInstallDir": "기본 경로가 ComfyUI 데스크톱 애플리케이션 번들 내부에 있습니다. 이 폴더는 업데이트 시 삭제되거나 덮어쓸 수 있습니다. Documents/ComfyUI와 같은 설치 폴더 외부의 디렉터리를 선택하세요.", + "generic": "현재 ComfyUI 기본 경로가 업데이트 중 삭제되거나 수정될 수 있는 위치에 있습니다. 데이터 손실을 방지하려면 안전한 폴더로 이동하세요.", + "oneDrive": "기본 경로가 OneDrive에 있습니다. 이는 동기화 문제 및 우발적인 데이터 손실을 일으킬 수 있습니다. OneDrive에서 관리되지 않는 로컬 폴더를 선택하세요.", + "title": "안전하지 않은 설치 위치 감지됨", + "updaterCache": "기본 경로가 ComfyUI 업데이트 캐시 내부에 있습니다. 이 폴더는 매번 업데이트 시 삭제됩니다. 데이터를 위한 다른 위치를 선택하세요." + } }, "manager": { "allMissingNodesInstalled": "누락된 모든 노드가 성공적으로 설치되었습니다", @@ -1077,6 +1358,8 @@ "totalNodes": "총 노드", "tryAgainLater": "나중에 다시 시도해 주세요.", "tryDifferentSearch": "다른 검색어를 시도해 주세요.", + "tryUpdate": "업데이트 시도", + "tryUpdateTooltip": "저장소에서 최신 변경 사항을 가져옵니다. 나이틀리 버전은 자동으로 감지할 수 없는 업데이트가 있을 수 있습니다.", "uninstall": "제거", "uninstallSelected": "선택 항목 제거", "uninstalling": "제거 중", @@ -1087,31 +1370,110 @@ "version": "버전" }, "maskEditor": { + "activateLayer": "레이어 활성화", + "applyToWholeImage": "전체 이미지에 적용", + "baseImageLayer": "기본 이미지 레이어", + "baseLayerPreview": "기본 레이어 미리보기", + "black": "검정", + "brushSettings": "브러시 설정", + "brushShape": "브러시 모양", + "clear": "지우기", + "clickToResetZoom": "클릭하여 확대/축소 초기화", + "colorSelectSettings": "색상 선택 설정", + "colorSelector": "색상 선택기", + "fillOpacity": "채우기 불투명도", + "hardness": "경도", + "imageLayer": "이미지 레이어", + "invert": "반전", + "layers": "레이어", + "livePreview": "실시간 미리보기", + "maskBlendingOptions": "마스크 혼합 옵션", + "maskLayer": "마스크 레이어", + "maskOpacity": "마스크 불투명도", + "maskTolerance": "마스크 허용치", + "method": "방법", + "mirrorHorizontal": "수평 반전", + "mirrorVertical": "수직 반전", + "negative": "네거티브", + "opacity": "불투명도", + "paintBucketSettings": "페인트 버킷 설정", + "paintLayer": "페인트 레이어", + "redo": "다시 실행", + "resetToDefault": "기본값으로 재설정", + "rotateLeft": "왼쪽으로 회전", + "rotateRight": "오른쪽으로 회전", + "selectionOpacity": "선택 영역 불투명도", + "smoothingPrecision": "부드럽기 정밀도", + "stepSize": "단계 크기", + "stopAtMask": "마스크에서 중지", + "thickness": "두께", + "title": "마스크 편집기", + "tolerance": "허용치", + "undo": "실행 취소", + "white": "흰색" }, "mediaAsset": { + "actions": { + "copyJobId": "작업 ID 복사", + "delete": "삭제", + "download": "다운로드", + "exportWorkflow": "워크플로우 내보내기", + "insertAsNodeInWorkflow": "워크플로우에 노드로 삽입", + "inspect": "에셋 검사", + "more": "더 많은 옵션", + "moreOptions": "더 많은 옵션", + "openWorkflow": "새 탭에서 워크플로우로 열기", + "seeMoreOutputs": "더 많은 출력 보기", + "zoom": "확대" + }, "assetDeletedSuccessfully": "에셋이 성공적으로 삭제되었습니다", "deleteAssetDescription": "이 에셋은 영구적으로 제거됩니다.", "deleteAssetTitle": "이 에셋을 삭제하시겠습니까?", "deleteSelectedDescription": "{count}개의 에셋이 영구적으로 제거됩니다.", "deleteSelectedTitle": "선택한 에셋을 삭제하시겠습니까?", "deletingImportedFilesCloudOnly": "가져온 파일 삭제는 클라우드 버전에서만 지원됩니다", + "failedToCreateNode": "노드 생성에 실패했습니다", "failedToDeleteAsset": "에셋 삭제 실패", + "failedToExportWorkflow": "워크플로우 내보내기에 실패했습니다", "jobIdToast": { "copied": "복사됨", "error": "오류", "jobIdCopied": "작업 ID가 클립보드에 복사되었습니다", "jobIdCopyFailed": "작업 ID 복사 실패" }, + "noJobIdFound": "이 에셋에 대한 작업 ID를 찾을 수 없습니다", + "noWorkflowDataFound": "이 에셋에서 워크플로우 데이터를 찾을 수 없습니다", + "nodeAddedToWorkflow": "{nodeType} 노드가 워크플로우에 추가되었습니다", + "nodeTypeNotFound": "{nodeType} 노드 유형을 찾을 수 없습니다", "selection": { "assetsDeletedSuccessfully": "{count}개 에셋이 성공적으로 삭제되었습니다", "deleteSelected": "삭제", + "deleteSelectedAll": "전체 삭제", "deselectAll": "모두 선택 해제", "downloadSelected": "다운로드", + "downloadSelectedAll": "전체 다운로드", "downloadStarted": "{count}개 파일 다운로드 중...", "downloadsStarted": "{count}개 파일 다운로드 시작됨", + "exportWorkflowAll": "모든 워크플로우 내보내기", + "failedToAddNodes": "노드를 워크플로우에 추가하지 못했습니다", "failedToDeleteAssets": "선택한 에셋 삭제 실패", - "selectedCount": "선택된 에셋: {count}개" - } + "insertAllAssetsAsNodes": "모든 에셋을 노드로 삽입", + "multipleSelectedAssets": "여러 자산이 선택됨", + "noWorkflowsFound": "선택한 에셋에서 워크플로우 데이터를 찾을 수 없습니다", + "noWorkflowsToExport": "내보낼 워크플로우 데이터를 찾을 수 없습니다", + "nodesAddedToWorkflow": "{count}개의 노드가 워크플로우에 추가됨", + "openWorkflowAll": "모든 워크플로우 열기", + "partialAddNodesSuccess": "{succeeded}개 성공, {failed}개 실패", + "partialDeleteSuccess": "{succeeded}개가 성공적으로 삭제되었고, {failed}개가 실패했습니다", + "partialWorkflowsExported": "{succeeded}개 성공적으로 내보내짐, {failed}개 실패", + "partialWorkflowsOpened": "{succeeded}개 워크플로우 열림, {failed}개 실패", + "selectedCount": "선택된 에셋: {count}개", + "workflowsExported": "{count}개의 워크플로우가 성공적으로 내보내졌습니다", + "workflowsOpened": "{count}개의 워크플로우가 새 탭에서 열렸습니다" + }, + "unsupportedFileType": "로더 노드에서 지원하지 않는 파일 형식입니다", + "workflowExportedSuccessfully": "워크플로우가 성공적으로 내보내졌습니다", + "workflowOpenedInNewTab": "워크플로우가 새 탭에서 열렸습니다" }, "menu": { "autoQueue": "자동 실행 대기열", @@ -1119,11 +1481,13 @@ "batchCountTooltip": "워크플로 작업을 실행 대기열에 반복 추가할 횟수", "clear": "워크플로 비우기", "clipspace": "클립스페이스 열기", + "customNodesManager": "커스텀 노드 관리자", "dark": "다크", "disabled": "비활성화됨", "disabledTooltip": "워크플로 작업을 자동으로 실행 대기열에 추가하지 않습니다.", "execute": "실행", "help": "도움말", + "helpAndFeedback": "도움말 및 피드백", "hideMenu": "메뉴 숨기기", "instant": "즉시", "instantTooltip": "워크플로 실행이 완료되면 즉시 실행 대기열에 추가합니다.", @@ -1137,6 +1501,7 @@ "resetView": "캔버스 보기 재설정", "run": "실행", "runWorkflow": "워크플로 실행 (시프트 키와 함께 클릭시 가장 먼저 실행)", + "runWorkflowDisabled": "워크플로우에 지원되지 않는 노드(빨간색 강조 표시)가 포함되어 있습니다. 실행하려면 제거하세요.", "runWorkflowFront": "워크플로 실행 (가장 먼저 실행)", "settings": "설정", "showMenu": "메뉴 표시", @@ -1152,6 +1517,7 @@ "Canvas Performance": "캔버스 성능", "Canvas Toggle Lock": "캔버스 토글 잠금", "Check for Custom Node Updates": "커스텀 노드 업데이트 확인", + "Check for Updates": "업데이트 확인", "Clear Pending Tasks": "보류 중인 작업 제거하기", "Clear Workflow": "워크플로 지우기", "Clipspace": "클립스페이스", @@ -1168,13 +1534,14 @@ "Custom Nodes Manager": "커스텀 노드 관리자", "Decrease Brush Size in MaskEditor": "마스크 편집기에서 브러시 크기 줄이기", "Delete Selected Items": "선택한 항목 삭제", + "Desktop User Guide": "데스크톱 사용자 가이드", "Duplicate Current Workflow": "현재 워크플로 복제", "Edit": "편집", "Edit Subgraph Widgets": "하위 그래프 위젯 편집", "Exit Subgraph": "서브그래프 나가기", "Experimental: Browse Model Assets": "실험적: 모델 에셋 탐색", "Experimental: Enable AssetAPI": "실험적: AssetAPI 활성화", - "Experimental: Enable Vue Nodes": "실험적: Vue 노드 활성화", + "Experimental: Enable Nodes 2_0": "실험적: Nodes 2.0 활성화", "Export": "내보내기", "Export (API)": "내보내기 (API)", "File": "파일", @@ -1186,12 +1553,15 @@ "Increase Brush Size in MaskEditor": "마스크 편집기에서 브러시 크기 늘리기", "Install Missing Custom Nodes": "누락된 커스텀 노드 설치", "Interrupt": "중단", + "Job History": "작업 기록", "Load Default Workflow": "기본 워크플로 불러오기", "Lock Canvas": "캔버스 잠금", "Manage group nodes": "그룹 노드 관리", "Manager": "매니저", "Manager Menu (Legacy)": "매니저 메뉴(구버전)", "Minimap": "미니맵", + "Mirror Horizontal in MaskEditor": "마스크 편집기에서 수평 반전", + "Mirror Vertical in MaskEditor": "마스크 편집기에서 수직 반전", "Model Library": "모델 라이브러리", "Move Selected Nodes Down": "선택한 노드 아래로 이동", "Move Selected Nodes Left": "선택한 노드 왼쪽으로 이동", @@ -1204,8 +1574,16 @@ "Node Links": "노드 링크", "Open": "열기", "Open 3D Viewer (Beta) for Selected Node": "선택한 노드에 대한 3D 뷰어 (베타) 열기", + "Open Color Picker in MaskEditor": "MaskEditor에서 색상 선택기 열기", + "Open Custom Nodes Folder": "커스텀 노드 폴더 열기", + "Open DevTools": "개발자 도구 열기", + "Open Inputs Folder": "입력 폴더 열기", + "Open Logs Folder": "로그 폴더 열기", "Open Mask Editor for Selected Node": "선택한 노드의 마스크 에디터 열기", + "Open Models Folder": "모델 폴더 열기", + "Open Outputs Folder": "출력 폴더 열기", "Open Sign In Dialog": "로그인 대화 상자 열기", + "Open extra_model_paths_yaml": "extra_model_paths.yaml 열기", "Pin/Unpin Selected Items": "선택한 항목 고정/고정 해제", "Pin/Unpin Selected Nodes": "선택한 노드 고정/고정 해제", "Previous Opened Workflow": "이전 열린 워크플로", @@ -1213,10 +1591,16 @@ "Queue Prompt": "실행 대기열에 프롬프트 추가", "Queue Prompt (Front)": "실행 대기열 맨 앞에 프롬프트 추가", "Queue Selected Output Nodes": "선택한 출력 노드 대기열에 추가", + "Quit": "종료", "Redo": "다시 실행", "Refresh Node Definitions": "노드 정의 새로 고침", + "Reinstall": "재설치", + "Rename": "이름 바꾸기", "Reset View": "보기 초기화", "Resize Selected Nodes": "선택된 노드 크기 조정", + "Restart": "재시작", + "Rotate Left in MaskEditor": "마스크 편집기에서 왼쪽으로 회전", + "Rotate Right in MaskEditor": "마스크 편집기에서 오른쪽으로 회전", "Save": "저장", "Save As": "다른 이름으로 저장", "Show Keybindings Dialog": "단축키 대화상자 표시", @@ -1225,12 +1609,13 @@ "Sign Out": "로그아웃", "Toggle Essential Bottom Panel": "필수 하단 패널 전환", "Toggle Logs Bottom Panel": "로그 하단 패널 전환", + "Toggle Queue Panel V2": "대기열 패널 V2 전환", "Toggle Search Box": "검색 상자 전환", + "Toggle Simple Mode": "간단 모드 전환", "Toggle Terminal Bottom Panel": "터미널 하단 패널 전환", "Toggle Theme (Dark/Light)": "테마 전환 (어두운/밝은)", "Toggle View Controls Bottom Panel": "뷰 컨트롤 하단 패널 전환", "Toggle promotion of hovered widget": "호버된 위젯 승격 전환", - "Toggle the Custom Nodes Manager Progress Bar": "커스텀 노드 매니저 진행률 표시줄 전환", "Undo": "실행 취소", "Ungroup selected group nodes": "선택한 그룹 노드 그룹 해제", "Unload Models": "모델 언로드", @@ -1255,30 +1640,56 @@ "missingModels": "모델이 없습니다", "missingModelsMessage": "그래프를 로드할 때 다음 모델을 찾을 수 없었습니다" }, + "missingNodes": { + "cloud": { + "description": "이 워크플로우는 Cloud 버전에서 아직 지원되지 않는 커스텀 노드를 사용합니다.", + "gotIt": "확인", + "learnMore": "자세히 알아보기", + "priorityMessage": "이 노드를 자동으로 표시하여 우선적으로 추가할 수 있도록 했습니다.", + "replacementInstruction": "그동안에는, 가능하다면 지원되는 노드(캔버스에서 빨간색으로 강조됨)로 교체하거나 다른 워크플로우를 시도해보세요.", + "title": "이 노드는 아직 Comfy Cloud에서 사용할 수 없습니다" + }, + "oss": { + "description": "이 워크플로우는 아직 설치하지 않은 커스텀 노드를 사용합니다.", + "replacementInstruction": "이 노드를 설치하거나, 설치된 대체 노드로 교체해야 워크플로우를 실행할 수 있습니다. 누락된 노드는 캔버스에서 빨간색으로 강조됩니다.", + "title": "이 워크플로우에 누락된 노드가 있습니다" + } + }, + "nightly": { + "badge": { + "label": "미리보기 버전", + "tooltip": "현재 ComfyUI의 나이트리 버전을 사용 중입니다. 이 기능들에 대한 의견을 피드백 버튼을 통해 공유해 주세요." + } + }, "nodeCategories": { + "": "", "3d": "3d", "3d_models": "3D 모델", "BFL": "BFL", + "Bria": "Bria", "ByteDance": "ByteDance", "Gemini": "Gemini", "Ideogram": "Ideogram", "Kling": "Kling", "LTXV": "LTXV", "Luma": "Luma", + "Meshy": "Meshy", "MiniMax": "MiniMax", "Moonvalley Marey": "Moonvalley Marey", "OpenAI": "OpenAI", - "Pika": "Pika", "PixVerse": "PixVerse", "Recraft": "Recraft", "Rodin": "Rodin", "Runway": "Runway", "Sora": "Sora", "Stability AI": "Stability AI", + "Tencent": "Tencent", + "Topaz": "Topaz", "Tripo": "Tripo", "Veo": "Veo", "Vidu": "Vidu", "Wan": "Wan", + "WaveSpeed": "WaveSpeed", "_for_testing": "_테스트용", "advanced": "고급", "animation": "애니메이션", @@ -1299,6 +1710,7 @@ "controlnet": "컨트롤넷", "create": "생성", "custom_sampling": "사용자 정의 샘플링", + "dataset": "데이터셋", "debug": "디버그", "deprecated": "지원 중단", "edit_models": "edit_models", @@ -1310,8 +1722,10 @@ "image": "이미지", "inpaint": "인페인팅", "instructpix2pix": "InstructPix2Pix", + "kandinsky5": "kandinsky5", "latent": "잠재 데이터", "loaders": "로더", + "logic": "로직", "lotus": "lotus", "ltxv": "ltxv", "mask": "마스크", @@ -1345,7 +1759,15 @@ "upscaling": "업스케일링", "utils": "유틸리티", "video": "비디오", - "video_models": "비디오 모델" + "video_models": "비디오 모델", + "zimage": "zimage" + }, + "nodeErrors": { + "content": "노드 콘텐츠 오류", + "header": "노드 헤더 오류", + "render": "노드 렌더 오류", + "slots": "노드 슬롯 오류", + "widgets": "노드 위젯 오류" }, "nodeHelpPage": { "documentationPage": "문서 페이지", @@ -1362,6 +1784,7 @@ "notSupported": { "continue": "계속", "continueTooltip": "내 장치가 지원되는 장치가 확실합니다.", + "illustrationAlt": "슬픈 소녀 일러스트", "learnMore": "자세히 알아보기", "message": "다음 장치만 지원됩니다:", "reportIssue": "이슈 보고", @@ -1371,12 +1794,136 @@ }, "title": "이 장치는 지원되지 않습니다." }, + "progressToast": { + "allDownloadsCompleted": "모든 다운로드가 완료되었습니다", + "downloadingModel": "모델 다운로드 중...", + "downloadsFailed": "{count}개 다운로드 실패 | {count}개 다운로드 실패 | {count}개 다운로드 실패", + "failed": "실패", + "filter": { + "all": "전체", + "completed": "완료됨", + "failed": "실패" + }, + "finished": "완료", + "importingModels": "모델 가져오는 중", + "noImportsInQueue": "대기 중인 {filter} 없음", + "pending": "대기 중", + "progressCount": "{completed} / {total}" + }, + "queue": { + "completedIn": "{duration} 내에 완료됨", + "inQueue": "대기열에 있음...", + "initializingAlmostReady": "초기화 중 - 거의 준비됨", + "jobAddedToQueue": "작업이 대기열에 추가됨", + "jobDetails": { + "computeHoursUsed": "사용한 컴퓨트 시간", + "errorMessage": "오류 메시지", + "estimatedFinishIn": "예상 완료 시간", + "estimatedStartIn": "예상 시작 시간", + "eta": { + "minutes": "약 {count}분 | 약 {count}분", + "minutesRange": "약 {lo}-{hi}분", + "seconds": "약 {count}초 | 약 {count}초", + "secondsRange": "약 {lo}-{hi}초" + }, + "failedAfter": "실패 시점", + "generatedOn": "생성 일시", + "header": "작업 세부 정보", + "jobId": "작업 ID", + "queuePosition": "대기열 위치", + "queuePositionValue": "내 작업 앞에 약 {count}개 작업 있음 | 내 작업 앞에 약 {count}개 작업 있음", + "queuedAt": "대기열에 추가된 시간", + "report": "신고", + "timeElapsed": "경과 시간", + "totalGenerationTime": "총 생성 시간", + "workflow": "워크플로우" + }, + "jobHistory": "작업 기록", + "jobList": { + "sortComputeHoursUsed": "사용한 컴퓨트 시간(많은 순)", + "sortMostRecent": "최신순", + "sortTotalGenerationTime": "총 생성 시간(긴 순)", + "undated": "날짜 없음" + }, + "jobMenu": { + "addToCurrentWorkflow": "현재 워크플로우에 추가", + "cancelJob": "작업 취소", + "copyErrorMessage": "오류 메시지 복사", + "copyJobId": "작업 ID 복사", + "delete": "삭제", + "deleteAsset": "에셋 삭제", + "download": "다운로드", + "exportWorkflow": "워크플로우 내보내기", + "inspectAsset": "에셋 검사", + "openAsWorkflowNewTab": "워크플로우로 새 탭에서 열기", + "openWorkflowNewTab": "워크플로우 새 탭에서 열기", + "removeJob": "작업 제거", + "reportError": "오류 신고" + }, + "toggleJobHistory": "작업 기록 전환" + }, "releaseToast": { + "description": "이번 업데이트의 최신 개선 사항과 기능을 확인하세요.", "newVersionAvailable": "새 버전이 있습니다!", "skip": "건너뛰기", "update": "업데이트", "whatsNew": "새로운 기능 보기" }, + "rightSidePanel": { + "addFavorite": "즐겨찾기", + "advancedInputs": "고급 입력", + "bypass": "우회", + "color": "노드 색상", + "fallbackGroupTitle": "그룹", + "fallbackNodeTitle": "노드", + "favorites": "즐겨찾는 입력", + "favoritesNone": "즐겨찾는 입력 없음", + "favoritesNoneDesc": "즐겨찾기한 입력이 여기에 표시됩니다", + "favoritesNoneTooltip": "노드를 선택하지 않고도 빠르게 접근하려면 위젯에 별표를 표시하세요", + "globalSettings": { + "canvas": "캔버스", + "connectionLinks": "연결 링크", + "gridSpacing": "그리드 간격", + "linkShape": "링크 모양", + "nodes": "노드", + "nodes2": "노드 2.0", + "searchPlaceholder": "빠른 설정 검색...", + "showAdvanced": "고급 파라미터 표시", + "showAdvancedTooltip": "이 설정을 TRUE로 하면 노드의 모든 고급 파라미터가 표시됩니다", + "showConnectedLinks": "연결된 링크 표시", + "showInfoBadges": "정보 배지 표시", + "showToolbox": "선택 시 툴박스 표시", + "snapNodesToGrid": "노드를 그리드에 맞추기", + "title": "글로벌 설정", + "viewAllSettings": "모든 설정 보기" + }, + "groupSettings": "그룹 설정", + "groups": "그룹", + "hideAdvancedInputsButton": "고급 입력 숨기기", + "hideInput": "입력 숨기기", + "info": "정보", + "inputs": "입력", + "inputsNone": "입력 없음", + "inputsNoneTooltip": "노드에 입력이 없습니다", + "locateNode": "캔버스에서 노드 찾기", + "mute": "음소거", + "noSelection": "노드를 선택하면 속성과 정보를 볼 수 있습니다.", + "nodeState": "노드 상태", + "nodes": "노드", + "nodesNoneDesc": "노드 없음", + "noneSearchDesc": "검색 결과가 없습니다", + "normal": "일반", + "parameters": "파라미터", + "pinned": "고정됨", + "properties": "속성", + "removeFavorite": "즐겨찾기 해제", + "settings": "설정", + "showAdvancedInputsButton": "고급 입력 표시", + "showInput": "입력 표시", + "title": "선택된 노드 없음 | 노드 1개 선택됨 | 노드 {count}개 선택됨", + "togglePanel": "속성 패널 전환", + "workflowOverview": "워크플로우 개요" + }, "selectionToolbox": { "Bypass Group Nodes": "그룹 노드 우회", "Set Group Nodes to Always": "그룹 노드를 항상 실행으로 설정", @@ -1389,6 +1936,8 @@ "serverConfig": { "modifiedConfigs": "다음 서버 구성을 수정했습니다. 변경 사항을 적용하려면 다시 시작하세오.", "restart": "다시 시작", + "restartRequiredToastDetail": "서버 구성 변경 사항을 적용하려면 앱을 재시작하세요.", + "restartRequiredToastSummary": "재시작 필요", "revertChanges": "변경 사항 되돌리기" }, "serverConfigCategories": { @@ -1457,6 +2006,10 @@ "enable-cors-header": { "name": "CORS 헤더 활성화: 모든 출처에 대해 \"*\" 사용 또는 도메인 지정" }, + "enable-manager-legacy-ui": { + "name": "레거시 Manager UI 사용", + "tooltip": "새 UI 대신 레거시 ComfyUI-Manager UI를 사용합니다." + }, "fast": { "name": "테스트되지 않고 품질 저하 가능성이 있는 실험적 최적화 기능을 활성화 합니다." }, @@ -1558,6 +2111,7 @@ "CustomColorPalettes": "사용자 정의 색상 팔레트", "DevMode": "개발자 모드", "EditTokenWeight": "토큰 가중치 편집", + "Execution": "실행", "Extension": "확장", "General": "일반", "Graph": "그래프", @@ -1572,12 +2126,14 @@ "Mask Editor": "마스크 편집기", "Menu": "메뉴", "ModelLibrary": "모델 라이브러리", - "NewEditor": "새 편집기", "Node": "노드", "Node Search Box": "노드 검색 상자", "Node Widget": "노드 위젯", "NodeLibrary": "노드 라이브러리", + "Nodes 2_0": "Nodes 2.0", "Notification Preferences": "알림 환경설정", + "Other": "기타", + "PLY": "PLY", "PlanCredits": "플랜 및 크레딧", "Pointer": "포인터", "Queue": "실행 대기열", @@ -1596,7 +2152,8 @@ "Vue Nodes": "Vue 노드", "VueNodes": "Vue 노드", "Window": "창", - "Workflow": "워크플로" + "Workflow": "워크플로", + "Workspace": "워크스페이스" }, "shape": { "CARD": "카드", @@ -1622,11 +2179,14 @@ "viewControls": "보기 컨트롤" }, "sideToolbar": { + "activeJobStatus": "진행 중인 작업: {status}", "assets": "에셋", "backToAssets": "모든 에셋으로 돌아가기", "browseTemplates": "예제 템플릿 탐색", "downloads": "다운로드", + "generatedAssetsHeader": "생성된 에셋", "helpCenter": "도움말 센터", + "importedAssetsHeader": "가져온 에셋", "labels": { "assets": "에셋", "console": "콘솔", @@ -1669,6 +2229,47 @@ }, "openWorkflow": "로컬 파일 시스템에서 워크플로 열기", "queue": "실행 대기열", + "queueProgressOverlay": { + "activeJobs": "{count}개의 활성 작업", + "activeJobsShort": "{count}개 활성", + "activeJobsSuffix": "활성 작업", + "cancelJobTooltip": "작업 취소", + "clearHistory": "작업 대기열 기록 삭제", + "clearHistoryDialogAssetsNote": "이 작업들로 생성된 에셋은 삭제되지 않으며, 언제든지 에셋 패널에서 볼 수 있습니다.", + "clearHistoryDialogDescription": "아래의 완료되었거나 실패한 모든 작업이 이 작업 대기열 패널에서 삭제됩니다.", + "clearHistoryDialogTitle": "작업 대기열 기록을 삭제하시겠습니까?", + "clearQueueTooltip": "대기열 비우기", + "clearQueued": "대기열 비우기", + "colonPercent": ": {percent}", + "currentNode": "현재 노드:", + "expandCollapsedQueue": "작업 대기열 확장", + "filterAllWorkflows": "모든 워크플로우", + "filterBy": "필터 기준", + "filterCurrentWorkflow": "현재 워크플로우", + "filterJobs": "작업 필터", + "interruptAll": "모든 실행 중인 작업 중단", + "jobQueue": "작업 대기열", + "jobsCompleted": "{count}개 작업 완료", + "jobsFailed": "{count}개 작업 실패", + "moreOptions": "더 많은 옵션", + "noActiveJobs": "활성 작업 없음", + "preview": "미리보기", + "queuedSuffix": "대기 중", + "running": "실행 중", + "showAssets": "에셋 보기", + "showAssetsPanel": "에셋 패널 보기", + "sortBy": "정렬 기준", + "sortJobs": "작업 정렬", + "stubClipTextEncode": "CLIP 텍스트 인코드:", + "title": "대기열 진행 상황", + "total": "총: {percent}", + "viewAllJobs": "모든 작업 보기", + "viewGrid": "그리드 보기", + "viewJobHistory": "작업 기록 보기", + "viewList": "리스트 보기" + }, + "searchAssets": "에셋 검색", + "sidebar": "사이드바", "templates": "템플릿", "themeToggle": "테마 전환", "workflowTab": { @@ -1711,24 +2312,58 @@ "subscription": { "addApiCredits": "API 크레딧 추가", "addCredits": "크레딧 추가", + "addCreditsLabel": "언제든지 크레딧 추가 가능", "benefits": { "benefit1": "파트너 노드 월간 크레딧 — 필요 시 충전", "benefit2": "작업당 최대 30분 실행 시간" }, "beta": "베타", + "billedMonthly": "매월 결제", + "billedYearly": "{total} 연간 결제", + "billingComingSoon": { + "message": "팀 결제 기능이 곧 제공됩니다. 워크스페이스별 좌석당 요금제로 구독할 수 있습니다. 업데이트를 기대해 주세요.", + "title": "곧 출시 예정" + }, + "cancelSubscription": "구독 취소", + "changeTo": "{plan}로 변경", "comfyCloud": "Comfy Cloud", + "comfyCloudLogo": "Comfy Cloud 로고", + "contactOwnerToSubscribe": "워크스페이스 소유자에게 구독을 요청하세요", + "contactUs": "문의하기", + "creditsRemainingThisMonth": "이번 달 남은 크레딧", + "creditsRemainingThisYear": "올해 남은 크레딧", + "creditsYouveAdded": "추가한 크레딧", + "currentPlan": "현재 플랜", + "customLoRAsLabel": "나만의 LoRA 가져오기", + "description": "가장 적합한 플랜을 선택하세요", "expiresDate": "만료일 {date}", + "gpuLabel": "RTX 6000 Pro (96GB VRAM)", + "haveQuestions": "질문이 있거나 엔터프라이즈가 궁금하신가요?", "invoiceHistory": "청구서 기록", "learnMore": "더 알아보기", + "managePayment": "결제 관리", + "managePlan": "플랜 관리", "manageSubscription": "구독 관리", + "maxDuration": { + "creator": "30분", + "founder": "30분", + "pro": "1시간", + "standard": "30분" + }, + "maxDurationLabel": "각 워크플로우 실행 최대 시간", "messageSupport": "고객 지원 문의", + "monthly": "월간", "monthlyBonusDescription": "월간 크레딧 보너스", + "monthlyCreditsInfo": "이 크레딧은 매월 갱신되며 이월되지 않습니다", + "monthlyCreditsLabel": "월간 크레딧", "monthlyCreditsRollover": "이 크레딧은 다음 달로 이월됩니다", + "mostPopular": "가장 인기 있음", "nextBillingCycle": "다음 결제 주기", "partnerNodesBalance": "\"파트너 노드\" 크레딧 잔액", "partnerNodesCredits": "파트너 노드 크레딧", "partnerNodesDescription": "상용/독점 모델 실행용", "perMonth": "USD / 월", + "plansAndPricing": "플랜 및 가격", "prepaidCreditsInfo": "별도 구매하여 만료되지 않는 크레딧", "prepaidDescription": "선불 크레딧", "renewsDate": "{date}에 갱신됨", @@ -1738,14 +2373,46 @@ "waitingForSubscription": "새 탭에서 구독을 완료해주세요. 완료되면 자동으로 감지합니다!" }, "subscribeNow": "지금 구독하기", + "subscribeTo": "{plan} 구독하기", "subscribeToComfyCloud": "Comfy Cloud 구독", "subscribeToRun": "구독", "subscribeToRunFull": "실행 구독", + "subscriptionRequiredMessage": "클라우드에서 워크플로우를 실행하려면 멤버가 구독해야 합니다", + "tierNameYearly": "{name} 연간", + "tiers": { + "creator": { + "name": "Creator" + }, + "founder": { + "name": "Founder's Edition" + }, + "pro": { + "name": "Pro" + }, + "standard": { + "name": "Standard" + } + }, "title": "구독", "titleUnsubscribed": "Comfy Cloud 구독하기", "totalCredits": "총 크레딧", + "upgrade": "업그레이드", + "upgradePlan": "플랜 업그레이드", + "upgradeTo": "{plan}로 업그레이드", + "usdPerMonth": "USD / 월", + "videoEstimateExplanation": "이 추정치는 기본 설정(5초, 640x640, 16fps, 4단계 샘플링)을 사용한 Wan 2.2 이미지-투-비디오 템플릿을 기준으로 합니다.", + "videoEstimateHelp": "이 템플릿에 대한 자세한 정보", + "videoEstimateLabel": "Wan 2.2 이미지-투-비디오 템플릿으로 생성 가능한 5초 비디오 수", + "videoEstimateTryTemplate": "이 템플릿 사용해보기", + "videoTemplateBasedCredits": "Wan 2.2 이미지-투-비디오로 생성된 비디오", + "viewEnterprise": "엔터프라이즈 보기", "viewMoreDetails": "자세히 보기", + "viewMoreDetailsPlans": "플랜 및 가격에 대한 자세한 정보 보기", "viewUsageHistory": "사용 기록 보기", + "workspaceNotSubscribed": "이 워크스페이스는 구독 중이 아닙니다", + "yearly": "연간", + "yearlyCreditsLabel": "연간 총 크레딧", + "yearlyDiscount": "20% 할인", "yourPlanIncludes": "귀하의 플랜 포함 사항:" }, "tabMenu": { @@ -1757,8 +2424,14 @@ "duplicateTab": "탭 복제", "removeFromBookmarks": "북마크에서 제거" }, + "templateWidgets": { + "sort": { + "searchPlaceholder": "검색..." + } + }, "templateWorkflows": { "activeFilters": "필터:", + "allTemplates": "모든 템플릿", "categories": "카테고리", "category": { "3D": "3D", @@ -1785,6 +2458,7 @@ "error": { "templateNotFound": "템플릿 \"{templateName}\"을(를) 찾을 수 없음" }, + "licenseFilter": "라이선스", "loading": "템플릿 불러오는 중...", "loadingMore": "템플릿 더 불러오는 중...", "modelFilter": "모델 필터", @@ -1801,12 +2475,14 @@ "default": "기본값", "modelSizeLowToHigh": "모델 크기 (낮음에서 높음 순)", "newest": "최신순", + "popular": "인기", "recommended": "권장", "searchPlaceholder": "검색...", "vramLowToHigh": "VRAM 사용량 (낮음에서 높음 순)" }, "sorting": "정렬 기준", "title": "템플릿으로 시작하기", + "useCaseFilter": "작업", "useCasesSelected": "{count}개 사용 사례" }, "toastMessages": { @@ -1835,9 +2511,22 @@ "failedToLoadModel": "3D 모델을 로드하지 못함", "failedToPurchaseCredits": "크레딧 구매에 실패했습니다: {error}", "failedToQueue": "대기열 추가 실패", + "failedToToggleCamera": "카메라 전환 실패", + "failedToToggleGrid": "그리드 전환 실패", + "failedToUpdateBackgroundColor": "배경색 업데이트 실패", + "failedToUpdateBackgroundImage": "배경 이미지 업데이트 실패", + "failedToUpdateBackgroundRenderMode": "배경 렌더 모드를 {mode}(으)로 업데이트 실패", + "failedToUpdateEdgeThreshold": "에지 임계값 업데이트 실패", + "failedToUpdateFOV": "시야각 업데이트 실패", + "failedToUpdateLightIntensity": "조명 세기 업데이트 실패", + "failedToUpdateMaterialMode": "머티리얼 모드 업데이트 실패", + "failedToUpdateUpDirection": "위쪽 방향 업데이트 실패", + "failedToUploadBackgroundImage": "배경 이미지 업로드 실패", "fileLoadError": "{fileName}에서 워크플로를 찾을 수 없습니다", + "fileTooLarge": "파일이 너무 큽니다({size} MB). 최대 지원 크기는 {maxSize} MB입니다.", "fileUploadFailed": "파일 업로드에 실패했습니다", "interrupted": "실행이 중단되었습니다", + "legacyMaskEditorDeprecated": "레거시 마스크 에디터는 더 이상 지원되지 않으며 곧 제거될 예정입니다.", "migrateToLitegraphReroute": "향후 버전에서는 Reroute 노드가 제거됩니다. LiteGraph 에서 자체 제공하는 경유점으로 변환하려면 클릭하세요.", "modelLoadedSuccessfully": "3D 모델이 성공적으로 로드됨", "no3dScene": "텍스처를 적용할 3D 장면이 없습니다", @@ -1864,12 +2553,14 @@ "selectUser": "사용자 선택" }, "userSettings": { + "accountSettings": "계정 설정", "email": "이메일", "name": "이름", "notSet": "설정되지 않음", "provider": "로그인 방법", "title": "사용자 설정", - "updatePassword": "비밀번호 업데이트" + "updatePassword": "비밀번호 업데이트", + "workspaceSettings": "워크스페이스 설정" }, "validation": { "descriptionRequired": "설명은 필수입니다", @@ -1898,22 +2589,32 @@ "updateFrontend": "프론트엔드 업데이트" }, "vueNodesBanner": { - "message": "노드가 새로운 모습으로 바뀌었습니다", + "desc": "– 더 유연한 워크플로우, 강력한 신규 위젯, 확장성을 위해 설계됨", + "title": "Nodes 2.0 소개", "tryItOut": "사용해 보기" }, "vueNodesMigration": { "button": "설정 열기", "message": "클래식 노드 디자인을 선호하시나요?" }, + "vueNodesMigrationMainMenu": { + "message": "메인 메뉴에서 언제든지 Nodes 2.0으로 다시 전환할 수 있습니다." + }, "welcome": { "getStarted": "시작하기", "title": "ComfyUI에 오신 것을 환영합니다" }, "whatsNewPopup": { + "later": "나중에", "learnMore": "자세히 알아보기", "noReleaseNotes": "릴리스 노트가 없습니다." }, + "widgetFileUpload": { + "browseFiles": "파일 찾아보기", + "dropPrompt": "파일을 끌어다 놓거나" + }, "widgets": { + "node2only": "Node 2.0 전용", "selectModel": "모델 선택", "uploadSelect": { "placeholder": "선택...", @@ -1922,6 +2623,26 @@ "placeholderModel": "모델 선택...", "placeholderUnknown": "미디어 선택...", "placeholderVideo": "비디오 선택..." + }, + "valueControl": { + "decrement": "값 감소", + "decrementDesc": "값에서 1을 빼거나 이전 옵션을 선택합니다", + "editSettings": "제어 설정 편집", + "fixed": "고정 값", + "fixedDesc": "값을 변경하지 않습니다", + "header": { + "after": "이후", + "before": "이전", + "postfix": "워크플로우 실행:", + "prefix": "값을 자동으로 업데이트" + }, + "increment": "값 증가", + "incrementDesc": "값에 1을 더하거나 다음 옵션을 선택합니다", + "linkToGlobal": "연결 대상", + "linkToGlobalDesc": "글로벌 값의 제어 설정에 연결된 고유 값", + "linkToGlobalSeed": "글로벌 값", + "randomize": "값 무작위화", + "randomizeDesc": "각 생성 후 값을 무작위로 섞습니다" } }, "workflowService": { @@ -1929,6 +2650,146 @@ "exportWorkflow": "워크플로 내보내기", "saveWorkflow": "워크플로 저장" }, + "workspace": { + "addedToWorkspace": "{workspaceName} 워크스페이스에 추가되었습니다", + "inviteAccepted": "초대 수락됨", + "inviteFailed": "초대 수락에 실패했습니다", + "unsavedChanges": { + "message": "저장되지 않은 변경 사항이 있습니다. 변경 사항을 취소하고 워크스페이스를 전환하시겠습니까?", + "title": "저장되지 않은 변경 사항" + } + }, + "workspaceAuth": { + "errors": { + "accessDenied": "이 워크스페이스에 접근할 수 없습니다.", + "invalidFirebaseToken": "인증에 실패했습니다. 다시 로그인해 주세요.", + "notAuthenticated": "워크스페이스에 접근하려면 로그인해야 합니다.", + "tokenExchangeFailed": "워크스페이스 인증에 실패했습니다: {error}", + "workspaceNotFound": "워크스페이스를 찾을 수 없습니다." + } + }, + "workspacePanel": { + "createWorkspaceDialog": { + "create": "만들기", + "message": "워크스페이스는 멤버들이 크레딧을 공유할 수 있게 해줍니다. 생성 후 소유자가 됩니다.", + "nameLabel": "워크스페이스 이름*", + "namePlaceholder": "워크스페이스 이름 입력", + "title": "새 워크스페이스 만들기" + }, + "dashboard": { + "placeholder": "대시보드 워크스페이스 설정" + }, + "deleteDialog": { + "message": "사용하지 않은 크레딧이나 저장되지 않은 자산이 모두 삭제됩니다. 이 작업은 되돌릴 수 없습니다.", + "messageWithName": "\"{name}\"을(를) 삭제하시겠습니까? 사용하지 않은 크레딧이나 저장되지 않은 자산이 모두 삭제됩니다. 이 작업은 되돌릴 수 없습니다.", + "title": "이 워크스페이스를 삭제하시겠습니까?" + }, + "editWorkspaceDialog": { + "nameLabel": "워크스페이스 이름", + "save": "저장", + "title": "워크스페이스 정보 수정" + }, + "invite": "초대", + "inviteLimitReached": "최대 50명의 멤버에 도달했습니다", + "inviteMember": "멤버 초대", + "inviteMemberDialog": { + "createLink": "링크 생성", + "linkCopied": "복사됨", + "linkCopyFailed": "링크 복사 실패", + "linkStep": { + "copyLink": "링크 복사", + "done": "완료", + "message": "상대방 계정이 이 이메일을 사용하는지 확인하세요.", + "title": "이 링크를 상대방에게 보내세요" + }, + "message": "공유 가능한 초대 링크를 생성하여 상대방에게 보내세요", + "placeholder": "이메일을 입력하세요", + "title": "이 워크스페이스에 사람 초대" + }, + "leaveDialog": { + "leave": "나가기", + "message": "워크스페이스 소유자에게 연락하지 않으면 다시 참여할 수 없습니다.", + "title": "이 워크스페이스를 나가시겠습니까?" + }, + "members": { + "actions": { + "copyLink": "초대 링크 복사", + "removeMember": "멤버 제거", + "revokeInvite": "초대 취소" + }, + "columns": { + "expiryDate": "만료 날짜", + "inviteDate": "초대 날짜", + "joinDate": "가입 날짜" + }, + "createNewWorkspace": "새 워크스페이스를 만드세요.", + "membersCount": "{count}/50 멤버", + "noInvites": "대기 중인 초대 없음", + "noMembers": "멤버 없음", + "pendingInvitesCount": "{count}건의 초대 대기 중 | {count}건의 초대 대기 중", + "personalWorkspaceMessage": "현재 개인 워크스페이스에는 다른 멤버를 초대할 수 없습니다. 멤버를 추가하려면", + "tabs": { + "active": "활성", + "pendingCount": "대기 중 ({count})" + } + }, + "menu": { + "deleteWorkspace": "워크스페이스 삭제", + "deleteWorkspaceDisabledTooltip": "먼저 워크스페이스의 활성 구독을 취소하세요", + "editWorkspace": "워크스페이스 정보 수정", + "leaveWorkspace": "워크스페이스 나가기" + }, + "removeMemberDialog": { + "error": "멤버 제거에 실패했습니다", + "message": "이 멤버는 워크스페이스에서 제거됩니다. 사용한 크레딧은 환불되지 않습니다.", + "remove": "멤버 제거", + "success": "멤버가 제거되었습니다", + "title": "이 멤버를 제거하시겠습니까?" + }, + "revokeInviteDialog": { + "message": "이 멤버는 더 이상 워크스페이스에 참여할 수 없습니다. 초대 링크가 무효화됩니다.", + "revoke": "초대 취소", + "title": "이 사람의 초대를 취소하시겠습니까?" + }, + "tabs": { + "dashboard": "대시보드", + "membersCount": "멤버 ({count})", + "planCredits": "플랜 및 크레딧" + }, + "toast": { + "failedToCreateWorkspace": "워크스페이스 생성에 실패했습니다", + "failedToDeleteWorkspace": "워크스페이스 삭제에 실패했습니다", + "failedToFetchWorkspaces": "워크스페이스 불러오기에 실패했습니다", + "failedToLeaveWorkspace": "워크스페이스 나가기에 실패했습니다", + "failedToUpdateWorkspace": "워크스페이스 업데이트에 실패했습니다", + "workspaceCreated": { + "message": "요금제 구독, 팀원 초대, 협업을 시작하세요.", + "subscribe": "구독하기", + "title": "워크스페이스 생성됨" + }, + "workspaceDeleted": { + "message": "워크스페이스가 영구적으로 삭제되었습니다.", + "title": "워크스페이스 삭제됨" + }, + "workspaceLeft": { + "message": "워크스페이스에서 나갔습니다.", + "title": "워크스페이스에서 나감" + }, + "workspaceUpdated": { + "message": "워크스페이스 정보가 저장되었습니다.", + "title": "워크스페이스가 업데이트되었습니다" + } + } + }, + "workspaceSwitcher": { + "createWorkspace": "새 워크스페이스 만들기", + "maxWorkspacesReached": "최대 10개의 워크스페이스만 소유할 수 있습니다. 새로 만들려면 하나를 삭제하세요.", + "personal": "개인", + "roleMember": "멤버", + "roleOwner": "소유자", + "subscribe": "구독하기", + "switchWorkspace": "워크스페이스 전환" + }, "zoomControls": { "hideMinimap": "미니맵 숨기기", "label": "줌 컨트롤", diff --git a/src/locales/ko/nodeDefs.json b/src/locales/ko/nodeDefs.json index b77a12dcf..98f1d5097 100644 --- a/src/locales/ko/nodeDefs.json +++ b/src/locales/ko/nodeDefs.json @@ -39,6 +39,87 @@ "sigmas": { "name": "시그마 배열" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AddTextPrefix": { + "display_name": "텍스트 접두사 추가", + "inputs": { + "prefix": { + "name": "prefix", + "tooltip": "추가할 접두사." + }, + "texts": { + "name": "texts", + "tooltip": "처리할 텍스트." + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "처리된 텍스트" + } + } + }, + "AddTextSuffix": { + "display_name": "텍스트 접미사 추가", + "inputs": { + "suffix": { + "name": "suffix", + "tooltip": "추가할 접미사." + }, + "texts": { + "name": "texts", + "tooltip": "처리할 텍스트." + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "처리된 텍스트" + } + } + }, + "AdjustBrightness": { + "display_name": "밝기 조정", + "inputs": { + "factor": { + "name": "factor", + "tooltip": "밝기 계수. 1.0 = 변경 없음, <1.0 = 더 어둡게, >1.0 = 더 밝게." + }, + "images": { + "name": "images", + "tooltip": "처리할 이미지." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "처리된 이미지" + } + } + }, + "AdjustContrast": { + "display_name": "명암 조정", + "inputs": { + "factor": { + "name": "factor", + "tooltip": "명암 계수. 1.0 = 변경 없음, <1.0 = 명암 감소, >1.0 = 명암 증가." + }, + "images": { + "name": "images", + "tooltip": "처리할 이미지." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "처리된 이미지" + } } }, "AlignYourStepsScheduler": { @@ -70,6 +151,11 @@ "name": "volume", "tooltip": "데시벨(dB) 단위의 볼륨 조절. 0 = 변경 없음, +6 = 두 배, -6 = 절반 등" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "AudioConcat": { @@ -86,6 +172,11 @@ "name": "direction", "tooltip": "audio2를 audio1 뒤에 추가할지 앞에 추가할지 여부입니다." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "AudioEncoderEncode": { @@ -131,6 +222,11 @@ "name": "merge_method", "tooltip": "오디오 파형을 결합하는 데 사용되는 방법입니다." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BasicGuider": { @@ -142,6 +238,11 @@ "model": { "name": "모델" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BasicScheduler": { @@ -159,6 +260,50 @@ "steps": { "name": "스텝 수" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchImagesNode": { + "display_name": "이미지 배치", + "inputs": { + "images": { + "name": "images" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchLatentsNode": { + "display_name": "latent 배치", + "inputs": { + "latents": { + "name": "latents" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchMasksNode": { + "display_name": "마스크 배치", + "inputs": { + "masks": { + "name": "masks" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BetaSamplingScheduler": { @@ -176,6 +321,73 @@ "steps": { "name": "스텝 수" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BriaImageEditNode": { + "description": "Bria 최신 모델을 사용하여 이미지를 편집합니다", + "display_name": "Bria 이미지 편집", + "inputs": { + "control_after_generate": { + "name": "생성 후 제어" + }, + "guidance_scale": { + "name": "가이던스 스케일", + "tooltip": "값이 높을수록 프롬프트를 더 엄격하게 따릅니다." + }, + "image": { + "name": "image" + }, + "mask": { + "name": "마스크", + "tooltip": "생략하면 전체 이미지에 편집이 적용됩니다." + }, + "model": { + "name": "model" + }, + "moderation": { + "name": "모더레이션", + "tooltip": "모더레이션 설정" + }, + "moderation_prompt_content_moderation": { + "name": "프롬프트 콘텐츠 모더레이션" + }, + "moderation_visual_input_moderation": { + "name": "비주얼 입력 모더레이션" + }, + "moderation_visual_output_moderation": { + "name": "비주얼 출력 모더레이션" + }, + "negative_prompt": { + "name": "네거티브 프롬프트" + }, + "prompt": { + "name": "프롬프트", + "tooltip": "이미지 편집을 위한 지시문" + }, + "seed": { + "name": "시드" + }, + "steps": { + "name": "스텝" + }, + "structured_prompt": { + "name": "구조화된 프롬프트", + "tooltip": "JSON 형식의 구조화된 편집 프롬프트 문자열입니다. 더 정확하고 프로그래밍적으로 제어하려면 일반 프롬프트 대신 사용하세요." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "구조화된 프롬프트", + "tooltip": null + } } }, "ByteDanceFirstLastFrameNode": { @@ -201,13 +413,16 @@ "name": "first_frame", "tooltip": "비디오에 사용될 첫 번째 프레임입니다." }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "이 매개변수는 seedance-1-5-pro 모델을 제외한 모든 모델에서 무시됩니다." + }, "last_frame": { "name": "last_frame", "tooltip": "비디오에 사용될 마지막 프레임입니다." }, "model": { - "name": "model", - "tooltip": "모델 이름" + "name": "model" }, "prompt": { "name": "prompt", @@ -248,8 +463,7 @@ "tooltip": "편집할 기본 이미지" }, "model": { - "name": "모델", - "tooltip": "모델 이름" + "name": "모델" }, "prompt": { "name": "프롬프트", @@ -286,8 +500,7 @@ "tooltip": "이미지의 사용자 지정 높이. `size_preset`이 `Custom`으로 설정된 경우에만 값이 적용됨" }, "model": { - "name": "모델", - "tooltip": "모델 이름" + "name": "모델" }, "prompt": { "name": "프롬프트", @@ -336,8 +549,7 @@ "tooltip": "1개에서 4개의 이미지" }, "model": { - "name": "모델", - "tooltip": "모델 이름" + "name": "모델" }, "prompt": { "name": "프롬프트", @@ -381,13 +593,16 @@ "name": "지속 시간", "tooltip": "출력 비디오의 지속 시간(초)입니다." }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "이 매개변수는 seedance-1-5-pro 모델을 제외한 모든 모델에서 무시됩니다." + }, "image": { "name": "이미지", "tooltip": "비디오에 사용할 첫 번째 프레임입니다." }, "model": { - "name": "모델", - "tooltip": "모델 이름" + "name": "모델" }, "prompt": { "name": "프롬프트", @@ -489,9 +704,12 @@ "name": "duration", "tooltip": "출력 비디오의 지속 시간(초)입니다." }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "이 매개변수는 seedance-1-5-pro 모델을 제외한 모든 모델에서 무시됩니다." + }, "model": { - "name": "model", - "tooltip": "모델 이름" + "name": "model" }, "prompt": { "name": "prompt", @@ -531,6 +749,11 @@ "positive": { "name": "긍정 조건" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "CFGNorm": { @@ -769,6 +992,25 @@ } } }, + "CLIPTextEncodeKandinsky5": { + "display_name": "CLIPTextEncodeKandinsky5", + "inputs": { + "clip": { + "name": "clip" + }, + "clip_l": { + "name": "clip_l" + }, + "qwen25_7b": { + "name": "qwen25_7b" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "CLIPTextEncodeLumina2": { "description": "CLIP 모델을 사용하여 시스템 프롬프트와 사용자 프롬프트를 인코딩하여 특정 이미지를 생성하는 데 사용할 수 있는 임베딩으로 변환합니다.", "display_name": "CLIP 텍스트 인코딩 (Lumina2)", @@ -959,6 +1201,29 @@ } } }, + "CenterCropImages": { + "display_name": "중앙 자르기", + "inputs": { + "height": { + "name": "height", + "tooltip": "자르기 높이." + }, + "images": { + "name": "images", + "tooltip": "처리할 이미지." + }, + "width": { + "name": "width", + "tooltip": "자르기 너비." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "처리된 이미지" + } + } + }, "CheckpointLoader": { "display_name": "구성으로 체크포인트 로드 (지원 중단)", "inputs": { @@ -1095,6 +1360,26 @@ } } }, + "ComfySwitchNode": { + "display_name": "스위치", + "inputs": { + "on_false": { + "name": "거짓일 때" + }, + "on_true": { + "name": "참일 때" + }, + "switch": { + "name": "스위치" + } + }, + "outputs": { + "0": { + "name": "출력", + "tooltip": null + } + } + }, "ConditioningAverage": { "display_name": "조건 (평균)", "inputs": { @@ -1327,14 +1612,14 @@ "name": "전체(초)" } }, - "outputs": { - "0": { - "name": "긍정 조건" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "부정 조건" + { + "tooltip": null } - } + ] }, "ConditioningTimestepsRange": { "display_name": "조건 (타임스텝 범위)", @@ -1391,6 +1676,10 @@ "name": "dim", "tooltip": "컨텍스트 윈도우를 적용할 차원입니다." }, + "freenoise": { + "name": "프리노이즈", + "tooltip": "FreeNoise 노이즈 셔플을 적용할지 여부, 윈도우 블렌딩을 향상시킵니다." + }, "fuse_method": { "name": "fuse_method", "tooltip": "컨텍스트 윈도우를 융합하는 데 사용할 방법입니다." @@ -1791,8 +2080,32 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, + "CustomCombo": { + "display_name": "사용자 지정 콤보", + "inputs": { + "choice": { + "name": "선택" + }, + "index": { + }, + "option1": { + } + }, + "outputs": [ + null, + { + "name": "INDEX", + "tooltip": null + } + ] + }, "DiffControlNetLoader": { "display_name": "컨트롤넷 모델 로드 (차이)", "inputs": { @@ -1829,7 +2142,12 @@ } }, "DisableNoise": { - "display_name": "노이즈 비활성화" + "display_name": "노이즈 비활성화", + "outputs": { + "0": { + "tooltip": null + } + } }, "DualCFGGuider": { "display_name": "이중 CFG 가이드", @@ -1855,6 +2173,11 @@ "style": { "name": "style" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "DualCLIPLoader": { @@ -1938,6 +2261,11 @@ "name": "샘플링 레이트", "tooltip": "빈 오디오 클립의 샘플링 레이트입니다." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyChromaRadianceLatentImage": { @@ -1981,6 +2309,25 @@ } } }, + "EmptyFlux2LatentImage": { + "display_name": "Empty Flux 2 Latent", + "inputs": { + "batch_size": { + "name": "배치 크기" + }, + "height": { + "name": "높이" + }, + "width": { + "name": "너비" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptyHunyuanImageLatent": { "display_name": "빈 훈위안 이미지 잠재", "inputs": { @@ -2022,6 +2369,28 @@ } } }, + "EmptyHunyuanVideo15Latent": { + "display_name": "Empty HunyuanVideo 1.5 Latent", + "inputs": { + "batch_size": { + "name": "배치 크기" + }, + "height": { + "name": "높이" + }, + "length": { + "name": "길이" + }, + "width": { + "name": "너비" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptyImage": { "display_name": "빈 이미지", "inputs": { @@ -2071,6 +2440,11 @@ "seconds": { "name": "초" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyLatentHunyuan3Dv2": { @@ -2083,6 +2457,11 @@ "resolution": { "name": "해상도" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyLatentImage": { @@ -2130,6 +2509,28 @@ } } }, + "EmptyQwenImageLayeredLatentImage": { + "display_name": "Empty Qwen Image Layered Latent", + "inputs": { + "batch_size": { + "name": "배치 크기" + }, + "height": { + "name": "높이" + }, + "layers": { + "name": "레이어" + }, + "width": { + "name": "너비" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptySD3LatentImage": { "display_name": "빈 잠재 이미지 (SD3)", "inputs": { @@ -2177,6 +2578,11 @@ "steps": { "name": "스텝 수" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ExtendIntermediateSigmas": { @@ -2197,6 +2603,11 @@ "steps": { "name": "스텝 수" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FeatherMask": { @@ -2217,6 +2628,11 @@ "top": { "name": "위쪽" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FlipSigmas": { @@ -2225,6 +2641,102 @@ "sigmas": { "name": "시그마 배열" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2MaxImageNode": { + "description": "프롬프트와 해상도에 따라 이미지를 동기적으로 생성합니다.", + "display_name": "Flux.2 [max] 이미지", + "inputs": { + "control_after_generate": { + "name": "생성 후 제어" + }, + "height": { + "name": "높이" + }, + "images": { + "name": "이미지", + "tooltip": "참조용으로 최대 9개의 이미지를 사용할 수 있습니다." + }, + "prompt": { + "name": "프롬프트", + "tooltip": "이미지 생성 또는 편집을 위한 프롬프트" + }, + "prompt_upsampling": { + "name": "프롬프트 업샘플링", + "tooltip": "프롬프트에 업샘플링을 수행할지 여부입니다. 활성화 시, 더 창의적인 생성을 위해 프롬프트가 자동으로 수정됩니다." + }, + "seed": { + "name": "시드", + "tooltip": "노이즈 생성을 위한 랜덤 시드입니다." + }, + "width": { + "name": "너비" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2ProImageNode": { + "description": "프롬프트와 해상도에 따라 이미지를 동기적으로 생성합니다.", + "display_name": "Flux.2 [pro] 이미지", + "inputs": { + "control_after_generate": { + "name": "생성 후 제어" + }, + "height": { + "name": "높이" + }, + "images": { + "name": "이미지", + "tooltip": "참조용으로 최대 9개의 이미지를 사용할 수 있습니다." + }, + "prompt": { + "name": "프롬프트", + "tooltip": "이미지 생성 또는 편집을 위한 프롬프트" + }, + "prompt_upsampling": { + "name": "프롬프트 업샘플링", + "tooltip": "프롬프트에 업샘플링을 수행할지 여부입니다. 활성화 시, 더 창의적인 생성을 위해 프롬프트가 자동으로 수정됩니다." + }, + "seed": { + "name": "시드", + "tooltip": "노이즈 생성을 위한 랜덤 시드입니다." + }, + "width": { + "name": "너비" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2Scheduler": { + "display_name": "Flux2Scheduler", + "inputs": { + "height": { + "name": "높이" + }, + "steps": { + "name": "스텝" + }, + "width": { + "name": "너비" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FluxDisableGuidance": { @@ -2547,6 +3059,11 @@ "s2": { "name": "s2" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FreeU_V2": { @@ -2567,6 +3084,11 @@ "s2": { "name": "s2" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "GITSScheduler": { @@ -2625,6 +3147,58 @@ } } }, + "GeminiImage2Node": { + "description": "Google Vertex API를 통해 동기적으로 이미지를 생성하거나 편집합니다.", + "display_name": "Nano Banana Pro (Google Gemini Image)", + "inputs": { + "aspect_ratio": { + "name": "종횡비", + "tooltip": "'auto'로 설정하면 입력 이미지의 종횡비와 일치합니다. 이미지가 없으면 일반적으로 16:9 정사각형이 생성됩니다." + }, + "control_after_generate": { + "name": "생성 후 제어" + }, + "files": { + "name": "파일", + "tooltip": "모델의 컨텍스트로 사용할 선택적 파일(들)입니다. Gemini Generate Content Input Files 노드의 입력을 허용합니다." + }, + "images": { + "name": "이미지", + "tooltip": "선택적 참조 이미지(들)입니다. 여러 이미지를 포함하려면 Batch Images 노드를 사용하세요(최대 14개)." + }, + "model": { + "name": "모델" + }, + "prompt": { + "name": "프롬프트", + "tooltip": "생성할 이미지 또는 적용할 편집을 설명하는 텍스트 프롬프트입니다. 모델이 따라야 할 제약, 스타일, 세부사항을 포함하세요." + }, + "resolution": { + "name": "해상도", + "tooltip": "목표 출력 해상도입니다. 2K/4K의 경우 Gemini 고유 업스케일러가 사용됩니다." + }, + "response_modalities": { + "name": "응답 형식", + "tooltip": "'IMAGE'를 선택하면 이미지만 출력되고, 'IMAGE+TEXT'를 선택하면 생성된 이미지와 텍스트 응답이 모두 반환됩니다." + }, + "seed": { + "name": "시드", + "tooltip": "시드를 특정 값으로 고정하면, 모델은 반복 요청에 대해 동일한 응답을 제공하려고 시도합니다. 결정적 결과는 보장되지 않습니다. 또한, 모델이나 파라미터(예: temperature)를 변경하면 같은 시드여도 결과가 달라질 수 있습니다. 기본적으로 무작위 시드가 사용됩니다." + }, + "system_prompt": { + "name": "시스템 프롬프트", + "tooltip": "AI의 동작을 지시하는 기본 지침입니다." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "tooltip": null + } + } + }, "GeminiImageNode": { "description": "Google API를 통해 이미지를 동기적으로 편집합니다.", "display_name": "Google Gemini 이미지", @@ -2652,9 +3226,17 @@ "name": "prompt", "tooltip": "생성을 위한 텍스트 프롬프트" }, + "response_modalities": { + "name": "응답 형식", + "tooltip": "'IMAGE'를 선택하면 이미지만 출력되고, 'IMAGE+TEXT'를 선택하면 생성된 이미지와 텍스트 응답이 모두 반환됩니다." + }, "seed": { "name": "seed", "tooltip": "시드가 특정 값으로 고정되면, 모델은 반복 요청에 대해 동일한 응답을 제공하기 위해 최선을 다합니다. 결정론적 출력은 보장되지 않습니다. 또한 모델이나 온도와 같은 매개변수 설정을 변경하면 동일한 시드 값을 사용하더라도 응답에 변동이 발생할 수 있습니다. 기본적으로 랜덤 시드 값이 사용됩니다." + }, + "system_prompt": { + "name": "시스템 프롬프트", + "tooltip": "AI의 동작을 지시하는 기본 지침입니다." } }, "outputs": { @@ -2716,6 +3298,10 @@ "name": "seed", "tooltip": "시드가 특정 값으로 고정되면, 모델은 반복 요청에 대해 동일한 응답을 제공하기 위해 최선을 다합니다. 결정론적 출력은 보장되지 않습니다. 또한 모델이나 온도와 같은 매개변수 설정을 변경하면 동일한 시드 값을 사용하더라도 응답에 변동이 발생할 수 있습니다. 기본적으로 랜덤 시드 값이 사용됩니다." }, + "system_prompt": { + "name": "시스템 프롬프트", + "tooltip": "AI의 동작을 지시하는 기본 지침입니다." + }, "video": { "name": "비디오", "tooltip": "모델의 컨텍스트로 사용할 선택적 비디오입니다." @@ -2727,6 +3313,72 @@ } } }, + "GenerateTracks": { + "display_name": "GenerateTracks", + "inputs": { + "bezier": { + "name": "bezier", + "tooltip": "중간점을 제어점으로 사용하여 베지어 곡선 경로를 활성화합니다." + }, + "end_x": { + "name": "end_x", + "tooltip": "종료 위치의 정규화된 X 좌표 (0-1)." + }, + "end_y": { + "name": "end_y", + "tooltip": "종료 위치의 정규화된 Y 좌표 (0-1)." + }, + "height": { + "name": "height" + }, + "interpolation": { + "name": "interpolation", + "tooltip": "경로를 따라 움직임의 타이밍/속도를 제어합니다." + }, + "mid_x": { + "name": "mid_x", + "tooltip": "베지어 곡선의 정규화된 X 제어점. 'bezier'가 활성화된 경우에만 사용됩니다." + }, + "mid_y": { + "name": "mid_y", + "tooltip": "베지어 곡선의 정규화된 Y 제어점. 'bezier'가 활성화된 경우에만 사용됩니다." + }, + "num_frames": { + "name": "num_frames" + }, + "num_tracks": { + "name": "num_tracks" + }, + "start_x": { + "name": "start_x", + "tooltip": "시작 위치의 정규화된 X 좌표 (0-1)." + }, + "start_y": { + "name": "start_y", + "tooltip": "시작 위치의 정규화된 Y 좌표 (0-1)." + }, + "track_mask": { + "name": "track_mask", + "tooltip": "보이는 프레임을 표시하는 선택적 mask입니다." + }, + "track_spread": { + "name": "track_spread", + "tooltip": "트랙 간의 정규화된 거리. 트랙은 이동 방향에 수직으로 퍼집니다." + }, + "width": { + "name": "width" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "track_length", + "tooltip": null + } + } + }, "GetImageSize": { "description": "이미지의 너비와 높이를 반환하고 변경 없이 전달합니다.", "display_name": "이미지 크기 가져오기", @@ -2735,17 +3387,17 @@ "name": "이미지" } }, - "outputs": { - "0": { - "name": "너비" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "높이" + { + "tooltip": null }, - "2": { - "name": "배치 크기" + { + "tooltip": null } - } + ] }, "GetVideoComponents": { "description": "비디오에서 모든 컴포넌트(프레임, 오디오, 프레임레이트)를 추출합니다.", @@ -2783,6 +3435,11 @@ "tapered_corners": { "name": "마름모 모서리" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Hunyuan3Dv2Conditioning": { @@ -2792,14 +3449,14 @@ "name": "clip_vision_output" } }, - "outputs": { - "0": { - "name": "긍정 조건" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "부정 조건" + { + "tooltip": null } - } + ] }, "Hunyuan3Dv2ConditioningMultiView": { "display_name": "Hunyuan3Dv2ConditioningMultiView", @@ -2817,14 +3474,14 @@ "name": "오른쪽" } }, - "outputs": { - "0": { - "name": "긍정 조건" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "부정 조건" + { + "tooltip": null } - } + ] }, "HunyuanImageToVideo": { "display_name": "HunyuanImageToVideo", @@ -2896,6 +3553,120 @@ } } }, + "HunyuanVideo15ImageToVideo": { + "display_name": "HunyuanVideo15ImageToVideo", + "inputs": { + "batch_size": { + "name": "batch_size" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "height": { + "name": "height" + }, + "length": { + "name": "length" + }, + "negative": { + "name": "negative" + }, + "positive": { + "name": "positive" + }, + "start_image": { + "name": "start_image" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "width" + } + }, + "outputs": { + "0": { + "name": "positive", + "tooltip": null + }, + "1": { + "name": "negative", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "HunyuanVideo15LatentUpscaleWithModel": { + "display_name": "Hunyuan Video 15 Latent Upscale With Model", + "inputs": { + "crop": { + "name": "crop" + }, + "height": { + "name": "height" + }, + "model": { + "name": "model" + }, + "samples": { + "name": "samples" + }, + "upscale_method": { + "name": "upscale_method" + }, + "width": { + "name": "width" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "HunyuanVideo15SuperResolution": { + "display_name": "HunyuanVideo15SuperResolution", + "inputs": { + "clip_vision_output": { + "name": "clip_vision_output" + }, + "latent": { + "name": "latent" + }, + "negative": { + "name": "negative" + }, + "noise_augmentation": { + "name": "노이즈 증강" + }, + "positive": { + "name": "positive" + }, + "start_image": { + "name": "시작 이미지" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "positive", + "tooltip": null + }, + "1": { + "name": "negative", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, "HyperTile": { "display_name": "하이퍼 타일", "inputs": { @@ -3100,6 +3871,11 @@ "strength": { "name": "강도" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageBatch": { @@ -3163,6 +3939,26 @@ "image": { "name": "이미지" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageCompare": { + "description": "두 이미지를 슬라이더로 나란히 비교합니다.", + "display_name": "이미지 비교", + "inputs": { + "compare_view": { + "name": "compare_view" + }, + "image_a": { + "name": "image_a" + }, + "image_b": { + "name": "image_b" + } } }, "ImageCompositeMasked": { @@ -3186,6 +3982,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageCrop": { @@ -3206,6 +4007,30 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageDeduplication": { + "display_name": "이미지 중복 제거", + "inputs": { + "images": { + "name": "이미지", + "tooltip": "처리할 이미지 목록입니다." + }, + "similarity_threshold": { + "name": "유사도 임계값", + "tooltip": "유사도 임계값 (0-1). 값이 높을수록 더 유사합니다. 이 임계값을 초과하는 이미지는 중복으로 간주됩니다." + } + }, + "outputs": { + "0": { + "name": "이미지", + "tooltip": "처리된 이미지" + } } }, "ImageFlip": { @@ -3217,6 +4042,11 @@ "image": { "name": "이미지" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageFromBatch": { @@ -3231,6 +4061,42 @@ "length": { "name": "길이" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageGrid": { + "display_name": "이미지 그리드", + "inputs": { + "cell_height": { + "name": "셀 높이", + "tooltip": "그리드 내 각 셀의 높이입니다." + }, + "cell_width": { + "name": "셀 너비", + "tooltip": "그리드 내 각 셀의 너비입니다." + }, + "columns": { + "name": "열", + "tooltip": "그리드의 열 개수입니다." + }, + "images": { + "name": "이미지", + "tooltip": "처리할 이미지 목록입니다." + }, + "padding": { + "name": "패딩", + "tooltip": "이미지 간의 간격입니다." + } + }, + "outputs": { + "0": { + "name": "이미지", + "tooltip": "처리된 이미지" + } } }, "ImageInvert": { @@ -3339,6 +4205,11 @@ "rotation": { "name": "회전" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageScale": { @@ -3387,6 +4258,11 @@ "upscale_method": { "name": "업스케일 방법" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageScaleToTotalPixels": { @@ -3398,6 +4274,9 @@ "megapixels": { "name": "메가픽셀수" }, + "resolution_steps": { + "name": "해상도 단계" + }, "upscale_method": { "name": "확대 방법" } @@ -3452,6 +4331,11 @@ "spacing_width": { "name": "간격 너비" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageToMask": { @@ -3463,6 +4347,11 @@ "image": { "name": "이미지" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageUpscaleWithModel": { @@ -3572,6 +4461,29 @@ "mask": { "name": "마스크" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "JoinAudioChannels": { + "description": "좌우 모노 오디오 채널을 스테레오 오디오로 합칩니다.", + "display_name": "오디오 채널 합치기", + "inputs": { + "audio_left": { + "name": "audio_left" + }, + "audio_right": { + "name": "audio_right" + } + }, + "outputs": { + "0": { + "name": "audio", + "tooltip": null + } } }, "JoinImageWithAlpha": { @@ -3697,6 +4609,58 @@ "sampler_name": { "name": "샘플러 이름" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Kandinsky5ImageToVideo": { + "display_name": "Kandinsky5ImageToVideo", + "inputs": { + "batch_size": { + "name": "배치 크기" + }, + "height": { + "name": "높이" + }, + "length": { + "name": "길이" + }, + "negative": { + "name": "negative" + }, + "positive": { + "name": "positive" + }, + "start_image": { + "name": "시작 이미지" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "너비" + } + }, + "outputs": { + "0": { + "name": "positive", + "tooltip": null + }, + "1": { + "name": "negative", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": "비어 있는 비디오 latent" + }, + "3": { + "name": "cond_latent", + "tooltip": "노이즈가 제거된 인코딩된 시작 이미지로, 모델 출력 latent의 노이즈 시작 부분을 대체하는 데 사용됩니다" + } } }, "KarrasScheduler": { @@ -3714,6 +4678,11 @@ "steps": { "name": "스텝 수" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "KlingCameraControlI2VNode": { @@ -3869,7 +4838,6 @@ } }, "KlingImage2VideoNode": { - "description": "Kling 이미지에서 비디오로 노드", "display_name": "Kling 비디오 생성 (이미지 → 비디오)", "inputs": { "aspect_ratio": { @@ -3957,6 +4925,35 @@ } } }, + "KlingImageToVideoWithAudio": { + "display_name": "Kling 이미지(첫 프레임) → 비디오 및 오디오", + "inputs": { + "duration": { + "name": "길이" + }, + "generate_audio": { + "name": "오디오 생성" + }, + "mode": { + "name": "모드" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "프롬프트", + "tooltip": "긍정적인 텍스트 프롬프트." + }, + "start_frame": { + "name": "시작 프레임" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingLipSyncAudioToVideoNode": { "description": "Kling 립싱크 오디오 투 비디오 노드. 비디오 파일의 입 모양 움직임을 오디오 파일의 오디오 내용에 맞게 동기화합니다.", "display_name": "Kling 립싱크 비디오 오디오와 함께", @@ -4018,6 +5015,227 @@ } } }, + "KlingMotionControl": { + "display_name": "Kling 모션 컨트롤", + "inputs": { + "character_orientation": { + "name": "캐릭터 방향", + "tooltip": "캐릭터의 시선/방향이 어디서 오는지 제어합니다.\n비디오: 움직임, 표정, 카메라 이동, 방향이 동작 참조 비디오를 따릅니다(기타 세부사항은 프롬프트로).\n이미지: 움직임과 표정은 여전히 동작 참조 비디오를 따르지만, 캐릭터 방향은 참조 이미지를 따릅니다(카메라/기타 세부사항은 프롬프트로)." + }, + "keep_original_sound": { + "name": "원본 사운드 유지" + }, + "mode": { + "name": "모드" + }, + "prompt": { + "name": "프롬프트" + }, + "reference_image": { + "name": "참조 이미지" + }, + "reference_video": { + "name": "참조 비디오", + "tooltip": "동작 참조 비디오로 움직임/표정을 제어합니다.\n길이 제한은 character_orientation에 따라 다릅니다:\n - 이미지: 3–10초 (최대 10초)\n - 비디오: 3–30초 (최대 30초)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProEditVideoNode": { + "description": "Kling의 최신 모델로 기존 비디오를 편집합니다.", + "display_name": "Kling 옴니 비디오 편집 (Pro)", + "inputs": { + "keep_original_sound": { + "name": "원본 사운드 유지" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "프롬프트", + "tooltip": "비디오 내용을 설명하는 텍스트 프롬프트입니다. 긍정적/부정적 설명 모두 포함할 수 있습니다." + }, + "reference_images": { + "name": "참조 이미지", + "tooltip": "최대 4개의 추가 참조 이미지를 사용할 수 있습니다." + }, + "resolution": { + "name": "해상도" + }, + "video": { + "name": "비디오", + "tooltip": "편집할 비디오입니다. 출력 비디오 길이는 동일합니다." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProFirstLastFrameNode": { + "description": "최신 Kling 모델을 사용하여 시작 프레임, 선택적 종료 프레임 또는 참조 이미지를 사용합니다.", + "display_name": "Kling Omni 첫-마지막 프레임에서 비디오로 (Pro)", + "inputs": { + "duration": { + "name": "지속 시간" + }, + "end_frame": { + "name": "종료 프레임", + "tooltip": "비디오의 선택적 종료 프레임입니다. 'reference_images'와 동시에 사용할 수 없습니다." + }, + "first_frame": { + "name": "시작 프레임" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "프롬프트", + "tooltip": "비디오 내용을 설명하는 텍스트 프롬프트입니다. 긍정적 및 부정적 설명을 모두 포함할 수 있습니다." + }, + "reference_images": { + "name": "참조 이미지", + "tooltip": "최대 6개의 추가 참조 이미지를 사용할 수 있습니다." + }, + "resolution": { + "name": "해상도" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageNode": { + "description": "최신 Kling 모델로 이미지를 생성하거나 편집합니다.", + "display_name": "Kling Omni 이미지 (Pro)", + "inputs": { + "aspect_ratio": { + "name": "화면 비율" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "프롬프트", + "tooltip": "이미지 내용을 설명하는 텍스트 프롬프트입니다. 긍정적 및 부정적 설명을 모두 포함할 수 있습니다." + }, + "reference_images": { + "name": "참조 이미지", + "tooltip": "최대 10개의 추가 참조 이미지를 사용할 수 있습니다." + }, + "resolution": { + "name": "해상도" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageToVideoNode": { + "description": "최신 Kling 모델로 최대 7개의 참조 이미지를 사용하여 비디오를 생성합니다.", + "display_name": "Kling Omni 이미지에서 비디오로 (Pro)", + "inputs": { + "aspect_ratio": { + "name": "화면 비율" + }, + "duration": { + "name": "지속 시간" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "프롬프트", + "tooltip": "비디오 내용을 설명하는 텍스트 프롬프트입니다. 긍정적 및 부정적 설명을 모두 포함할 수 있습니다." + }, + "reference_images": { + "name": "참조 이미지", + "tooltip": "최대 7개의 참조 이미지를 사용할 수 있습니다." + }, + "resolution": { + "name": "해상도" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProTextToVideoNode": { + "description": "최신 Kling 모델로 텍스트 프롬프트를 사용해 비디오를 생성합니다.", + "display_name": "Kling Omni 텍스트에서 비디오로 (Pro)", + "inputs": { + "aspect_ratio": { + "name": "화면 비율" + }, + "duration": { + "name": "지속 시간" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "프롬프트", + "tooltip": "비디오 내용을 설명하는 텍스트 프롬프트입니다. 긍정적 및 부정적 설명을 모두 포함할 수 있습니다." + }, + "resolution": { + "name": "해상도" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProVideoToVideoNode": { + "description": "비디오와 최대 4개의 참조 이미지를 사용하여 최신 Kling 모델로 비디오를 생성합니다.", + "display_name": "Kling Omni 비디오 투 비디오 (Pro)", + "inputs": { + "aspect_ratio": { + "name": "종횡비" + }, + "duration": { + "name": "길이" + }, + "keep_original_sound": { + "name": "원본 사운드 유지" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "프롬프트", + "tooltip": "비디오 내용을 설명하는 텍스트 프롬프트입니다. 긍정적 및 부정적 설명을 모두 포함할 수 있습니다." + }, + "reference_images": { + "name": "참조 이미지", + "tooltip": "최대 4개의 추가 참조 이미지." + }, + "reference_video": { + "name": "참조 비디오", + "tooltip": "참조로 사용할 비디오입니다." + }, + "resolution": { + "name": "해상도" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingSingleImageVideoEffectNode": { "description": "'효과 장면'에 따라 비디오를 생성할 때 다양한 특수 효과를 적용합니다.", "display_name": "Kling 비디오 효과", @@ -4132,6 +5350,35 @@ } } }, + "KlingTextToVideoWithAudio": { + "display_name": "Kling 텍스트 투 비디오 (오디오 포함)", + "inputs": { + "aspect_ratio": { + "name": "종횡비" + }, + "duration": { + "name": "길이" + }, + "generate_audio": { + "name": "오디오 생성" + }, + "mode": { + "name": "모드" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "프롬프트", + "tooltip": "긍정적인 텍스트 프롬프트." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingVideoExtendNode": { "description": "Kling 비디오 확장 노드입니다. 다른 Kling 노드에서 생성된 비디오를 확장합니다. video_id는 다른 Kling 노드를 사용하여 생성됩니다.", "display_name": "Kling 비디오 확장", @@ -4186,6 +5433,26 @@ } } }, + "LTXAVTextEncoderLoader": { + "description": "[Recipes]\n\nltxav: gemma 3 12B", + "display_name": "LTXV 오디오 텍스트 인코더 로더", + "inputs": { + "ckpt_name": { + "name": "ckpt_name" + }, + "device": { + "name": "device" + }, + "text_encoder": { + "name": "text_encoder" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LTXVAddGuide": { "display_name": "LTXV 가이드 추가", "inputs": { @@ -4228,6 +5495,76 @@ } } }, + "LTXVAudioVAEDecode": { + "display_name": "LTXV 오디오 VAE 디코드", + "inputs": { + "audio_vae": { + "name": "audio_vae", + "tooltip": "latent 디코딩에 사용되는 Audio VAE 모델." + }, + "samples": { + "name": "samples", + "tooltip": "디코딩할 latent." + } + }, + "outputs": { + "0": { + "name": "Audio", + "tooltip": null + } + } + }, + "LTXVAudioVAEEncode": { + "display_name": "LTXV 오디오 VAE 인코드", + "inputs": { + "audio": { + "name": "audio", + "tooltip": "인코딩할 오디오." + }, + "audio_vae": { + "name": "audio_vae", + "tooltip": "인코딩에 사용할 Audio VAE 모델." + } + }, + "outputs": { + "0": { + "name": "Audio Latent", + "tooltip": null + } + } + }, + "LTXVAudioVAELoader": { + "display_name": "LTXV 오디오 VAE 로더", + "inputs": { + "ckpt_name": { + "name": "ckpt_name", + "tooltip": "불러올 Audio VAE 체크포인트." + } + }, + "outputs": { + "0": { + "name": "Audio VAE", + "tooltip": null + } + } + }, + "LTXVConcatAVLatent": { + "display_name": "LTXVConcatAVLatent", + "inputs": { + "audio_latent": { + "name": "audio_latent" + }, + "video_latent": { + "name": "video_latent" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, "LTXVConditioning": { "display_name": "LTXV 조건 설정", "inputs": { @@ -4280,6 +5617,33 @@ } } }, + "LTXVEmptyLatentAudio": { + "display_name": "LTXV 빈 latent 오디오", + "inputs": { + "audio_vae": { + "name": "audio_vae", + "tooltip": "구성을 가져올 Audio VAE 모델." + }, + "batch_size": { + "name": "batch_size", + "tooltip": "배치 내 latent 오디오 샘플 수." + }, + "frame_rate": { + "name": "frame_rate", + "tooltip": "초당 프레임 수." + }, + "frames_number": { + "name": "frames_number", + "tooltip": "프레임 수." + } + }, + "outputs": { + "0": { + "name": "Latent", + "tooltip": null + } + } + }, "LTXVImgToVideo": { "display_name": "LTXV 비디오 생성 (이미지 → 비디오)", "inputs": { @@ -4326,6 +5690,47 @@ } } }, + "LTXVImgToVideoInplace": { + "display_name": "LTXVImgToVideoInplace", + "inputs": { + "bypass": { + "name": "우회", + "tooltip": "컨디셔닝을 우회합니다." + }, + "image": { + "name": "이미지" + }, + "latent": { + "name": "latent" + }, + "strength": { + "name": "강도" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, + "LTXVLatentUpsampler": { + "display_name": "LTXVLatentUpsampler", + "inputs": { + "samples": { + "name": "샘플" + }, + "upscale_model": { + "name": "업스케일 모델" + }, + "vae": { + "name": "vae" + } + } + }, "LTXVPreprocess": { "display_name": "LTXV 전처리", "inputs": { @@ -4374,6 +5779,25 @@ } } }, + "LTXVSeparateAVLatent": { + "description": "LTXV Separate AV Latent", + "display_name": "LTXVSeparateAVLatent", + "inputs": { + "av_latent": { + "name": "av_latent" + } + }, + "outputs": { + "0": { + "name": "video_latent", + "tooltip": null + }, + "1": { + "name": "audio_latent", + "tooltip": null + } + } + }, "LaplaceScheduler": { "display_name": "라플라스 스케줄러", "inputs": { @@ -4392,6 +5816,11 @@ "steps": { "name": "스텝 수" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LatentAdd": { @@ -4529,6 +5958,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LatentConcat": { @@ -4592,6 +6026,25 @@ } } }, + "LatentCutToBatch": { + "display_name": "LatentCutToBatch", + "inputs": { + "dim": { + "name": "차원" + }, + "samples": { + "name": "샘플" + }, + "slice_size": { + "name": "슬라이스 크기" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LatentFlip": { "display_name": "잠재 데이터 연산 (뒤집기)", "inputs": { @@ -4745,6 +6198,19 @@ } } }, + "LatentUpscaleModelLoader": { + "display_name": "Latent 업스케일 모델 불러오기", + "inputs": { + "model_name": { + "name": "model_name" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LazyCache": { "description": "EasyCache의 자체 제작 버전 - 구현이 '더 쉬운' EasyCache 버전입니다. 전체적으로는 EasyCache보다 성능이 떨어지지만, 일부 드문 경우에 더 좋으며 ComfyUI의 모든 것과 완벽한 호환성을 제공합니다.", "display_name": "LazyCache", @@ -4792,70 +6258,32 @@ }, "upload 3d model": { }, - "width": { - "name": "너비" - } - }, - "outputs": { - "0": { - "name": "이미지" - }, - "1": { - "name": "마스크" - }, - "2": { - "name": "메시 경로" - }, - "3": { - "name": "노멀" - }, - "4": { - "name": "라인아트" - }, - "5": { - "name": "카메라 정보" - }, - "6": { - "name": "비디오 녹화" - } - } - }, - "Load3DAnimation": { - "display_name": "3D 불러오기 - 애니메이션", - "inputs": { - "height": { - "name": "높이" - }, - "image": { - "name": "이미지" - }, - "model_file": { - "name": "모델 파일" + "upload extra resources": { }, "width": { "name": "너비" } }, - "outputs": { - "0": { - "name": "이미지" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "마스크" + { + "tooltip": null }, - "2": { - "name": "메시 경로" + { + "tooltip": null }, - "3": { - "name": "노멀" + { + "tooltip": null }, - "4": { - "name": "카메라 정보" + { + "tooltip": null }, - "5": { - "name": "비디오 녹화" + { + "tooltip": null } - } + ] }, "LoadAudio": { "display_name": "오디오 로드", @@ -4869,6 +6297,11 @@ "upload": { "name": "업로드할 파일 선택" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LoadImage": { @@ -4882,6 +6315,21 @@ } } }, + "LoadImageDataSetFromFolder": { + "display_name": "폴더에서 이미지 데이터셋 불러오기", + "inputs": { + "folder": { + "name": "폴더", + "tooltip": "이미지를 불러올 폴더입니다." + } + }, + "outputs": { + "0": { + "name": "이미지", + "tooltip": "불러온 이미지 목록" + } + } + }, "LoadImageMask": { "display_name": "마스크 이미지 로드", "inputs": { @@ -4900,6 +6348,8 @@ "description": "입력(input) 폴더 대신 출력(output) 폴더에서 이미지를 로드합니다. 새로 고침 버튼을 클릭하면 노드는 이미지 목록을 업데이트하고 자동으로 첫 번째 이미지를 선택하여 쉬운 반복을 가능하게 합니다.", "display_name": "이미지 로드 (출력에서)", "inputs": { + "Auto-refresh after generation": { + }, "image": { "name": "이미지" }, @@ -4910,41 +6360,22 @@ } } }, - "LoadImageSetFromFolderNode": { - "description": "학습을 위해 디렉토리에서 이미지 배치를 로드합니다.", - "display_name": "폴더에서 이미지 데이터셋 로드", + "LoadImageTextDataSetFromFolder": { + "display_name": "폴더에서 이미지 및 텍스트 데이터셋 불러오기", "inputs": { "folder": { "name": "폴더", - "tooltip": "이미지를 로드할 폴더입니다." - }, - "resize_method": { - "name": "크기 조정 방법" + "tooltip": "이미지를 불러올 폴더입니다." } - } - }, - "LoadImageTextSetFromFolderNode": { - "description": "학습을 위해 디렉토리에서 이미지와 캡션 배치를 로드합니다.", - "display_name": "폴더에서 이미지 및 텍스트 데이터셋 로드", - "inputs": { - "clip": { - "name": "CLIP", - "tooltip": "텍스트 인코딩에 사용되는 CLIP 모델입니다." + }, + "outputs": { + "0": { + "name": "이미지", + "tooltip": "불러온 이미지 목록" }, - "folder": { - "name": "폴더", - "tooltip": "이미지를 로드할 폴더입니다." - }, - "height": { - "name": "높이", - "tooltip": "이미지를 조정할 높이입니다. -1은 원본 높이를 사용함을 의미합니다." - }, - "resize_method": { - "name": "크기 조정 방법" - }, - "width": { - "name": "너비", - "tooltip": "이미지를 조정할 너비입니다. -1은 원본 너비를 사용함을 의미합니다." + "1": { + "name": "텍스트", + "tooltip": "텍스트 캡션 목록" } } }, @@ -4956,6 +6387,25 @@ } } }, + "LoadTrainingDataset": { + "display_name": "학습 데이터셋 불러오기", + "inputs": { + "folder_name": { + "name": "folder_name", + "tooltip": "저장된 데이터셋이 들어있는 폴더 이름 (output 디렉토리 내부)." + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "latent 딕셔너리 목록" + }, + "1": { + "name": "conditioning", + "tooltip": "conditioning 리스트 목록" + } + } + }, "LoadVideo": { "display_name": "비디오 불러오기", "inputs": { @@ -5027,7 +6477,6 @@ } }, "LoraModelLoader": { - "description": "Train LoRA 노드에서 학습된 LoRA 가중치를 불러옵니다.", "display_name": "LoRA 모델 로드", "inputs": { "lora": { @@ -5043,11 +6492,11 @@ "tooltip": "디퓨전 모델을 수정하는 강도입니다. 이 값은 음수일 수 있습니다." } }, - "outputs": { - "0": { - "tooltip": "수정된 디퓨전 모델입니다." + "outputs": [ + { + "name": "model" } - } + ] }, "LoraSave": { "display_name": "LoRA 추출 및 저장", @@ -5075,14 +6524,15 @@ } }, "LossGraphNode": { - "description": "손실 그래프를 그리고 출력 디렉토리에 저장합니다.", "display_name": "손실 그래프 그리기", "inputs": { "filename_prefix": { - "name": "파일명 접두사" + "name": "파일명 접두사", + "tooltip": "저장될 loss 그래프 이미지의 접두사." }, "loss": { - "name": "손실" + "name": "손실", + "tooltip": "학습 노드에서 가져온 loss 맵." } } }, @@ -5388,6 +6838,50 @@ } } }, + "MakeTrainingDataset": { + "display_name": "학습 데이터셋 만들기", + "inputs": { + "clip": { + "name": "clip", + "tooltip": "텍스트를 컨디셔닝으로 인코딩하는 CLIP 모델입니다." + }, + "images": { + "name": "이미지", + "tooltip": "인코딩할 이미지 목록입니다." + }, + "texts": { + "name": "텍스트", + "tooltip": "텍스트 캡션 목록입니다. 이미지 개수(n)와 일치하거나, 1개(모두에 반복), 또는 생략(빈 문자열 사용)할 수 있습니다." + }, + "vae": { + "name": "vae", + "tooltip": "이미지를 latent로 인코딩하는 VAE 모델입니다." + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "latent 딕셔너리 목록" + }, + "1": { + "name": "conditioning", + "tooltip": "컨디셔닝 리스트 목록" + } + } + }, + "ManualSigmas": { + "display_name": "ManualSigmas", + "inputs": { + "sigmas": { + "name": "시그마" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "MaskComposite": { "display_name": "마스크 합성", "inputs": { @@ -5406,6 +6900,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "MaskPreview": { @@ -5423,6 +6922,315 @@ "mask": { "name": "마스크" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MergeImageLists": { + "display_name": "이미지 리스트 병합", + "inputs": { + "images": { + "name": "이미지", + "tooltip": "처리할 이미지 목록입니다." + } + }, + "outputs": { + "0": { + "name": "이미지", + "tooltip": "처리된 이미지" + } + } + }, + "MergeTextLists": { + "display_name": "텍스트 리스트 병합", + "inputs": { + "texts": { + "name": "텍스트", + "tooltip": "처리할 텍스트 목록입니다." + } + }, + "outputs": { + "0": { + "name": "텍스트", + "tooltip": "처리된 텍스트" + } + } + }, + "MeshyAnimateModelNode": { + "description": "이전에 리깅된 캐릭터에 특정 애니메이션 동작을 적용합니다.", + "display_name": "Meshy: 모델 애니메이션", + "inputs": { + "action_id": { + "name": "action_id", + "tooltip": "사용 가능한 값 목록은 https://docs.meshy.ai/en/api/animation-library 를 방문하세요." + }, + "rig_task_id": { + "name": "rig_task_id" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + } + } + }, + "MeshyImageToModelNode": { + "display_name": "Meshy: 이미지 → 모델", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "image": { + "name": "image" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "생성된 모델의 포즈 모드를 지정합니다." + }, + "seed": { + "name": "seed", + "tooltip": "시드는 노드가 다시 실행될지 여부를 제어합니다. 시드와 관계없이 결과는 비결정적입니다." + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "거짓으로 설정하면, 가공되지 않은 삼각형 메시를 반환합니다." + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "should_texture": { + "name": "should_texture", + "tooltip": "텍스처 생성 여부를 결정합니다. 거짓으로 설정하면 텍스처 단계가 생략되고 텍스처 없는 메시가 반환됩니다." + }, + "should_texture_enable_pbr": { + "name": "enable_pbr" + }, + "should_texture_texture_prompt": { + "name": "texture_prompt" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyMultiImageToModelNode": { + "display_name": "Meshy: 다중 이미지 → 모델", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "생성된 모델의 포즈 모드를 지정합니다." + }, + "seed": { + "name": "seed", + "tooltip": "시드는 노드가 다시 실행될지 여부를 제어합니다. 시드와 관계없이 결과는 비결정적입니다." + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "거짓으로 설정하면, 가공되지 않은 삼각형 메시를 반환합니다." + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "should_texture": { + "name": "should_texture", + "tooltip": "텍스처 생성 여부를 결정합니다. 거짓으로 설정하면 텍스처 단계가 생략되고 텍스처 없는 메시가 반환됩니다." + }, + "should_texture_enable_pbr": { + "name": "enable_pbr" + }, + "should_texture_texture_prompt": { + "name": "texture_prompt" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRefineNode": { + "description": "이전에 생성된 초안 모델을 다듬습니다.", + "display_name": "Meshy: 모델 다듬기", + "inputs": { + "enable_pbr": { + "name": "enable_pbr", + "tooltip": "기본 색상 외에 PBR 맵(금속, 거칠기, 노멀)을 생성합니다. 참고: Sculpture 스타일을 사용할 때는 Sculpture 스타일이 자체 PBR 맵을 생성하므로 false로 설정해야 합니다." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "model": { + "name": "model" + }, + "texture_image": { + "name": "texture_image", + "tooltip": "'texture_image' 또는 'texture_prompt' 중 하나만 동시에 사용할 수 있습니다." + }, + "texture_prompt": { + "name": "texture_prompt", + "tooltip": "텍스처링 과정을 안내할 텍스트 프롬프트를 입력하세요. 최대 600자. 'texture_image'와 동시에 사용할 수 없습니다." + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRigModelNode": { + "description": "표준 포맷으로 리깅된 캐릭터를 제공합니다. 자동 리깅은 텍스처가 없는 메시, 비휴머노이드 자산, 또는 팔다리와 신체 구조가 불분명한 휴머노이드 자산에는 적합하지 않습니다.", + "display_name": "Meshy: 모델 리깅", + "inputs": { + "height_meters": { + "name": "height_meters", + "tooltip": "캐릭터 모델의 대략적인 높이(미터 단위)입니다. 스케일링과 리깅 정확도에 도움이 됩니다." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "texture_image": { + "name": "texture_image", + "tooltip": "모델의 UV 언랩된 기본 색상 텍스처 이미지입니다." + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "rig_task_id", + "tooltip": null + } + } + }, + "MeshyTextToModelNode": { + "display_name": "Meshy: 텍스트 → 모델", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "생성된 모델의 포즈 모드를 지정합니다." + }, + "prompt": { + "name": "prompt" + }, + "seed": { + "name": "seed", + "tooltip": "시드는 노드가 다시 실행될지 여부를 제어합니다. 시드와 관계없이 결과는 비결정적입니다." + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "false로 설정하면 가공되지 않은 삼각형 메시를 반환합니다." + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "style": { + "name": "style" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyTextureNode": { + "display_name": "Meshy: 텍스처 모델", + "inputs": { + "enable_original_uv": { + "name": "원본 UV 사용", + "tooltip": "새로운 UV를 생성하는 대신 모델의 원본 UV를 사용합니다. 활성화하면 Meshy는 업로드된 모델의 기존 텍스처를 보존합니다. 모델에 원본 UV가 없을 경우 출력 품질이 저하될 수 있습니다." + }, + "image_style": { + "name": "이미지 스타일", + "tooltip": "텍스처링 과정을 안내할 2D 이미지를 사용합니다. 'text_style_prompt'와 동시에 사용할 수 없습니다." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "model": { + "name": "model" + }, + "pbr": { + "name": "pbr" + }, + "text_style_prompt": { + "name": "텍스트 스타일 프롬프트", + "tooltip": "오브젝트의 원하는 텍스처 스타일을 텍스트로 설명하세요. 최대 600자까지 입력할 수 있습니다. 'image_style'과 동시에 사용할 수 없습니다." + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } } }, "MinimaxHailuoVideoNode": { @@ -7866,6 +9674,52 @@ } } }, + "NormalizeImages": { + "display_name": "이미지 정규화", + "inputs": { + "images": { + "name": "이미지", + "tooltip": "처리할 이미지입니다." + }, + "mean": { + "name": "평균값", + "tooltip": "정규화에 사용할 평균값입니다." + }, + "std": { + "name": "표준편차", + "tooltip": "정규화에 사용할 표준편차입니다." + } + }, + "outputs": { + "0": { + "name": "이미지", + "tooltip": "처리된 이미지" + } + } + }, + "NormalizeVideoLatentStart": { + "description": "비디오 latent의 초기 프레임을 이후 참조 프레임의 평균 및 표준편차에 맞게 정규화합니다. 시작 프레임과 나머지 비디오 간의 차이를 줄이는 데 도움이 됩니다.", + "display_name": "NormalizeVideoLatentStart", + "inputs": { + "latent": { + "name": "latent" + }, + "reference_frame_count": { + "name": "reference_frame_count", + "tooltip": "참조로 사용할 시작 프레임 이후의 latent 프레임 수" + }, + "start_frame_count": { + "name": "start_frame_count", + "tooltip": "정규화할 latent 프레임 수(시작부터 계산)" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, "OpenAIChatConfig": { "description": "OpenAI 채팅 노드에 대한 고급 구성 옵션을 지정할 수 있습니다.", "display_name": "OpenAI ChatGPT 고급 옵션", @@ -8015,6 +9869,9 @@ "name": "마스크", "tooltip": "인페인팅을 위한 선택적 마스크 (흰색 영역이 대체됨)" }, + "model": { + "name": "model" + }, "n": { "name": "개수", "tooltip": "생성할 이미지의 수" @@ -8373,265 +10230,6 @@ } } }, - "PikaImageToVideoNode2_2": { - "description": "이미지와 프롬프트를 Pika API v2.2에 전송하여 비디오를 생성합니다.", - "display_name": "Pika 비디오 생성 (이미지 → 비디오)", - "inputs": { - "control_after_generate": { - "name": "생성 후 제어" - }, - "duration": { - "name": "길이" - }, - "image": { - "name": "이미지", - "tooltip": "비디오로 변환할 이미지" - }, - "negative_prompt": { - "name": "부정 프롬프트" - }, - "prompt_text": { - "name": "프롬프트" - }, - "resolution": { - "name": "해상도" - }, - "seed": { - "name": "시드" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaScenesV2_2": { - "description": "여러 이미지를 결합하여 이미지 속 객체들이 포함된 비디오를 만듭니다. 여러 이미지를 재료로 업로드하고, 이 모든 이미지를 반영한 고품질 비디오를 생성하세요.", - "display_name": "Pika Scenes (비디오 이미지 합성)", - "inputs": { - "aspect_ratio": { - "name": "종횡비", - "tooltip": "종횡비 (가로 / 세로)" - }, - "control_after_generate": { - "name": "생성 후 제어" - }, - "duration": { - "name": "길이" - }, - "image_ingredient_1": { - "name": "이미지 재료 1", - "tooltip": "비디오 생성을 위한 재료로 사용할 이미지입니다." - }, - "image_ingredient_2": { - "name": "이미지 재료 2", - "tooltip": "비디오 생성을 위한 재료로 사용할 이미지입니다." - }, - "image_ingredient_3": { - "name": "이미지 재료 3", - "tooltip": "비디오 생성을 위한 재료로 사용할 이미지입니다." - }, - "image_ingredient_4": { - "name": "이미지 재료 4", - "tooltip": "비디오 생성을 위한 재료로 사용할 이미지입니다." - }, - "image_ingredient_5": { - "name": "이미지 재료 5", - "tooltip": "비디오 생성을 위한 재료로 사용할 이미지입니다." - }, - "ingredients_mode": { - "name": "재료 모드" - }, - "negative_prompt": { - "name": "부정 프롬프트" - }, - "prompt_text": { - "name": "프롬프트 텍스트" - }, - "resolution": { - "name": "해상도" - }, - "seed": { - "name": "시드" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaStartEndFrameNode2_2": { - "description": "첫 번째 프레임과 마지막 프레임을 결합하여 비디오를 생성합니다. 시작점과 종료점을 정의할 두 이미지를 업로드하면, AI가 그 사이를 부드럽게 전환하는 영상을 만들어줍니다.", - "display_name": "Pika 비디오 생성 (시작-끝 프레임)", - "inputs": { - "control_after_generate": { - "name": "생성 후 제어" - }, - "duration": { - "name": "길이" - }, - "image_end": { - "name": "끝 이미지", - "tooltip": "결합할 마지막 이미지입니다." - }, - "image_start": { - "name": "시작 이미지", - "tooltip": "결합할 첫 번째 이미지입니다." - }, - "negative_prompt": { - "name": "부정 프롬프트" - }, - "prompt_text": { - "name": "프롬프트 텍스트" - }, - "resolution": { - "name": "해상도" - }, - "seed": { - "name": "시드" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaTextToVideoNode2_2": { - "description": "텍스트 프롬프트를 Pika API v2.2에 전송하여 비디오를 생성합니다.", - "display_name": "Pika 비디오 생성 (텍스트 → 비디오)", - "inputs": { - "aspect_ratio": { - "name": "종횡비", - "tooltip": "종횡비 (가로 / 세로)" - }, - "control_after_generate": { - "name": "생성 후 제어" - }, - "duration": { - "name": "길이" - }, - "negative_prompt": { - "name": "부정 프롬프트" - }, - "prompt_text": { - "name": "프롬프트 텍스트" - }, - "resolution": { - "name": "해상도" - }, - "seed": { - "name": "시드" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikadditions": { - "description": "비디오에 원하는 객체나 이미지를 추가하세요. 비디오를 업로드하고 추가하고 싶은 내용을 지정하면 자연스럽게 통합된 결과를 얻을 수 있습니다.", - "display_name": "Pikadditions (비디오 객체 삽입)", - "inputs": { - "control_after_generate": { - "name": "생성 후 제어" - }, - "image": { - "name": "이미지", - "tooltip": "비디오에 추가할 이미지입니다." - }, - "negative_prompt": { - "name": "부정 프롬프트" - }, - "prompt_text": { - "name": "프롬프트 텍스트" - }, - "seed": { - "name": "시드" - }, - "video": { - "name": "비디오", - "tooltip": "이미지를 추가할 비디오입니다." - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikaffects": { - "description": "특정 Pikaffect로 비디오를 생성합니다. 지원되는 Pikaffect: Cake-ify, Crumble, Crush, Decapitate, Deflate, Dissolve, Explode, Eye-pop, Inflate, Levitate, Melt, Peel, Poke, Squish, Ta-da, Tear", - "display_name": "Pikaffects (비디오 효과)", - "inputs": { - "control_after_generate": { - "name": "생성 후 제어" - }, - "image": { - "name": "이미지", - "tooltip": "Pikaffect를 적용할 기준 이미지입니다." - }, - "negative_prompt": { - "name": "부정 프롬프트" - }, - "pikaffect": { - "name": "pikaffect" - }, - "prompt_text": { - "name": "프롬프트 텍스트" - }, - "seed": { - "name": "시드" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikaswaps": { - "description": "비디오의 어떤 객체나 영역도 새로운 이미지나 객체로 교체하세요. 마스크나 좌표를 사용해 교체할 영역을 정의할 수 있습니다.", - "display_name": "Pika Swaps (비디오 객체 교체)", - "inputs": { - "control_after_generate": { - "name": "생성 후 제어" - }, - "image": { - "name": "이미지", - "tooltip": "비디오에서 마스킹된 객체를 교체하는 데 사용되는 이미지입니다." - }, - "mask": { - "name": "마스크", - "tooltip": "비디오에서 교체할 영역을 정의하려면 마스크를 사용하세요." - }, - "negative_prompt": { - "name": "부정 프롬프트" - }, - "prompt_text": { - "name": "프롬프트 텍스트" - }, - "region_to_modify": { - "name": "수정할 영역", - "tooltip": "수정할 객체/영역의 일반 텍스트 설명." - }, - "seed": { - "name": "시드" - }, - "video": { - "name": "비디오", - "tooltip": "객체를 교체할 비디오입니다." - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, "PixverseImageToVideoNode": { "description": "프롬프트와 output_size에 따라 동기적으로 비디오를 생성합니다.", "display_name": "PixVerse 이미지에서 비디오로", @@ -8786,6 +10384,11 @@ "steps": { "name": "스텝 수" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "PorterDuffImageComposite": { @@ -8819,6 +10422,9 @@ "Preview3D": { "display_name": "3D 미리보기", "inputs": { + "bg_image": { + "name": "bg_image" + }, "camera_info": { "name": "카메라 정보" }, @@ -8830,22 +10436,13 @@ } } }, - "Preview3DAnimation": { - "display_name": "3D 미리보기 - 애니메이션", - "inputs": { - "camera_info": { - "name": "카메라 정보" - }, - "model_file": { - "name": "모델 파일" - } - } - }, "PreviewAny": { "display_name": "미리보기 아무거나", "inputs": { "preview": { }, + "previewMode": { + }, "source": { "name": "소스" } @@ -8985,6 +10582,36 @@ } } }, + "RandomCropImages": { + "display_name": "무작위 이미지 자르기", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "height": { + "name": "height", + "tooltip": "자르기 높이." + }, + "images": { + "name": "images", + "tooltip": "처리할 이미지." + }, + "seed": { + "name": "seed", + "tooltip": "무작위 시드." + }, + "width": { + "name": "width", + "tooltip": "자르기 너비." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "처리된 이미지" + } + } + }, "RandomNoise": { "display_name": "무작위 노이즈", "inputs": { @@ -8994,6 +10621,11 @@ "noise_seed": { "name": "노이즈 시드" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RebatchImages": { @@ -9034,6 +10666,11 @@ "audio": { "name": "오디오" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RecraftColorRGB": { @@ -9538,6 +11175,11 @@ "image": { "name": "이미지" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RepeatLatentBatch": { @@ -9551,6 +11193,51 @@ } } }, + "ReplaceText": { + "display_name": "텍스트 교체", + "inputs": { + "find": { + "name": "find", + "tooltip": "찾을 텍스트." + }, + "replace": { + "name": "replace", + "tooltip": "교체할 텍스트." + }, + "texts": { + "name": "texts", + "tooltip": "처리할 텍스트." + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "처리된 텍스트" + } + } + }, + "ReplaceVideoLatentFrames": { + "display_name": "비디오 Latent 프레임 교체", + "inputs": { + "destination": { + "name": "destination", + "tooltip": "프레임이 교체될 대상 latent." + }, + "index": { + "name": "index", + "tooltip": "소스 latent 프레임이 대상 latent에 삽입될 시작 프레임 인덱스. 음수 값은 끝에서부터 계산." + }, + "source": { + "name": "source", + "tooltip": "대상 latent에 삽입할 프레임을 제공하는 소스 latent. 제공되지 않으면 대상 latent가 변경 없이 반환됩니다." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "RescaleCFG": { "display_name": "CFG 리스케일", "inputs": { @@ -9580,6 +11267,104 @@ "target_width": { "name": "대상 너비" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ResizeImageMaskNode": { + "description": "여러 가지 스케일링 방법을 사용하여 이미지 또는 mask를 리사이즈합니다.", + "display_name": "이미지/마스크 크기 조정", + "inputs": { + "input": { + "name": "input" + }, + "resize_type": { + "name": "resize_type", + "tooltip": "리사이즈 방법을 선택하세요: 정확한 크기, 배율, 다른 이미지와 일치 등." + }, + "resize_type_crop": { + "name": "crop" + }, + "resize_type_height": { + "name": "height" + }, + "resize_type_width": { + "name": "width" + }, + "scale_method": { + "name": "scale_method", + "tooltip": "보간 알고리즘입니다. 'area'는 축소에, 'lanczos'는 확대에, 'nearest-exact'는 픽셀 아트에 적합합니다." + } + }, + "outputs": { + "0": { + "name": "resized", + "tooltip": null + } + } + }, + "ResizeImagesByLongerEdge": { + "display_name": "긴 변 기준 이미지 크기 조정", + "inputs": { + "images": { + "name": "images", + "tooltip": "처리할 이미지." + }, + "longer_edge": { + "name": "longer_edge", + "tooltip": "긴 변의 목표 길이." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "처리된 이미지" + } + } + }, + "ResizeImagesByShorterEdge": { + "display_name": "짧은 변 기준 이미지 크기 조정", + "inputs": { + "images": { + "name": "images", + "tooltip": "처리할 이미지." + }, + "shorter_edge": { + "name": "shorter_edge", + "tooltip": "짧은 변의 목표 길이." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "처리된 이미지" + } + } + }, + "ResolutionBucket": { + "display_name": "해상도 버킷", + "inputs": { + "conditioning": { + "name": "conditioning", + "tooltip": "conditioning 리스트 목록 (latents 길이와 일치해야 함)." + }, + "latents": { + "name": "latents", + "tooltip": "해상도별로 버킷팅할 latent 딕셔너리 목록." + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "해상도 버킷별로 배치된 latent 딕셔너리 목록." + }, + "1": { + "name": "conditioning", + "tooltip": "해상도 버킷별로 조건 리스트 목록." + } } }, "Rodin3D_Detail": { @@ -9833,6 +11618,11 @@ "steps": { "name": "스텝 수" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SD_4XUpscale_Conditioning": { @@ -9986,14 +11776,14 @@ "name": "시그마 배열" } }, - "outputs": { - "0": { - "name": "출력" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "노이즈 제거된 출력" + { + "tooltip": null } - } + ] }, "SamplerCustomAdvanced": { "display_name": "고급 사용자 정의 샘플러", @@ -10014,14 +11804,14 @@ "name": "시그마 배열" } }, - "outputs": { - "0": { - "name": "출력" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "노이즈 제거된 출력" + { + "tooltip": null } - } + ] }, "SamplerDPMAdaptative": { "display_name": "DPMAdaptive 샘플러", @@ -10056,6 +11846,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_2M_SDE": { @@ -10073,6 +11868,11 @@ "solver_type": { "name": "Solver 유형" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_2S_Ancestral": { @@ -10084,6 +11884,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_3M_SDE": { @@ -10098,6 +11903,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_SDE": { @@ -10115,6 +11925,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerER_SDE": { @@ -10133,6 +11948,11 @@ "solver_type": { "name": "solver_type" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerAncestral": { @@ -10144,6 +11964,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerAncestralCFGPP": { @@ -10155,6 +11980,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerCFGpp": { @@ -10195,6 +12025,11 @@ "order": { "name": "순서" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerSASolver": { @@ -10227,6 +12062,37 @@ "use_pece": { "name": "PECE 사용" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerSEEDS2": { + "description": "이 샘플러 노드는 여러 샘플러를 나타낼 수 있습니다:\n\nseeds_2\n- 기본 설정\n\nexp_heun_2_x0\n- solver_type=phi_2, r=1.0, eta=0.0\n\nexp_heun_2_x0_sde\n- solver_type=phi_2, r=1.0, eta=1.0, s_noise=1.0", + "display_name": "SamplerSEEDS2", + "inputs": { + "eta": { + "name": "eta", + "tooltip": "확률적 강도" + }, + "r": { + "name": "r", + "tooltip": "중간 단계의 상대 스텝 크기 (c2 노드)" + }, + "s_noise": { + "name": "s_noise", + "tooltip": "SDE 노이즈 배수" + }, + "solver_type": { + "name": "solver_type" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplingPercentToSigma": { @@ -10243,11 +12109,11 @@ "name": "샘플링 백분율" } }, - "outputs": { - "0": { - "name": "시그마 값" + "outputs": [ + { + "tooltip": null } - } + ] }, "SaveAnimatedPNG": { "display_name": "애니메이션 PNG 저장", @@ -10365,6 +12231,44 @@ } } }, + "SaveImageDataSetToFolder": { + "display_name": "이미지 데이터셋을 폴더에 저장", + "inputs": { + "filename_prefix": { + "name": "filename_prefix", + "tooltip": "저장된 이미지 파일 이름의 접두사입니다." + }, + "folder_name": { + "name": "folder_name", + "tooltip": "이미지를 저장할 폴더 이름(출력 디렉터리 내)." + }, + "images": { + "name": "images", + "tooltip": "저장할 이미지 목록입니다." + } + } + }, + "SaveImageTextDataSetToFolder": { + "display_name": "이미지 및 텍스트 데이터셋을 폴더에 저장", + "inputs": { + "filename_prefix": { + "name": "filename_prefix", + "tooltip": "저장된 이미지 파일 이름의 접두사입니다." + }, + "folder_name": { + "name": "folder_name", + "tooltip": "이미지를 저장할 폴더 이름(출력 디렉터리 내)." + }, + "images": { + "name": "images", + "tooltip": "저장할 이미지 목록입니다." + }, + "texts": { + "name": "texts", + "tooltip": "저장할 텍스트 캡션 목록입니다." + } + } + }, "SaveImageWebsocket": { "display_name": "이미지 웹소켓 전송", "inputs": { @@ -10384,20 +12288,20 @@ } } }, - "SaveLoRANode": { + "SaveLoRA": { "display_name": "LoRA 가중치 저장", "inputs": { "lora": { - "name": "LoRA", - "tooltip": "저장할 LoRA 모델. LoRA 레이어가 포함된 모델은 사용하지 마십시오." + "name": "lora", + "tooltip": "저장할 LoRA 모델입니다. LoRA 레이어가 있는 모델은 사용하지 마세요." }, "prefix": { - "name": "접두사", - "tooltip": "저장된 LoRA 파일에 사용할 접두사." + "name": "prefix", + "tooltip": "저장된 LoRA 파일에 사용할 접두사입니다." }, "steps": { - "name": "스텝", - "tooltip": "선택사항: LoRA가 훈련된 스텝 수로, 저장된 파일 이름에 사용됩니다." + "name": "steps", + "tooltip": "선택 사항: LoRA가 학습된 스텝 수로, 저장 파일 이름에 사용됩니다." } } }, @@ -10414,6 +12318,27 @@ } } }, + "SaveTrainingDataset": { + "display_name": "학습 데이터셋 저장", + "inputs": { + "conditioning": { + "name": "conditioning", + "tooltip": "MakeTrainingDataset에서 생성된 conditioning 리스트 목록입니다." + }, + "folder_name": { + "name": "folder_name", + "tooltip": "데이터셋을 저장할 폴더 이름(출력 디렉터리 내)." + }, + "latents": { + "name": "latents", + "tooltip": "MakeTrainingDataset에서 생성된 latent 딕셔너리 목록입니다." + }, + "shard_size": { + "name": "shard_size", + "tooltip": "샤드 파일당 샘플 개수입니다." + } + } + }, "SaveVideo": { "description": "입력 이미지를 ComfyUI 출력 디렉토리에 저장합니다.", "display_name": "비디오 저장", @@ -10534,6 +12459,11 @@ "sigmas": { "name": "시그마 배열" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SetHookKeyframes": { @@ -10574,6 +12504,58 @@ } } }, + "ShuffleDataset": { + "display_name": "이미지 데이터셋 섞기", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images", + "tooltip": "처리할 이미지 목록입니다." + }, + "seed": { + "name": "seed", + "tooltip": "랜덤 시드입니다." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "처리된 이미지" + } + } + }, + "ShuffleImageTextDataset": { + "display_name": "이미지-텍스트 데이터셋 섞기", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images", + "tooltip": "섞을 이미지 목록입니다." + }, + "seed": { + "name": "seed", + "tooltip": "랜덤 시드입니다." + }, + "texts": { + "name": "texts", + "tooltip": "섞을 텍스트 목록입니다." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "섞인 이미지" + }, + "1": { + "name": "texts", + "tooltip": "섞인 텍스트" + } + } + }, "SkipLayerGuidanceDiT": { "description": "모든 DiT 모델에서 사용할 수 있는 '레이어 건너뛰기 가이던스' 노드의 범용 버전입니다.", "display_name": "레이어 건너뛰기 가이던스 (DiT)", @@ -10670,6 +12652,11 @@ "width": { "name": "너비" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SplitAudioChannels": { @@ -10680,14 +12667,14 @@ "name": "오디오" } }, - "outputs": { - "0": { - "name": "왼쪽" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "오른쪽" + { + "tooltip": null } - } + ] }, "SplitImageWithAlpha": { "display_name": "이미지와 알파채널 분리", @@ -10715,14 +12702,14 @@ "name": "분할 스텝" } }, - "outputs": { - "0": { - "name": "높은 시그마 배열" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "낮은 시그마 배열" + { + "tooltip": null } - } + ] }, "SplitSigmasDenoise": { "display_name": "시그마 배열 분할 (노이즈 제거양)", @@ -10734,14 +12721,14 @@ "name": "시그마 배열" } }, - "outputs": { - "0": { - "name": "높은 시그마 배열" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "낮은 시그마 배열" + { + "tooltip": null } - } + ] }, "StabilityAudioInpaint": { "description": "텍스트 지침을 사용하여 기존 오디오 샘플의 일부를 변환합니다.", @@ -11343,6 +13330,21 @@ } } }, + "StripWhitespace": { + "display_name": "공백 제거", + "inputs": { + "texts": { + "name": "texts", + "tooltip": "처리할 텍스트입니다." + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "처리된 텍스트" + } + } + }, "StyleModelApply": { "display_name": "스타일 모델 적용", "inputs": { @@ -11428,6 +13430,84 @@ } } }, + "TencentImageToModelNode": { + "display_name": "Hunyuan3D: 이미지 → 모델 (Pro)", + "inputs": { + "control_after_generate": { + "name": "생성 후 제어" + }, + "face_count": { + "name": "면 개수" + }, + "generate_type": { + "name": "생성 유형" + }, + "generate_type_pbr": { + "name": "PBR" + }, + "image": { + "name": "이미지" + }, + "image_back": { + "name": "뒷면 이미지" + }, + "image_left": { + "name": "왼쪽 이미지" + }, + "image_right": { + "name": "오른쪽 이미지" + }, + "model": { + "name": "모델", + "tooltip": "`3.1` 모델에서는 LowPoly 옵션을 사용할 수 없습니다." + }, + "seed": { + "name": "시드", + "tooltip": "시드는 노드가 다시 실행될지 여부를 제어합니다. 시드와 관계없이 결과는 비결정적입니다." + } + }, + "outputs": { + "0": { + "name": "모델 파일", + "tooltip": null + } + } + }, + "TencentTextToModelNode": { + "display_name": "Hunyuan3D: 텍스트 → 모델 (Pro)", + "inputs": { + "control_after_generate": { + "name": "생성 후 제어" + }, + "face_count": { + "name": "면 개수" + }, + "generate_type": { + "name": "생성 유형" + }, + "generate_type_pbr": { + "name": "PBR" + }, + "model": { + "name": "모델", + "tooltip": "`3.1` 모델에서는 LowPoly 옵션을 사용할 수 없습니다." + }, + "prompt": { + "name": "프롬프트", + "tooltip": "최대 1024자까지 지원합니다." + }, + "seed": { + "name": "시드", + "tooltip": "시드는 노드가 다시 실행될지 여부를 제어합니다. 시드와 관계없이 결과는 비결정적입니다." + } + }, + "outputs": { + "0": { + "name": "모델 파일", + "tooltip": null + } + } + }, "TextEncodeAceStepAudio": { "display_name": "TextEncodeAceStepAudio", "inputs": { @@ -11523,6 +13603,70 @@ } } }, + "TextEncodeZImageOmni": { + "display_name": "TextEncodeZImageOmni", + "inputs": { + "auto_resize_images": { + "name": "이미지 자동 크기 조정" + }, + "clip": { + "name": "clip" + }, + "image1": { + "name": "이미지1" + }, + "image2": { + "name": "이미지2" + }, + "image3": { + "name": "이미지3" + }, + "image_encoder": { + "name": "이미지 인코더" + }, + "prompt": { + "name": "프롬프트" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TextToLowercase": { + "display_name": "텍스트 소문자 변환", + "inputs": { + "texts": { + "name": "texts", + "tooltip": "처리할 텍스트입니다." + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "처리된 텍스트" + } + } + }, + "TextToUppercase": { + "display_name": "텍스트 대문자 변환", + "inputs": { + "texts": { + "name": "texts", + "tooltip": "처리할 텍스트입니다." + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "처리된 텍스트" + } + } + }, "ThresholdMask": { "display_name": "임계값 마스크", "inputs": { @@ -11532,6 +13676,11 @@ "value": { "name": "값" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "TomePatchModel": { @@ -11550,6 +13699,118 @@ } } }, + "TopazImageEnhance": { + "description": "업계 표준 업스케일링 및 이미지 향상.", + "display_name": "Topaz 이미지 향상", + "inputs": { + "color_preservation": { + "name": "color_preservation", + "tooltip": "원본 색상을 보존합니다." + }, + "creativity": { + "name": "creativity" + }, + "crop_to_fill": { + "name": "crop_to_fill", + "tooltip": "기본적으로 출력 종횡비가 다를 때 이미지는 레터박스 처리됩니다. 출력 크기에 맞게 이미지를 자르려면 활성화하세요." + }, + "face_enhancement": { + "name": "face_enhancement", + "tooltip": "처리 중 얼굴(존재 시)을 향상합니다." + }, + "face_enhancement_creativity": { + "name": "face_enhancement_creativity", + "tooltip": "얼굴 향상에 대한 창의성 수준을 설정합니다." + }, + "face_enhancement_strength": { + "name": "face_enhancement_strength", + "tooltip": "향상된 얼굴이 배경에 비해 얼마나 선명한지 조절합니다." + }, + "face_preservation": { + "name": "face_preservation", + "tooltip": "피사체의 얼굴 정체성을 보존합니다." + }, + "image": { + "name": "image" + }, + "model": { + "name": "model" + }, + "output_height": { + "name": "output_height", + "tooltip": "0으로 설정하면 원본과 동일한 높이 또는 output_width에 맞춰 출력됩니다." + }, + "output_width": { + "name": "output_width", + "tooltip": "0으로 설정하면 자동으로 계산됩니다(일반적으로 원본 크기 또는 output_height가 지정된 경우 해당 값 사용)." + }, + "prompt": { + "name": "prompt", + "tooltip": "창의적인 업스케일링 가이드를 위한 선택적 텍스트 프롬프트입니다." + }, + "subject_detection": { + "name": "subject_detection" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TopazVideoEnhance": { + "description": "강력한 업스케일링 및 복원 기술로 비디오에 새 생명을 불어넣으세요.", + "display_name": "Topaz Video Enhance", + "inputs": { + "dynamic_compression_level": { + "name": "dynamic_compression_level", + "tooltip": "CQP 레벨." + }, + "interpolation_duplicate": { + "name": "interpolation_duplicate", + "tooltip": "입력에서 중복 프레임을 분석하여 제거합니다." + }, + "interpolation_duplicate_threshold": { + "name": "interpolation_duplicate_threshold", + "tooltip": "중복 프레임 감지 민감도." + }, + "interpolation_enabled": { + "name": "interpolation_enabled" + }, + "interpolation_frame_rate": { + "name": "interpolation_frame_rate", + "tooltip": "출력 프레임 속도." + }, + "interpolation_model": { + "name": "interpolation_model" + }, + "interpolation_slowmo": { + "name": "interpolation_slowmo", + "tooltip": "입력 비디오에 적용되는 슬로우 모션 배수입니다. 예를 들어, 2로 설정하면 출력이 두 배 느려지고 길이도 두 배가 됩니다." + }, + "upscaler_creativity": { + "name": "upscaler_creativity", + "tooltip": "창의성 수준 (Starlight (Astra) Creative에만 적용됩니다)." + }, + "upscaler_enabled": { + "name": "upscaler_enabled" + }, + "upscaler_model": { + "name": "upscaler_model" + }, + "upscaler_resolution": { + "name": "upscaler_resolution" + }, + "video": { + "name": "video" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "TorchCompileModel": { "display_name": "모델 토치 컴파일", "inputs": { @@ -11577,6 +13838,10 @@ "name": "배치 크기", "tooltip": "학습에 사용할 배치 크기입니다." }, + "bucket_mode": { + "name": "bucket_mode", + "tooltip": "해상도 버킷 모드를 활성화합니다. 활성화 시, ResolutionBucket 노드에서 미리 버킷 처리된 latent를 기대합니다." + }, "control_after_generate": { "name": "생성 후 제어" }, @@ -11637,20 +13902,20 @@ "tooltip": "훈련에 사용할 데이터 타입입니다." } }, - "outputs": { - "0": { - "name": "LoRA가 적용된 모델" + "outputs": [ + { + "tooltip": "LoRA가 적용된 모델" }, - "1": { - "name": "LoRA" + { + "tooltip": "LoRA 가중치" }, - "2": { - "name": "손실" + { + "tooltip": "손실 기록" }, - "3": { - "name": "단계" + { + "tooltip": "총 학습 스텝" } - } + ] }, "TrimAudioDuration": { "description": "오디오 텐서를 선택한 시간 범위로 자릅니다.", @@ -11667,6 +13932,11 @@ "name": "시작 인덱스", "tooltip": "시작 시간(초), 음수일 경우 끝에서부터 계산 (소수 단위 지원)." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "TrimVideoLatent": { @@ -11708,23 +13978,62 @@ "TripoConversionNode": { "display_name": "Tripo: 모델 변환", "inputs": { + "animate_in_place": { + "name": "animate_in_place" + }, + "bake": { + "name": "bake" + }, + "export_orientation": { + "name": "export_orientation" + }, + "export_vertex_colors": { + "name": "export_vertex_colors" + }, "face_limit": { "name": "면 제한" }, + "fbx_preset": { + "name": "fbx_preset" + }, + "flatten_bottom": { + "name": "flatten_bottom" + }, + "flatten_bottom_threshold": { + "name": "flatten_bottom_threshold" + }, + "force_symmetry": { + "name": "force_symmetry" + }, "format": { "name": "형식" }, "original_model_task_id": { "name": "원본 모델 작업 ID" }, + "pack_uv": { + "name": "pack_uv" + }, + "part_names": { + "name": "part_names" + }, + "pivot_to_center_bottom": { + "name": "pivot_to_center_bottom" + }, "quad": { "name": "쿼드" }, + "scale_factor": { + "name": "scale_factor" + }, "texture_format": { "name": "텍스처 형식" }, "texture_size": { "name": "텍스처 크기" + }, + "with_animation": { + "name": "with_animation" } } }, @@ -11734,6 +14043,9 @@ "face_limit": { "name": "얼굴 제한" }, + "geometry_quality": { + "name": "geometry_quality" + }, "image": { "name": "이미지" }, @@ -11786,6 +14098,9 @@ "face_limit": { "name": "얼굴 제한" }, + "geometry_quality": { + "name": "geometry_quality" + }, "image": { "name": "이미지" }, @@ -11903,6 +14218,9 @@ "face_limit": { "name": "얼굴 제한" }, + "geometry_quality": { + "name": "geometry_quality" + }, "image_seed": { "name": "이미지 시드" }, @@ -11981,6 +14299,25 @@ } } }, + "TruncateText": { + "display_name": "텍스트 자르기", + "inputs": { + "max_length": { + "name": "max_length", + "tooltip": "최대 텍스트 길이." + }, + "texts": { + "name": "texts", + "tooltip": "처리할 텍스트." + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "처리된 텍스트" + } + } + }, "UNETLoader": { "display_name": "확산 모델 로드", "inputs": { @@ -12122,6 +14459,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEDecodeHunyuan3D": { @@ -12139,6 +14481,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEDecodeTiled": { @@ -12186,6 +14533,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEEncodeForInpaint": { @@ -12264,6 +14616,63 @@ "steps": { "name": "스텝 수" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Veo3FirstLastFrameNode": { + "description": "프롬프트와 첫 프레임, 마지막 프레임을 사용하여 비디오를 생성합니다.", + "display_name": "Google Veo 3 첫 프레임-마지막 프레임에서 비디오 생성", + "inputs": { + "aspect_ratio": { + "name": "종횡비", + "tooltip": "출력 비디오의 종횡비" + }, + "control_after_generate": { + "name": "생성 후 제어" + }, + "duration": { + "name": "길이", + "tooltip": "출력 비디오의 길이(초)" + }, + "first_frame": { + "name": "첫 프레임", + "tooltip": "시작 프레임" + }, + "generate_audio": { + "name": "오디오 생성", + "tooltip": "비디오용 오디오를 생성합니다." + }, + "last_frame": { + "name": "마지막 프레임", + "tooltip": "종료 프레임" + }, + "model": { + "name": "모델" + }, + "negative_prompt": { + "name": "네거티브 프롬프트", + "tooltip": "비디오에서 피하고 싶은 내용을 안내하는 네거티브 텍스트 프롬프트" + }, + "prompt": { + "name": "프롬프트", + "tooltip": "비디오에 대한 텍스트 설명" + }, + "resolution": { + "name": "해상도" + }, + "seed": { + "name": "시드", + "tooltip": "비디오 생성용 시드" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Veo3VideoGenerationNode": { @@ -12392,6 +14801,166 @@ } } }, + "Vidu2ImageToVideoNode": { + "description": "이미지와 선택적 프롬프트로부터 비디오를 생성합니다.", + "display_name": "Vidu2 이미지-비디오 생성", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "duration": { + "name": "duration" + }, + "image": { + "name": "image", + "tooltip": "생성된 비디오의 시작 프레임으로 사용할 이미지입니다." + }, + "model": { + "name": "model" + }, + "movement_amplitude": { + "name": "movement_amplitude", + "tooltip": "프레임 내 객체의 움직임 진폭입니다." + }, + "prompt": { + "name": "prompt", + "tooltip": "비디오 생성을 위한 선택적 텍스트 프롬프트 (최대 2000자)." + }, + "resolution": { + "name": "resolution" + }, + "seed": { + "name": "seed" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2ReferenceVideoNode": { + "description": "여러 참조 이미지와 프롬프트로부터 비디오를 생성합니다.", + "display_name": "Vidu2 참조-비디오 생성", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "audio": { + "name": "audio", + "tooltip": "활성화 시, 프롬프트를 기반으로 생성된 음성과 배경 음악이 비디오에 포함됩니다." + }, + "control_after_generate": { + "name": "control after generate" + }, + "duration": { + "name": "duration" + }, + "model": { + "name": "model" + }, + "movement_amplitude": { + "name": "movement_amplitude", + "tooltip": "프레임 내 객체의 움직임 진폭입니다." + }, + "prompt": { + "name": "prompt", + "tooltip": "활성화 시, 프롬프트를 기반으로 생성된 음성과 배경 음악이 비디오에 포함됩니다." + }, + "resolution": { + "name": "resolution" + }, + "seed": { + "name": "seed" + }, + "subjects": { + "name": "subjects", + "tooltip": "각 subject마다 최대 3개의 참조 이미지를 제공합니다 (전체 subject 합계 7장). 프롬프트에서 @subject{subject_id}로 참조하세요." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2StartEndToVideoNode": { + "description": "시작 프레임, 종료 프레임, 프롬프트로부터 비디오를 생성합니다.", + "display_name": "Vidu2 시작/종료 프레임-투-비디오 생성", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "duration": { + "name": "duration" + }, + "end_frame": { + "name": "end_frame" + }, + "first_frame": { + "name": "first_frame" + }, + "model": { + "name": "model" + }, + "movement_amplitude": { + "name": "movement_amplitude", + "tooltip": "프레임 내 객체의 움직임 진폭입니다." + }, + "prompt": { + "name": "prompt", + "tooltip": "프롬프트 설명 (최대 2000자)." + }, + "resolution": { + "name": "resolution" + }, + "seed": { + "name": "seed" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2TextToVideoNode": { + "description": "텍스트 프롬프트로부터 비디오를 생성합니다.", + "display_name": "Vidu2 텍스트-투-비디오 생성", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "background_music": { + "name": "background_music", + "tooltip": "생성된 비디오에 배경 음악을 추가할지 여부입니다." + }, + "control_after_generate": { + "name": "control after generate" + }, + "duration": { + "name": "duration" + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "비디오 생성을 위한 텍스트 설명 (최대 2000자)." + }, + "resolution": { + "name": "resolution" + }, + "seed": { + "name": "seed" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "ViduImageToVideoNode": { "description": "이미지와 선택적 프롬프트로부터 비디오 생성", "display_name": "Vidu 이미지 비디오 생성", @@ -12580,6 +15149,11 @@ "voxel": { "name": "복셀" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VoxelToMeshBasic": { @@ -12591,6 +15165,11 @@ "voxel": { "name": "복셀" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Wan22FunControlToVideo": { @@ -12870,6 +15449,10 @@ "name": "컨텍스트 스트라이드", "tooltip": "컨텍스트 창의 스트라이드입니다. 균일 스케줄에만 적용됩니다." }, + "freenoise": { + "name": "freenoise", + "tooltip": "FreeNoise 노이즈 셔플을 적용할지 여부, 윈도우 블렌딩을 향상시킵니다." + }, "fuse_method": { "name": "퓨즈 방법", "tooltip": "컨텍스트 창을 융합하는 데 사용할 방법입니다." @@ -13210,6 +15793,10 @@ "name": "시드", "tooltip": "생성에 사용할 시드 값입니다." }, + "shot_type": { + "name": "샷 타입", + "tooltip": "생성된 비디오의 샷 타입을 지정합니다. 즉, 비디오가 단일 연속 샷인지, 컷이 있는 여러 샷인지 설정합니다. 이 파라미터는 prompt_extend가 True일 때만 적용됩니다." + }, "watermark": { "name": "워터마크", "tooltip": "결과물에 \"AI 생성\" 워터마크를 추가할지 여부입니다." @@ -13221,6 +15808,196 @@ } } }, + "WanInfiniteTalkToVideo": { + "display_name": "WanInfiniteTalkToVideo", + "inputs": { + "audio_encoder_output_1": { + "name": "audio_encoder_output_1" + }, + "audio_scale": { + "name": "audio_scale" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "height": { + "name": "height" + }, + "length": { + "name": "length" + }, + "mode": { + "name": "mode" + }, + "model": { + "name": "model" + }, + "model_patch": { + "name": "model_patch" + }, + "motion_frame_count": { + "name": "motion_frame_count", + "tooltip": "이전 프레임 수를 모션 컨텍스트로 사용합니다." + }, + "negative": { + "name": "negative" + }, + "positive": { + "name": "positive" + }, + "previous_frames": { + "name": "previous_frames" + }, + "start_image": { + "name": "start_image" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "width" + } + }, + "outputs": { + "0": { + "name": "model", + "tooltip": null + }, + "1": { + "name": "positive", + "tooltip": null + }, + "2": { + "name": "negative", + "tooltip": null + }, + "3": { + "name": "latent", + "tooltip": null + }, + "4": { + "name": "trim_image", + "tooltip": null + } + } + }, + "WanMoveConcatTrack": { + "display_name": "WanMoveConcatTrack", + "inputs": { + "tracks_1": { + "name": "트랙 1" + }, + "tracks_2": { + "name": "트랙 2" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanMoveTrackToVideo": { + "display_name": "WanMoveTrackToVideo", + "inputs": { + "batch_size": { + "name": "배치 크기" + }, + "clip_vision_output": { + "name": "clip 비전 출력" + }, + "height": { + "name": "높이" + }, + "length": { + "name": "길이" + }, + "negative": { + "name": "negative" + }, + "positive": { + "name": "positive" + }, + "start_image": { + "name": "시작 이미지" + }, + "strength": { + "name": "강도", + "tooltip": "트랙 컨디셔닝의 강도입니다." + }, + "tracks": { + "name": "트랙" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "너비" + } + }, + "outputs": { + "0": { + "name": "positive", + "tooltip": null + }, + "1": { + "name": "negative", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "WanMoveTracksFromCoords": { + "display_name": "WanMoveTracksFromCoords", + "inputs": { + "track_coords": { + "name": "트랙 좌표" + }, + "track_mask": { + "name": "트랙 마스크" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "트랙 길이", + "tooltip": null + } + } + }, + "WanMoveVisualizeTracks": { + "display_name": "WanMoveVisualizeTracks", + "inputs": { + "circle_size": { + "name": "원 크기" + }, + "images": { + "name": "이미지" + }, + "line_resolution": { + "name": "선 해상도" + }, + "line_width": { + "name": "선 두께" + }, + "opacity": { + "name": "불투명도" + }, + "tracks": { + "name": "트랙" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WanPhantomSubjectToVideo": { "display_name": "WanPhantomSubjectToVideo", "inputs": { @@ -13268,6 +16045,51 @@ } } }, + "WanReferenceVideoApi": { + "description": "입력 비디오의 캐릭터와 음성을 프롬프트와 결합하여 캐릭터 일관성을 유지하는 새로운 비디오를 생성합니다.", + "display_name": "Wan Reference to Video", + "inputs": { + "control_after_generate": { + "name": "생성 후 제어" + }, + "duration": { + "name": "길이" + }, + "model": { + "name": "모델" + }, + "negative_prompt": { + "name": "네거티브 프롬프트", + "tooltip": "피해야 할 내용을 설명하는 네거티브 프롬프트입니다." + }, + "prompt": { + "name": "프롬프트", + "tooltip": "요소와 시각적 특징을 설명하는 프롬프트입니다. 영어와 중국어를 지원합니다. 참조 캐릭터를 지칭할 때 `character1`, `character2`와 같은 식별자를 사용하세요." + }, + "reference_videos": { + "name": "참조 비디오" + }, + "seed": { + "name": "시드" + }, + "shot_type": { + "name": "샷 타입", + "tooltip": "생성된 비디오의 샷 타입을 지정합니다. 즉, 비디오가 하나의 연속된 샷인지, 컷이 있는 여러 샷인지 지정합니다." + }, + "size": { + "name": "크기" + }, + "watermark": { + "name": "워터마크", + "tooltip": "결과에 AI 생성 워터마크를 추가할지 여부입니다." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WanSoundImageToVideo": { "display_name": "WanSoundImageToVideo", "inputs": { @@ -13446,6 +16268,10 @@ "name": "시드", "tooltip": "생성에 사용할 시드 값." }, + "shot_type": { + "name": "샷 타입", + "tooltip": "생성된 비디오의 샷 타입을 지정합니다. 즉, 비디오가 하나의 연속된 샷인지, 컷이 있는 여러 샷인지 지정합니다. 이 파라미터는 prompt_extend가 True일 때만 적용됩니다." + }, "size": { "name": "크기" }, @@ -13571,6 +16397,43 @@ } } }, + "WavespeedFlashVSRNode": { + "description": "저해상도 또는 흐릿한 영상의 해상도를 높이고 선명도를 복원하는 빠르고 고품질의 비디오 업스케일러입니다.", + "display_name": "FlashVSR 비디오 업스케일", + "inputs": { + "target_resolution": { + "name": "목표 해상도" + }, + "video": { + "name": "비디오" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WavespeedImageUpscaleNode": { + "description": "이미지 해상도와 품질을 높여 사진을 4K 또는 8K로 업스케일하여 선명하고 디테일한 결과를 제공합니다.", + "display_name": "WaveSpeed 이미지 업스케일", + "inputs": { + "image": { + "name": "image" + }, + "model": { + "name": "model" + }, + "target_resolution": { + "name": "목표 해상도" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WebcamCapture": { "display_name": "웹캠 캡처", "inputs": { @@ -13590,6 +16453,32 @@ } } }, + "ZImageFunControlnet": { + "display_name": "ZImageFunControlnet", + "inputs": { + "image": { + "name": "이미지" + }, + "inpaint_image": { + "name": "인페인트 이미지" + }, + "mask": { + "name": "mask" + }, + "model": { + "name": "모델" + }, + "model_patch": { + "name": "모델 패치" + }, + "strength": { + "name": "강도" + }, + "vae": { + "name": "vae" + } + } + }, "unCLIPCheckpointLoader": { "display_name": "unCLIP 체크포인트 로드", "inputs": { @@ -13614,5 +16503,19 @@ "name": "강도" } } + }, + "wanBlockSwap": { + "description": "NOP", + "display_name": "wanBlockSwap", + "inputs": { + "model": { + "name": "모델" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } } } diff --git a/src/locales/ko/settings.json b/src/locales/ko/settings.json index 130f47ffd..c813290f0 100644 --- a/src/locales/ko/settings.json +++ b/src/locales/ko/settings.json @@ -29,12 +29,26 @@ "name": "캔버스 배경 이미지", "tooltip": "캔버스 배경에 사용할 이미지 URL입니다. 출력 패널에서 이미지를 마우스 오른쪽 버튼으로 클릭한 후 \"배경으로 설정\"을 선택해 사용할 수 있습니다." }, + "Comfy_Canvas_LeftMouseClickBehavior": { + "name": "왼쪽 마우스 클릭 동작", + "options": { + "Panning": "패닝", + "Select": "선택" + } + }, + "Comfy_Canvas_MouseWheelScroll": { + "name": "마우스 휠 스크롤", + "options": { + "Panning": "패닝", + "Zoom in/out": "확대/축소" + } + }, "Comfy_Canvas_NavigationMode": { "name": "캔버스 내비게이션 모드", "options": { + "Custom": "사용자 지정", "Drag Navigation": "드래그 내비게이션", - "Standard (New)": "표준(신규)", - "Custom": "사용자 지정" + "Standard (New)": "표준(신규)" } }, "Comfy_Canvas_SelectionToolbox": { @@ -65,6 +79,17 @@ "Comfy_EnableWorkflowViewRestore": { "name": "워크플로에서 캔버스 위치 및 확대/축소 수준 저장 및 복원" }, + "Comfy_Execution_PreviewMethod": { + "name": "라이브 미리보기 방식", + "options": { + "auto": "auto", + "default": "default", + "latent2rgb": "latent2rgb", + "none": "none", + "taesd": "taesd" + }, + "tooltip": "이미지 생성 중 라이브 미리보기 방식입니다. \"default\"는 서버 CLI 설정을 사용합니다." + }, "Comfy_FloatRoundingPrecision": { "name": "부동 소수점 위젯 반올림 소수 자리 수 [0 = 자동]", "tooltip": "(페이지 새로 고침 필요)" @@ -86,6 +111,10 @@ "None": "없음" } }, + "Comfy_Graph_LiveSelection": { + "name": "실시간 선택", + "tooltip": "활성화하면, 다른 디자인 툴처럼 선택 사각형을 드래그할 때 노드가 실시간으로 선택/해제됩니다." + }, "Comfy_Graph_ZoomSpeed": { "name": "캔버스 확대/축소 속도" }, @@ -152,6 +181,15 @@ "name": "최소 광원 세기", "tooltip": "3D 장면에서 허용되는 최소 광원 세기 값을 설정합니다. 이는 모든 3D 위젯에서 조명을 조정할 때 설정할 수 있는 밝기의 하한을 정의합니다." }, + "Comfy_Load3D_PLYEngine": { + "name": "PLY 엔진", + "options": { + "fastply": "fastply", + "sparkjs": "sparkjs", + "threejs": "threejs" + }, + "tooltip": "PLY 파일을 불러올 엔진을 선택하세요. \"threejs\"는 Three.js의 기본 PLYLoader를 사용합니다(메시 PLY 파일에 적합). \"fastply\"는 ASCII 포인트 클라우드 PLY 파일에 최적화된 로더를 사용합니다. \"sparkjs\"는 3D Gaussian Splatting PLY 파일에 Spark.js를 사용합니다." + }, "Comfy_Load3D_ShowGrid": { "name": "그리드 표시", "tooltip": "기본적으로 그리드를 표시하도록 전환" @@ -167,10 +205,6 @@ "name": "브러시 조정을 지배 축에 고정", "tooltip": "활성화하면 브러시 조정이 이동하는 방향에 따라 크기 또는 경도에만 영향을 미칩니다." }, - "Comfy_MaskEditor_UseNewEditor": { - "name": "새 마스크 편집기 사용", - "tooltip": "새 마스크 편집기 인터페이스로 전환" - }, "Comfy_ModelLibrary_AutoLoadAll": { "name": "모든 모델 폴더 자동 로드", "tooltip": "참이면 모든 폴더가 모델 라이브러리를 열 때 즉시 로드됩니다 (로드하는 동안 지연이 발생할 수 있습니다). 거짓이면 루트 수준 모델 폴더는 클릭할 때만 로드됩니다." @@ -238,6 +272,10 @@ "Comfy_Node_AllowImageSizeDraw": { "name": "이미지 미리보기 아래에 너비 × 높이 표시" }, + "Comfy_Node_AlwaysShowAdvancedWidgets": { + "name": "모든 노드에서 고급 위젯 항상 표시", + "tooltip": "이 옵션을 활성화하면, 모든 노드에서 고급 위젯이 개별적으로 확장하지 않아도 항상 표시됩니다." + }, "Comfy_Node_AutoSnapLinkToSlot": { "name": "링크를 노드 슬롯에 자동 스냅", "tooltip": "링크를 노드 위로 드래그할 때 링크가 노드의 유효한 입력 슬롯에 자동으로 스냅됩니다." @@ -298,6 +336,10 @@ "name": "실행 큐 기록 갯수", "tooltip": "실행 큐 기록에 표시되는 최대 작업 수입니다." }, + "Comfy_Queue_QPOV2": { + "name": "에셋 사이드 패널에서 통합 작업 큐 사용", + "tooltip": "떠다니는 작업 큐 패널을 에셋 사이드 패널에 내장된 동등한 작업 큐로 대체합니다. 이 옵션을 비활성화하면 기존의 떠다니는 패널 레이아웃으로 돌아갈 수 있습니다." + }, "Comfy_Sidebar_Location": { "name": "사이드바 위치", "options": { @@ -312,6 +354,13 @@ "small": "작음" } }, + "Comfy_Sidebar_Style": { + "name": "사이드바 스타일", + "options": { + "connected": "연결됨", + "floating": "플로팅" + } + }, "Comfy_Sidebar_UnifiedWidth": { "name": "통합 사이드바 너비" }, @@ -328,6 +377,14 @@ "Comfy_TreeExplorer_ItemPadding": { "name": "트리 탐색기 항목 패딩" }, + "Comfy_UI_TabBarLayout": { + "name": "탭 바 레이아웃", + "options": { + "Default": "기본값", + "Integrated": "통합" + }, + "tooltip": "탭 바의 레이아웃을 제어합니다. \"통합\"을 선택하면 도움말과 사용자 컨트롤이 탭 바 영역으로 이동합니다." + }, "Comfy_UseNewMenu": { "name": "새 메뉴 사용", "options": { @@ -339,6 +396,14 @@ "Comfy_Validation_Workflows": { "name": "워크플로 유효성 검사" }, + "Comfy_VueNodes_AutoScaleLayout": { + "name": "자동 스케일 레이아웃 (Vue 노드)", + "tooltip": "Vue 렌더링으로 전환 시 노드 위치를 자동으로 조정하여 겹침 방지" + }, + "Comfy_VueNodes_Enabled": { + "name": "모던 노드 디자인 (Vue 노드)", + "tooltip": "모던: 향상된 상호작용, 기본 브라우저 기능, 업데이트된 시각적 디자인을 갖춘 DOM 기반 렌더링. 클래식: 전통적인 캔버스 렌더링." + }, "Comfy_WidgetControlMode": { "name": "위젯 제어 모드", "options": { @@ -376,6 +441,9 @@ "Comfy_Workflow_SortNodeIdOnSave": { "name": "워크플로 저장 시 노드 ID 정렬" }, + "Comfy_Workflow_WarnBlueprintOverwrite": { + "name": "기존 서브그래프 블루프린트 덮어쓰기 전 확인 요청" + }, "Comfy_Workflow_WorkflowTabsPosition": { "name": "열린 워크플로 위치", "options": { @@ -407,37 +475,5 @@ }, "pysssss_SnapToGrid": { "name": "항상 그리드에 스냅" - }, - "Comfy_Canvas_LeftMouseClickBehavior": { - "name": "왼쪽 마우스 클릭 동작", - "options": { - "Panning": "패닝", - "Select": "선택" - } - }, - "Comfy_Canvas_MouseWheelScroll": { - "name": "마우스 휠 스크롤", - "options": { - "Panning": "패닝", - "Zoom in/out": "확대/축소" - } - }, - "Comfy_Sidebar_Style": { - "name": "사이드바 스타일", - "options": { - "floating": "플로팅", - "connected": "연결됨" - } - }, - "Comfy_VueNodes_AutoScaleLayout": { - "name": "자동 스케일 레이아웃 (Vue 노드)", - "tooltip": "Vue 렌더링으로 전환 시 노드 위치를 자동으로 조정하여 겹침 방지" - }, - "Comfy_VueNodes_Enabled": { - "name": "모던 노드 디자인 (Vue 노드)", - "tooltip": "모던: 향상된 상호작용, 기본 브라우저 기능, 업데이트된 시각적 디자인을 갖춘 DOM 기반 렌더링. 클래식: 전통적인 캔버스 렌더링." - }, - "Comfy_Workflow_WarnBlueprintOverwrite": { - "name": "기존 서브그래프 블루프린트 덮어쓰기 전 확인 요청" } } diff --git a/src/locales/pt-BR/commands.json b/src/locales/pt-BR/commands.json new file mode 100644 index 000000000..87ab65656 --- /dev/null +++ b/src/locales/pt-BR/commands.json @@ -0,0 +1,348 @@ +{ + "Comfy-Desktop_CheckForUpdates": { + "label": "Verificar atualizações" + }, + "Comfy-Desktop_Folders_OpenCustomNodesFolder": { + "label": "Abrir pasta de nós personalizados" + }, + "Comfy-Desktop_Folders_OpenInputsFolder": { + "label": "Abrir pasta de entradas" + }, + "Comfy-Desktop_Folders_OpenLogsFolder": { + "label": "Abrir pasta de logs" + }, + "Comfy-Desktop_Folders_OpenModelConfig": { + "label": "Abrir extra_model_paths.yaml" + }, + "Comfy-Desktop_Folders_OpenModelsFolder": { + "label": "Abrir pasta de modelos" + }, + "Comfy-Desktop_Folders_OpenOutputsFolder": { + "label": "Abrir pasta de saídas" + }, + "Comfy-Desktop_OpenDevTools": { + "label": "Abrir ferramentas de desenvolvedor" + }, + "Comfy-Desktop_OpenUserGuide": { + "label": "Guia do usuário para desktop" + }, + "Comfy-Desktop_Quit": { + "label": "Sair" + }, + "Comfy-Desktop_Reinstall": { + "label": "Reinstalar" + }, + "Comfy-Desktop_Restart": { + "label": "Reiniciar" + }, + "Comfy_3DViewer_Open3DViewer": { + "label": "Abrir visualizador 3D (Beta) para o nó selecionado" + }, + "Comfy_BrowseModelAssets": { + "label": "Experimental: Navegar pelos ativos de modelo" + }, + "Comfy_BrowseTemplates": { + "label": "Navegar por modelos" + }, + "Comfy_Canvas_DeleteSelectedItems": { + "label": "Excluir itens selecionados" + }, + "Comfy_Canvas_FitView": { + "label": "Ajustar visualização aos nós selecionados" + }, + "Comfy_Canvas_Lock": { + "label": "Travar tela" + }, + "Comfy_Canvas_MoveSelectedNodes_Down": { + "label": "Mover nós selecionados para baixo" + }, + "Comfy_Canvas_MoveSelectedNodes_Left": { + "label": "Mover nós selecionados para a esquerda" + }, + "Comfy_Canvas_MoveSelectedNodes_Right": { + "label": "Mover nós selecionados para a direita" + }, + "Comfy_Canvas_MoveSelectedNodes_Up": { + "label": "Mover nós selecionados para cima" + }, + "Comfy_Canvas_ResetView": { + "label": "Redefinir visualização" + }, + "Comfy_Canvas_Resize": { + "label": "Redimensionar nós selecionados" + }, + "Comfy_Canvas_ToggleLinkVisibility": { + "label": "Alternar visibilidade dos links na tela" + }, + "Comfy_Canvas_ToggleLock": { + "label": "Alternar trava da tela" + }, + "Comfy_Canvas_ToggleMinimap": { + "label": "Alternar minimapa da tela" + }, + "Comfy_Canvas_ToggleSelectedNodes_Bypass": { + "label": "Ignorar/Não ignorar nós selecionados" + }, + "Comfy_Canvas_ToggleSelectedNodes_Collapse": { + "label": "Recolher/Expandir nós selecionados" + }, + "Comfy_Canvas_ToggleSelectedNodes_Mute": { + "label": "Silenciar/Ativar som dos nós selecionados" + }, + "Comfy_Canvas_ToggleSelectedNodes_Pin": { + "label": "Fixar/Desafixar nós selecionados" + }, + "Comfy_Canvas_ToggleSelected_Pin": { + "label": "Fixar/Desafixar itens selecionados" + }, + "Comfy_Canvas_Unlock": { + "label": "Destravar tela" + }, + "Comfy_Canvas_ZoomIn": { + "label": "Aproximar" + }, + "Comfy_Canvas_ZoomOut": { + "label": "Afastar" + }, + "Comfy_ClearPendingTasks": { + "label": "Limpar tarefas pendentes" + }, + "Comfy_ClearWorkflow": { + "label": "Limpar fluxo de trabalho" + }, + "Comfy_ContactSupport": { + "label": "Entrar em contato com o suporte" + }, + "Comfy_Dev_ShowModelSelector": { + "label": "Mostrar seletor de modelo (Dev)" + }, + "Comfy_DuplicateWorkflow": { + "label": "Duplicar fluxo de trabalho atual" + }, + "Comfy_ExportWorkflow": { + "label": "Exportar fluxo de trabalho" + }, + "Comfy_ExportWorkflowAPI": { + "label": "Exportar fluxo de trabalho (formato API)" + }, + "Comfy_Graph_ConvertToSubgraph": { + "label": "Converter seleção em subgrafo" + }, + "Comfy_Graph_EditSubgraphWidgets": { + "label": "Editar widgets do subgrafo" + }, + "Comfy_Graph_ExitSubgraph": { + "label": "Sair do subgrafo" + }, + "Comfy_Graph_FitGroupToContents": { + "label": "Ajustar grupo ao conteúdo" + }, + "Comfy_Graph_GroupSelectedNodes": { + "label": "Agrupar nós selecionados" + }, + "Comfy_Graph_ToggleWidgetPromotion": { + "label": "Alternar promoção do widget sob o cursor" + }, + "Comfy_Graph_UnpackSubgraph": { + "label": "Desempacotar o Subgrafo selecionado" + }, + "Comfy_GroupNode_ConvertSelectedNodesToGroupNode": { + "label": "Converter nós selecionados em nó de grupo" + }, + "Comfy_GroupNode_ManageGroupNodes": { + "label": "Gerenciar nós de grupo" + }, + "Comfy_GroupNode_UngroupSelectedGroupNodes": { + "label": "Desagrupar nós de grupo selecionados" + }, + "Comfy_Help_AboutComfyUI": { + "label": "Abrir Sobre o ComfyUI" + }, + "Comfy_Help_OpenComfyOrgDiscord": { + "label": "Abrir Comfy-Org Discord" + }, + "Comfy_Help_OpenComfyUIDocs": { + "label": "Abrir Documentação do ComfyUI" + }, + "Comfy_Help_OpenComfyUIForum": { + "label": "Abrir Fórum do ComfyUI" + }, + "Comfy_Help_OpenComfyUIIssues": { + "label": "Abrir Issues do ComfyUI" + }, + "Comfy_Interrupt": { + "label": "Interromper" + }, + "Comfy_LoadDefaultWorkflow": { + "label": "Carregar Fluxo de Trabalho Padrão" + }, + "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { + "label": "Gerenciador de Nós Personalizados" + }, + "Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": { + "label": "Nós Personalizados (Legado)" + }, + "Comfy_Manager_ShowLegacyManagerMenu": { + "label": "Menu do Gerenciador (Legado)" + }, + "Comfy_Manager_ShowMissingPacks": { + "label": "Instalar Nós Personalizados Ausentes" + }, + "Comfy_Manager_ShowUpdateAvailablePacks": { + "label": "Verificar Atualizações de Nós Personalizados" + }, + "Comfy_MaskEditor_BrushSize_Decrease": { + "label": "Diminuir tamanho do pincel no Editor de Máscara" + }, + "Comfy_MaskEditor_BrushSize_Increase": { + "label": "Aumentar tamanho do pincel no Editor de Máscara" + }, + "Comfy_MaskEditor_ColorPicker": { + "label": "Abrir Seletor de Cores no Editor de Máscara" + }, + "Comfy_MaskEditor_Mirror_Horizontal": { + "label": "Espelhar horizontalmente no MaskEditor" + }, + "Comfy_MaskEditor_Mirror_Vertical": { + "label": "Espelhar verticalmente no MaskEditor" + }, + "Comfy_MaskEditor_OpenMaskEditor": { + "label": "Abrir Editor de Máscara para o nó selecionado" + }, + "Comfy_MaskEditor_Rotate_Left": { + "label": "Girar para a esquerda no MaskEditor" + }, + "Comfy_MaskEditor_Rotate_Right": { + "label": "Girar para a direita no MaskEditor" + }, + "Comfy_Memory_UnloadModels": { + "label": "Descarregar Modelos" + }, + "Comfy_Memory_UnloadModelsAndExecutionCache": { + "label": "Descarregar Modelos e Cache de Execução" + }, + "Comfy_NewBlankWorkflow": { + "label": "Novo Fluxo de Trabalho em Branco" + }, + "Comfy_OpenClipspace": { + "label": "Clipspace" + }, + "Comfy_OpenManagerDialog": { + "label": "Gerenciador" + }, + "Comfy_OpenWorkflow": { + "label": "Abrir Fluxo de Trabalho" + }, + "Comfy_PublishSubgraph": { + "label": "Publicar Subgrafo" + }, + "Comfy_QueuePrompt": { + "label": "Adicionar Prompt à Fila" + }, + "Comfy_QueuePromptFront": { + "label": "Adicionar Prompt à Fila (Início)" + }, + "Comfy_QueueSelectedOutputNodes": { + "label": "Adicionar Nós de Saída Selecionados à Fila" + }, + "Comfy_Queue_ToggleOverlay": { + "label": "Alternar Histórico de Tarefas" + }, + "Comfy_Redo": { + "label": "Refazer" + }, + "Comfy_RefreshNodeDefinitions": { + "label": "Atualizar Definições de Nós" + }, + "Comfy_RenameWorkflow": { + "label": "Renomear fluxo de trabalho" + }, + "Comfy_SaveWorkflow": { + "label": "Salvar Fluxo de Trabalho" + }, + "Comfy_SaveWorkflowAs": { + "label": "Salvar Fluxo de Trabalho Como" + }, + "Comfy_ShowSettingsDialog": { + "label": "Mostrar Diálogo de Configurações" + }, + "Comfy_ToggleAssetAPI": { + "label": "Experimental: Ativar AssetAPI" + }, + "Comfy_ToggleCanvasInfo": { + "label": "Desempenho da Tela" + }, + "Comfy_ToggleHelpCenter": { + "label": "Central de Ajuda" + }, + "Comfy_ToggleLinear": { + "label": "alternar modo linear" + }, + "Comfy_ToggleQPOV2": { + "label": "Alternar Painel de Fila V2" + }, + "Comfy_ToggleTheme": { + "label": "Alternar Tema (Escuro/Claro)" + }, + "Comfy_Undo": { + "label": "Desfazer" + }, + "Comfy_User_OpenSignInDialog": { + "label": "Abrir diálogo de login" + }, + "Comfy_User_SignOut": { + "label": "Sair" + }, + "Experimental_ToggleVueNodes": { + "label": "Experimental: Ativar Nodes 2.0" + }, + "Workspace_CloseWorkflow": { + "label": "Fechar fluxo de trabalho atual" + }, + "Workspace_NextOpenedWorkflow": { + "label": "Próximo fluxo de trabalho aberto" + }, + "Workspace_PreviousOpenedWorkflow": { + "label": "Fluxo de trabalho aberto anterior" + }, + "Workspace_SearchBox_Toggle": { + "label": "Alternar caixa de pesquisa" + }, + "Workspace_ToggleBottomPanel": { + "label": "Alternar painel inferior" + }, + "Workspace_ToggleBottomPanelTab_command-terminal": { + "label": "Alternar painel inferior do terminal" + }, + "Workspace_ToggleBottomPanelTab_logs-terminal": { + "label": "Alternar painel inferior de logs" + }, + "Workspace_ToggleBottomPanelTab_shortcuts-essentials": { + "label": "Alternar painel inferior essencial" + }, + "Workspace_ToggleBottomPanelTab_shortcuts-view-controls": { + "label": "Alternar painel inferior de controles de visualização" + }, + "Workspace_ToggleBottomPanel_Shortcuts": { + "label": "Mostrar diálogo de atalhos" + }, + "Workspace_ToggleFocusMode": { + "label": "Alternar modo de foco" + }, + "Workspace_ToggleSidebarTab_assets": { + "label": "Alternar barra lateral de assets", + "tooltip": "Assets" + }, + "Workspace_ToggleSidebarTab_model-library": { + "label": "Alternar barra lateral da biblioteca de modelos", + "tooltip": "Biblioteca de modelos" + }, + "Workspace_ToggleSidebarTab_node-library": { + "label": "Alternar barra lateral da biblioteca de nodes", + "tooltip": "Biblioteca de nodes" + }, + "Workspace_ToggleSidebarTab_workflows": { + "label": "Alternar barra lateral de fluxos de trabalho", + "tooltip": "Fluxos de trabalho" + } +} diff --git a/src/locales/pt-BR/main.json b/src/locales/pt-BR/main.json new file mode 100644 index 000000000..bbe390313 --- /dev/null +++ b/src/locales/pt-BR/main.json @@ -0,0 +1,2810 @@ +{ + "actionbar": { + "dockToTop": "Fixar no topo", + "feedback": "Feedback", + "feedbackTooltip": "Feedback" + }, + "apiNodesCostBreakdown": { + "costPerRun": "Custo por execução", + "title": "Nó(s) de API", + "totalCost": "Custo total" + }, + "apiNodesSignInDialog": { + "message": "Este fluxo de trabalho contém nós de API, que exigem que você esteja conectado à sua conta para executar.", + "title": "Login necessário para usar nós de API" + }, + "assetBrowser": { + "allCategory": "Todas as {category}", + "allModels": "Todos os Modelos", + "ariaLabel": { + "assetCard": "{name} - ativo do tipo {type}", + "loadingAsset": "Carregando ativo" + }, + "assetCollection": "Coleção de ativos", + "assets": "Ativos", + "baseModels": "Modelos base", + "browseAssets": "Explorar Ativos", + "byType": "Por tipo", + "checkpoints": "Checkpoints", + "civitaiLinkExample": "{example} {link}", + "civitaiLinkExampleStrong": "Exemplo:", + "civitaiLinkExampleUrl": "https://civitai.com/models/10706/luisap-z-image-and-qwen-pixel-art-refiner?modelVersionId=2225295", + "civitaiLinkLabel": "Link de {download} do modelo Civitai", + "civitaiLinkLabelDownload": "download", + "civitaiLinkPlaceholder": "Cole o link aqui", + "confirmModelDetails": "Confirmar detalhes do modelo", + "connectionError": "Por favor, verifique sua conexão e tente novamente", + "deletion": { + "body": "Este modelo será removido permanentemente da sua biblioteca.", + "complete": "{assetName} foi excluído.", + "failed": "{assetName} não pôde ser excluído.", + "header": "Excluir este modelo?", + "inProgress": "Excluindo {assetName}..." + }, + "download": { + "complete": "Download concluído", + "failed": "Falha no download", + "inProgress": "Baixando {assetName}..." + }, + "emptyImported": { + "canImport": "Nenhum modelo importado ainda. Clique em \"Importar Modelo\" para adicionar o seu.", + "restricted": "Modelos pessoais estão disponíveis apenas no nível Creator ou superior." + }, + "errorFileTooLarge": "O arquivo excede o limite máximo de tamanho permitido", + "errorFormatNotAllowed": "Apenas o formato SafeTensor é permitido", + "errorModelTypeNotSupported": "Este tipo de modelo não é suportado", + "errorUnknown": "Ocorreu um erro inesperado", + "errorUnsafePickleScan": "O CivitAI detectou código potencialmente inseguro neste arquivo", + "errorUnsafeVirusScan": "O CivitAI detectou malware ou conteúdo suspeito neste arquivo", + "errorUploadFailed": "Falha ao importar o ativo. Por favor, tente novamente.", + "failedToCreateNode": "Falha ao criar o nó. Por favor, tente novamente ou verifique o console para mais detalhes.", + "fileFormats": "Formatos de arquivo", + "fileName": "Nome do arquivo", + "fileSize": "Tamanho do arquivo", + "filterBy": "Filtrar por", + "findInLibrary": "Encontre na seção {type} da biblioteca de modelos.", + "finish": "Concluir", + "genericLinkPlaceholder": "Cole o link aqui", + "importAnother": "Importar outro", + "imported": "Importado", + "jobId": "ID do trabalho", + "loadingModels": "Carregando {type}...", + "maxFileSize": "Tamanho máximo do arquivo: {size}", + "maxFileSizeValue": "1 GB", + "media": { + "audioPlaceholder": "Áudio", + "threeDModelPlaceholder": "Modelo 3D" + }, + "modelAssociatedWithLink": "O modelo associado ao link fornecido:", + "modelInfo": { + "addBaseModel": "Adicionar modelo base...", + "addTag": "Adicionar tag...", + "additionalTags": "Tags Adicionais", + "baseModelUnknown": "Modelo base desconhecido", + "basicInfo": "Informações Básicas", + "compatibleBaseModels": "Modelos Base Compatíveis", + "description": "Descrição", + "descriptionNotSet": "Nenhuma descrição definida", + "descriptionPlaceholder": "Adicione uma descrição para este modelo...", + "displayName": "Nome de Exibição", + "editDisplayName": "Editar nome de exibição", + "fileName": "Nome do Arquivo", + "modelDescription": "Descrição do Modelo", + "modelTagging": "Tagueamento do Modelo", + "modelType": "Tipo de Modelo", + "noAdditionalTags": "Sem tags adicionais", + "selectModelPrompt": "Selecione um modelo para ver suas informações", + "selectModelType": "Selecione o tipo de modelo...", + "source": "Fonte", + "title": "Informações do Modelo", + "triggerPhrases": "Frases de Ativação", + "viewOnSource": "Ver em {source}" + }, + "modelName": "Nome do modelo", + "modelNamePlaceholder": "Digite um nome para este modelo", + "modelTypeSelectorLabel": "Qual o tipo deste modelo?", + "modelTypeSelectorPlaceholder": "Selecione o tipo de modelo", + "modelUploaded": "Modelo importado com sucesso.", + "noAssetsFound": "Nenhum ativo encontrado", + "noModelsInFolder": "Nenhum {type} disponível nesta pasta", + "noValidSourceDetected": "Nenhuma fonte de importação válida detectada", + "notSureLeaveAsIs": "Não tem certeza? Deixe como está", + "onlyCivitaiUrlsSupported": "Apenas URLs do Civitai são suportadas", + "ownership": "Propriedade", + "ownershipAll": "Todos", + "ownershipMyModels": "Meus modelos", + "ownershipPublicModels": "Modelos públicos", + "processingModel": "Download iniciado", + "processingModelDescription": "Você pode fechar este diálogo. O download continuará em segundo plano.", + "providerCivitai": "Civitai", + "providerHuggingFace": "Hugging Face", + "rename": { + "failed": "Não foi possível renomear o ativo." + }, + "selectFrameworks": "Selecione Frameworks", + "selectModelType": "Selecione o tipo de modelo", + "selectProjects": "Selecione Projetos", + "sortAZ": "A-Z", + "sortBy": "Ordenar por", + "sortPopular": "Popular", + "sortRecent": "Recentes", + "sortZA": "Z-A", + "sortingType": "Tipo de ordenação", + "tags": "Tags", + "tagsHelp": "Separe as tags com vírgulas", + "tagsPlaceholder": "ex: modelos, checkpoint", + "tryAdjustingFilters": "Tente ajustar sua busca ou filtros", + "unknown": "Desconhecido", + "unsupportedUrlSource": "Apenas URLs de {sources} são suportadas", + "upgradeFeatureDescription": "Este recurso está disponível apenas nos planos Creator ou Pro.", + "upgradeToUnlockFeature": "Faça upgrade para desbloquear este recurso", + "upload": "Importar", + "uploadFailed": "Falha na importação", + "uploadModel": "Importar", + "uploadModelDescription1": "Cole um link de download de modelo do Civitai para adicioná-lo à sua biblioteca.", + "uploadModelDescription1Generic": "Cole um link de download de modelo para adicioná-lo à sua biblioteca.", + "uploadModelDescription2": "Apenas links de {link} são suportados no momento", + "uploadModelDescription2Generic": "Apenas URLs dos seguintes provedores são suportadas:", + "uploadModelDescription2Link": "https://civitai.com/models", + "uploadModelDescription3": "Tamanho máximo do arquivo: {size}", + "uploadModelFailedToRetrieveMetadata": "Falha ao recuperar os metadados. Por favor, verifique o link e tente novamente.", + "uploadModelFromCivitai": "Importar um modelo do Civitai", + "uploadModelGeneric": "Importar um modelo", + "uploadModelHelpFooterText": "Precisa de ajuda para encontrar os URLs? Clique em um provedor abaixo para ver um vídeo tutorial.", + "uploadModelHelpVideo": "Vídeo de Ajuda para Importação de Modelo", + "uploadModelHowDoIFindThis": "Como encontro isso?", + "uploadSuccess": "Modelo importado com sucesso!", + "uploadingModel": "Importando modelo..." + }, + "auth": { + "apiKey": { + "cleared": "Chave de API removida", + "clearedDetail": "Sua chave de API foi removida com sucesso", + "description": "Use sua chave de API Comfy para habilitar os Nós de API", + "error": "Chave de API inválida", + "generateKey": "Obtenha uma aqui", + "helpText": "Precisa de uma chave de API?", + "invalid": "Chave de API inválida", + "invalidDetail": "Por favor, insira uma chave de API válida", + "label": "Chave de API", + "placeholder": "Digite sua chave de API", + "storageFailed": "Falha ao armazenar a chave de API", + "storageFailedDetail": "Por favor, tente novamente.", + "stored": "Chave de API armazenada", + "storedDetail": "Sua chave de API foi armazenada com sucesso", + "title": "Chave de API", + "whitelistInfo": "Sobre sites não permitidos" + }, + "deleteAccount": { + "cancel": "Cancelar", + "confirm": "Excluir conta", + "confirmMessage": "Tem certeza de que deseja excluir sua conta? Esta ação não pode ser desfeita e removerá permanentemente todos os seus dados.", + "confirmTitle": "Excluir conta", + "deleteAccount": "Excluir conta", + "success": "Conta excluída", + "successDetail": "Sua conta foi excluída com sucesso." + }, + "errors": { + "auth/cancelled-popup-request": "Login cancelado. Por favor, tente novamente.", + "auth/email-already-in-use": "Já existe uma conta com este e-mail. Tente fazer login.", + "auth/invalid-credential": "Credenciais de login inválidas. Verifique seu e-mail e senha.", + "auth/invalid-email": "Por favor, insira um endereço de e-mail válido.", + "auth/network-request-failed": "Erro de rede. Verifique sua conexão e tente novamente.", + "auth/operation-not-allowed": "Este método de login não é suportado no momento.", + "auth/popup-closed-by-user": "Login cancelado. Por favor, tente novamente.", + "auth/too-many-requests": "Muitas tentativas de login. Aguarde um momento e tente novamente.", + "auth/user-disabled": "Esta conta foi desativada. Por favor, entre em contato com o suporte.", + "auth/user-not-found": "Nenhuma conta encontrada com este e-mail. Gostaria de criar uma nova conta?", + "auth/weak-password": "A senha é muito fraca. Use uma senha mais forte com pelo menos 6 caracteres.", + "auth/wrong-password": "A senha que você digitou está incorreta. Por favor, tente novamente." + }, + "login": { + "andText": "e", + "backToLogin": "Voltar para login", + "confirmPasswordLabel": "Confirmar senha", + "confirmPasswordPlaceholder": "Digite a mesma senha novamente", + "didntReceiveEmail": "Não recebeu o e-mail? Entre em contato conosco em", + "emailLabel": "E-mail", + "emailPlaceholder": "Digite seu e-mail", + "failed": "Falha no login", + "forgotPassword": "Esqueceu a senha?", + "forgotPasswordError": "Falha ao enviar e-mail de redefinição de senha", + "insecureContextWarning": "Esta conexão é insegura (HTTP) - suas credenciais podem ser interceptadas por invasores se você continuar.", + "loginButton": "Entrar", + "loginWithGithub": "Entrar com Github", + "loginWithGoogle": "Entrar com Google", + "newUser": "Novo por aqui?", + "noAssociatedUser": "Não há usuário Comfy associado à chave de API fornecida", + "orContinueWith": "Ou continue com", + "passwordLabel": "Senha", + "passwordPlaceholder": "Digite sua senha", + "passwordResetError": "Falha ao enviar e-mail de redefinição de senha. Por favor, tente novamente.", + "passwordResetInstructions": "Digite seu endereço de e-mail e enviaremos um link para redefinir sua senha.", + "passwordResetSent": "E-mail de redefinição de senha enviado", + "passwordResetSentDetail": "Por favor, verifique seu e-mail para um link de redefinição de senha.", + "privacyLink": "Política de Privacidade", + "questionsContactPrefix": "Dúvidas? Entre em contato conosco em", + "sendResetLink": "Enviar link de redefinição", + "signInOrSignUp": "Entrar / Cadastrar-se", + "signUp": "Cadastrar-se", + "success": "Login realizado com sucesso", + "termsLink": "Termos de Uso", + "termsText": "Ao clicar em \"Próximo\" ou \"Cadastrar-se\", você concorda com nossos", + "title": "Faça login na sua conta", + "useApiKey": "Chave de API Comfy", + "userAvatar": "Avatar do usuário" + }, + "loginButton": { + "tooltipHelp": "Faça login para poder usar os \"Nós de API\"", + "tooltipLearnMore": "Saiba mais..." + }, + "passwordUpdate": { + "success": "Senha atualizada", + "successDetail": "Sua senha foi atualizada com sucesso" + }, + "reauthRequired": { + "cancel": "Cancelar", + "confirm": "Fazer login novamente", + "message": "Por motivos de segurança, esta ação exige que você faça login novamente. Deseja continuar?", + "title": "Reautenticação necessária" + }, + "signOut": { + "signOut": "Sair", + "success": "Logout realizado com sucesso", + "successDetail": "Você saiu da sua conta." + }, + "signup": { + "alreadyHaveAccount": "Já tem uma conta?", + "emailLabel": "E-mail", + "emailPlaceholder": "Digite seu e-mail", + "passwordLabel": "Senha", + "passwordPlaceholder": "Digite uma nova senha", + "personalDataConsentLabel": "Concordo com o processamento dos meus dados pessoais.", + "regionRestrictionChina": "De acordo com os requisitos regulatórios locais, nossos serviços estão temporariamente indisponíveis para usuários localizados na China.", + "signIn": "Entrar", + "signUpButton": "Cadastrar-se", + "signUpWithGithub": "Cadastrar-se com Github", + "signUpWithGoogle": "Cadastrar-se com Google", + "title": "Criar uma conta" + } + }, + "boundingBox": { + "height": "Altura", + "width": "Largura", + "x": "X", + "y": "Y" + }, + "breadcrumbsMenu": { + "clearWorkflow": "Limpar Fluxo de Trabalho", + "deleteBlueprint": "Excluir Blueprint", + "deleteWorkflow": "Excluir Fluxo de Trabalho", + "duplicate": "Duplicar", + "enterNewName": "Digite um novo nome", + "missingNodesWarning": "O fluxo de trabalho contém nós não suportados (destacados em vermelho)." + }, + "clipboard": { + "errorMessage": "Falha ao copiar para a área de transferência", + "errorNotSupported": "API de área de transferência não suportada no seu navegador", + "successMessage": "Copiado para a área de transferência" + }, + "cloudFooter_needHelp": "Precisa de ajuda?", + "cloudForgotPassword_backToLogin": "Voltar para login", + "cloudForgotPassword_didntReceiveEmail": "Não recebeu o e-mail?", + "cloudForgotPassword_emailLabel": "E-mail", + "cloudForgotPassword_emailPlaceholder": "Digite seu e-mail", + "cloudForgotPassword_emailRequired": "E-mail é obrigatório", + "cloudForgotPassword_instructions": "Digite seu endereço de e-mail e enviaremos um link para redefinir sua senha.", + "cloudForgotPassword_passwordResetError": "Falha ao enviar o e-mail de redefinição de senha", + "cloudForgotPassword_passwordResetSent": "Redefinição de senha enviada", + "cloudForgotPassword_sendResetLink": "Enviar link de redefinição", + "cloudForgotPassword_title": "Esqueceu a senha", + "cloudOnboarding": { + "authTimeout": { + "causes": [ + "Firewall corporativo ou proxy bloqueando serviços de autenticação", + "Restrições de VPN ou rede", + "Extensões do navegador interferindo nas requisições", + "Limitações de rede regionais", + "Tente outro navegador ou rede" + ], + "helpText": "Precisa de ajuda? Contate", + "message": "Estamos com dificuldades para conectar ao ComfyUI Cloud. Isso pode ser devido a uma conexão lenta ou problema temporário no serviço.", + "restart": "Sair e tentar novamente", + "supportLink": "suporte", + "technicalDetails": "Detalhes técnicos", + "title": "Conexão demorando demais", + "troubleshooting": "Causas comuns:" + }, + "checkingStatus": "Verificando o status da sua conta...", + "forgotPassword": { + "backToLogin": "Voltar para login", + "didntReceiveEmail": "Não recebeu o e-mail? Entre em contato conosco em", + "emailLabel": "E-mail", + "emailPlaceholder": "Digite seu e-mail", + "emailRequired": "E-mail é obrigatório", + "instructions": "Digite seu endereço de e-mail e enviaremos um link para redefinir sua senha.", + "passwordResetError": "Falha ao enviar o e-mail de redefinição de senha. Tente novamente.", + "passwordResetSent": "E-mail de redefinição de senha enviado", + "sendResetLink": "Enviar link de redefinição", + "title": "Esqueceu a senha" + }, + "privateBeta": { + "desc": "Faça login para entrar na lista de espera. Vamos avisar quando chegar a sua vez. Já foi notificado? Faça login para começar a usar o Cloud.", + "title": "Cloud está atualmente em beta privado" + }, + "retry": "Tentar novamente", + "retrying": "Tentando novamente...", + "skipToCloudApp": "Pular para o aplicativo na nuvem", + "start": { + "desc": "Nenhuma configuração necessária. Funciona em qualquer dispositivo.", + "download": "Baixar ComfyUI", + "explain": "Gere múltiplos resultados de uma vez. Compartilhe fluxos de trabalho com facilidade.", + "learnAboutButton": "Saiba mais sobre o Cloud", + "title": "comece a criar em segundos", + "wantToRun": "Prefere rodar o ComfyUI localmente?" + }, + "survey": { + "options": { + "familiarity": { + "advanced": "Usuário avançado (fluxos de trabalho personalizados)", + "basics": "Confortável com o básico", + "expert": "Especialista (ajuda outras pessoas)", + "new": "Novo no ComfyUI (nunca usei antes)", + "starting": "Começando agora (seguindo tutoriais)" + }, + "industry": { + "architecture": "Arquitetura", + "education": "Educação", + "film_tv_animation": "Filmes, TV e animação", + "fine_art": "Belas artes e ilustração", + "gaming": "Jogos", + "marketing": "Marketing e publicidade", + "other": "Outro", + "otherPlaceholder": "Por favor, especifique", + "product_design": "Design de produto e gráfico", + "software": "Software e tecnologia" + }, + "making": { + "3d": "Assets 3D", + "audio": "Áudio / música", + "custom_nodes": "Nós e fluxos de trabalho personalizados", + "images": "Imagens", + "video": "Vídeo e animação" + }, + "purpose": { + "client": "Trabalho para clientes (freelancer)", + "community": "Contribuições para a comunidade (nós, fluxos de trabalho, etc.)", + "inhouse": "No meu próprio local de trabalho (interno)", + "personal": "Projetos pessoais / hobby", + "research": "Pesquisa acadêmica" + } + }, + "placeholder": "Espaço reservado para perguntas da pesquisa", + "questions": { + "familiarity": "Qual o seu nível de familiaridade com o ComfyUI?", + "industry": "Qual é o seu setor principal?", + "making": "O que você planeja criar?", + "purpose": "Para que você pretende usar principalmente o ComfyUI?" + }, + "steps": { + "familiarity": "Qual o seu nível de familiaridade com o ComfyUI?", + "industry": "Qual é o seu setor principal?", + "making": "O que você planeja criar?", + "purpose": "Para que você pretende usar principalmente o ComfyUI?" + }, + "title": "Pesquisa da Nuvem" + } + }, + "cloudPrivateBeta_desc": "Faça login para entrar na lista de espera. Vamos avisar você quando chegar a sua vez. Já foi notificado? Faça login para começar a usar o Cloud.", + "cloudPrivateBeta_title": "Cloud está atualmente em beta privado", + "cloudSorryContactSupport_title": "Desculpe, entre em contato com o suporte", + "cloudStart_desc": "Nenhuma configuração necessária. Funciona em qualquer dispositivo.", + "cloudStart_download": "Baixar ComfyUI", + "cloudStart_explain": "Gere múltiplos resultados de uma vez. Compartilhe fluxos de trabalho com facilidade.", + "cloudStart_learnAboutButton": "Saiba mais sobre a Nuvem", + "cloudStart_title": "comece a criar em segundos", + "cloudStart_wantToRun": "Prefere rodar o ComfyUI localmente?", + "cloudSurvey_steps_familiarity": "Qual o seu nível de familiaridade com o ComfyUI?", + "cloudSurvey_steps_industry": "Qual é o seu setor principal?", + "cloudSurvey_steps_making": "O que você planeja criar?", + "cloudSurvey_steps_purpose": "Para que você pretende usar principalmente o ComfyUI?", + "cloudWaitlist_contactLink": "aqui", + "cloudWaitlist_questionsText": "Dúvidas? Entre em contato conosco", + "color": { + "black": "Preto", + "blue": "Azul", + "brown": "Marrom", + "custom": "Personalizado", + "cyan": "Ciano", + "default": "Padrão", + "green": "Verde", + "noColor": "Sem Cor", + "pale_blue": "Azul Claro", + "pink": "Rosa", + "purple": "Roxo", + "red": "Vermelho", + "yellow": "Amarelo" + }, + "commands": { + "clear": "Limpar fluxo de trabalho", + "clipspace": "Abrir Clipspace", + "dark": "Escuro", + "execute": "Executar", + "help": "Ajuda", + "interrupt": "Cancelar execução atual", + "light": "Claro", + "manageExtensions": "Gerenciar extensões", + "queue": "Painel de fila", + "refresh": "Atualizar definições de nó", + "resetView": "Redefinir visualização da tela", + "run": "Executar", + "runWorkflow": "Executar fluxo de trabalho", + "runWorkflowFront": "Executar fluxo de trabalho (Fila na frente)", + "settings": "Configurações", + "theme": "Tema", + "toggleBottomPanel": "Alternar painel inferior" + }, + "contextMenu": { + "Add Group": "Adicionar Grupo", + "Add Group For Selected Nodes": "Adicionar Grupo para Nós Selecionados", + "Add Node": "Adicionar Nó", + "Add Subgraph to Library": "Adicionar Subgrafo à Biblioteca", + "Adjust Size": "Ajustar Tamanho", + "Align Selected To": "Alinhar Selecionados a", + "Bottom": "Inferior", + "Bypass": "Ignorar", + "Clone": "Clonar", + "Collapse": "Recolher", + "Color": "Cor", + "Colors": "Cores", + "Convert to Group Node": "Converter para Nó de Grupo", + "Convert to Subgraph": "Converter para Subgrafo", + "Copy": "Copiar", + "Copy (Clipspace)": "Copiar (Clipspace)", + "Copy Image": "Copiar Imagem", + "Delete": "Excluir", + "Distribute Nodes": "Distribuir Nós", + "Duplicate": "Duplicar", + "Edit Subgraph Widgets": "Editar Widgets do Subgrafo", + "Expand": "Expandir", + "Expand Node": "Expandir Nó", + "Extensions": "Extensões", + "FavoriteWidget": "Favoritar Widget", + "Horizontal": "Horizontal", + "Inputs": "Entradas", + "Left": "Esquerda", + "Manage": "Gerenciar", + "Manage Group Nodes": "Gerenciar Nós de Grupo", + "Minimize Node": "Minimizar Nó", + "Mode": "Modo", + "Node Info": "Informações do Nó", + "Node Templates": "Modelos de Nó", + "Open Image": "Abrir Imagem", + "Open in Mask Editor": "Abrir no Editor de Máscara", + "Outputs": "Saídas", + "Paste": "Colar", + "Pin": "Fixar", + "Properties": "Propriedades", + "Properties Panel": "Painel de Propriedades", + "Remove": "Remover", + "Remove Bypass": "Remover Ignorar", + "Rename": "Renomear", + "RenameWidget": "Renomear Widget", + "Resize": "Redimensionar", + "Right": "Direita", + "Run Branch": "Executar Ramificação", + "Save Image": "Salvar Imagem", + "Save Selected as Template": "Salvar Selecionados como Modelo", + "Search": "Pesquisar", + "Shape": "Forma", + "Shapes": "Formas", + "Title": "Título", + "Top": "Topo", + "UnfavoriteWidget": "Desfavoritar Widget", + "Unpack Subgraph": "Desempacotar Subgrafo", + "Unpin": "Desafixar", + "Vertical": "Vertical", + "deprecated": "obsoleto", + "new": "novo" + }, + "credits": { + "accountInitialized": "Conta inicializada", + "activity": "Atividade", + "added": "Adicionado", + "additionalInfo": "Informações adicionais", + "apiPricing": "Preços da API", + "credits": "Créditos", + "creditsAvailable": "Créditos disponíveis", + "details": "Detalhes", + "eventType": "Tipo de evento", + "faqs": "Perguntas frequentes", + "invoiceHistory": "Histórico de faturas", + "lastUpdated": "Última atualização", + "messageSupport": "Suporte por mensagem", + "model": "Modelo", + "purchaseCredits": "Comprar créditos", + "refreshes": "Atualiza em {date}", + "time": "Hora", + "topUp": { + "addMoreCredits": "Adicionar mais créditos", + "addMoreCreditsToRun": "Adicione mais créditos para executar", + "amountToPayLabel": "Valor a pagar em dólares", + "buy": "Comprar", + "buyCredits": "Continuar para o pagamento", + "buyNow": "Comprar agora", + "contactUs": "Fale conosco", + "creditsDescription": "Os créditos são usados para executar fluxos de trabalho ou nós parceiros.", + "creditsPerDollar": "créditos por dólar", + "creditsToReceiveLabel": "Créditos a receber", + "howManyCredits": "Quantos créditos você gostaria de adicionar?", + "insufficientMessage": "Você não tem créditos suficientes para executar este fluxo de trabalho.", + "insufficientTitle": "Créditos insuficientes", + "insufficientWorkflowMessage": "Você não tem créditos suficientes para executar este fluxo de trabalho.", + "maxAllowed": "Máximo de {credits} créditos.", + "maxAmount": "(Máx. US$ 1.000)", + "maximumAmount": "Máximo de ${amount}.", + "minRequired": "Mínimo de {credits} créditos", + "minimumPurchase": "Mínimo de ${amount} ({credits} créditos)", + "needMore": "Precisa de mais?", + "purchaseError": "Falha na compra", + "purchaseErrorDetail": "Falha ao comprar créditos: {error}", + "quickPurchase": "Compra rápida", + "seeDetails": "Ver detalhes", + "selectAmount": "Selecione o valor", + "templateNote": "*Gerado com o template Wan Fun Control", + "topUp": "Adicionar créditos", + "unknownError": "Ocorreu um erro desconhecido", + "usdAmount": "${amount}", + "videosEstimate": "~{count} vídeos", + "viewPricing": "Ver detalhes de preços", + "youGet": "Créditos", + "youPay": "Valor (USD)" + }, + "unified": { + "message": "Os créditos foram unificados", + "tooltip": "Unificamos os pagamentos no Comfy. Agora tudo funciona com Créditos Comfy:\n- Nós parceiros (anteriormente nós de API)\n- Fluxos de trabalho na nuvem\n\nSeu saldo existente de nós parceiros foi convertido em créditos." + }, + "yourCreditBalance": "Seu saldo de créditos" + }, + "dataTypes": { + "*": "*", + "AUDIO": "ÁUDIO", + "AUDIO_ENCODER": "CODIFICADOR DE ÁUDIO", + "AUDIO_ENCODER_OUTPUT": "SAÍDA DO CODIFICADOR DE ÁUDIO", + "AUDIO_RECORD": "GRAVAÇÃO DE ÁUDIO", + "BOOLEAN": "BOOLEANO", + "CAMERA_CONTROL": "CONTROLE DE CÂMERA", + "CLIP": "clip", + "CLIP_VISION": "clip visão", + "CLIP_VISION_OUTPUT": "saída de clip visão", + "COMBO": "COMBO", + "COMFY_AUTOGROW_V3": "COMFY_AUTOGROW_V3", + "COMFY_DYNAMICCOMBO_V3": "COMFY_DYNAMICCOMBO_V3", + "COMFY_MATCHTYPE_V3": "COMFY_MATCHTYPE_V3", + "CONDITIONING": "CONDICIONAMENTO", + "CONTROL_NET": "controlnet", + "FLOAT": "PONTO FLUTUANTE", + "FLOATS": "PONTOS FLUTUANTES", + "GEMINI_INPUT_FILES": "ARQUIVOS DE ENTRADA GEMINI", + "GLIGEN": "GLIGEN", + "GUIDER": "ORIENTADOR", + "HOOKS": "GANCHOS", + "HOOK_KEYFRAMES": "QUADROS-CHAVE DE GANCHO", + "IMAGE": "IMAGEM", + "IMAGECOMPARE": "COMPARAR_IMAGEM", + "INT": "INTEIRO", + "LATENT": "LATENT", + "LATENT_OPERATION": "OPERAÇÃO LATENT", + "LATENT_UPSCALE_MODEL": "MODELO DE AUMENTO DE LATENT", + "LOAD3D_CAMERA": "CÂMERA LOAD3D", + "LOAD_3D": "CARREGAR 3D", + "LORA_MODEL": "lora MODEL", + "LOSS_MAP": "MAPA DE PERDA", + "LUMA_CONCEPTS": "CONCEITOS LUMA", + "LUMA_REF": "REFERÊNCIA LUMA", + "MASK": "MÁSCARA", + "MESH": "MALHA", + "MESHY_RIGGED_TASK_ID": "MESHY_RIGGED_TASK_ID", + "MESHY_TASK_ID": "MESHY_TASK_ID", + "MODEL": "MODELO", + "MODEL_PATCH": "PATCH DE MODELO", + "MODEL_TASK_ID": "ID DA TAREFA DO MODELO", + "NOISE": "RUÍDO", + "OPENAI_CHAT_CONFIG": "CONFIGURAÇÃO DE CHAT OPENAI", + "OPENAI_INPUT_FILES": "ARQUIVOS DE ENTRADA OPENAI", + "PHOTOMAKER": "photomaker", + "PIXVERSE_TEMPLATE": "MODELO PIXVERSE", + "RECRAFT_COLOR": "COR RECRAFT", + "RECRAFT_CONTROLS": "CONTROLES RECRAFT", + "RECRAFT_V3_STYLE": "ESTILO RECRAFT V3", + "RETARGET_TASK_ID": "ID DA TAREFA DE REDIRECIONAMENTO", + "RIG_TASK_ID": "ID DA TAREFA DE RIG", + "SAMPLER": "AMOSTRADOR", + "SIGMAS": "SIGMAS", + "STRING": "TEXTO", + "STYLE_MODEL": "MODELO DE ESTILO", + "SVG": "SVG", + "TIMESTEPS_RANGE": "INTERVALO DE PASSOS DE TEMPO", + "TRACKS": "FAIXAS", + "UPSCALE_MODEL": "MODELO DE AUMENTO DE RESOLUÇÃO", + "VAE": "vae", + "VIDEO": "VÍDEO", + "VOXEL": "VOXEL", + "WAN_CAMERA_EMBEDDING": "EMBEDDING DE CÂMERA WAN", + "WEBCAM": "WEBCAM" + }, + "desktopDialogs": { + "": { + "buttons": { + "Close": "Fechar" + }, + "message": "ID de diálogo inválido fornecido.", + "title": "Diálogo inválido" + } + }, + "desktopMenu": { + "confirmQuit": "Existem fluxos de trabalho não salvos abertos; quaisquer alterações não salvas serão perdidas. Ignorar isso e sair?", + "confirmReinstall": "Isso irá limpar seu arquivo extra_models_config.yaml,\ne iniciar a instalação novamente.\n\nTem certeza?", + "quit": "Sair", + "reinstall": "Reinstalar" + }, + "desktopStart": { + "initialising": "Inicializando..." + }, + "desktopUpdate": { + "description": "O ComfyUI Desktop está instalando novas dependências. Isso pode levar alguns minutos.", + "errorCheckingUpdate": "Erro ao verificar atualizações", + "errorInstallingUpdate": "Erro ao instalar atualização", + "noUpdateFound": "Nenhuma atualização encontrada", + "terminalDefaultMessage": "Qualquer saída do console da atualização será exibida aqui.", + "title": "Atualizando ComfyUI Desktop", + "updateAvailableMessage": "Uma atualização está disponível. Deseja reiniciar e atualizar agora?", + "updateFoundTitle": "Atualização Encontrada (v{version})" + }, + "downloadGit": { + "gitWebsite": "Baixar git", + "instructions": "Por favor, baixe e instale a versão mais recente para o seu sistema operacional. O botão Baixar git abaixo abrirá a página de downloads do git-scm.com.", + "message": "Não foi possível localizar o git. Uma cópia funcional do git é necessária para o funcionamento normal.", + "skip": "Pular", + "title": "Baixar git", + "warning": "Se você tem certeza de que não precisa do git instalado, ou se houve um engano, você pode clicar em Pular para ignorar esta verificação. Tentar executar o ComfyUI sem uma cópia funcional do git atualmente não é suportado." + }, + "electronFileDownload": { + "cancel": "Cancelar download", + "cancelled": "Cancelado", + "inProgress": "Em andamento", + "pause": "Pausar download", + "paused": "Pausado", + "resume": "Retomar download" + }, + "errorDialog": { + "defaultTitle": "Ocorreu um erro", + "extensionFileHint": "Isso pode ser devido ao seguinte script", + "loadWorkflowTitle": "Carregamento abortado devido a erro ao recarregar os dados do fluxo de trabalho", + "noStackTrace": "Nenhum stacktrace disponível", + "promptExecutionError": "Falha na execução do prompt" + }, + "g": { + "1x": "1x", + "2x": "2x", + "about": "Sobre", + "add": "Adicionar", + "addNodeFilterCondition": "Adicionar condição de filtro de nó", + "all": "Todos", + "amount": "Quantidade", + "apply": "Aplicar", + "architecture": "Arquitetura", + "asset": "{count} ativos | {count} ativo | {count} ativos", + "audioFailedToLoad": "Falha ao carregar áudio", + "audioProgress": "Progresso do áudio", + "author": "Autor", + "back": "Voltar", + "batchRename": "Renomear em lote", + "beta": "BETA", + "bookmark": "Salvar na biblioteca", + "calculatingDimensions": "Calculando dimensões", + "cancel": "Cancelar", + "cancelled": "Cancelado", + "capture": "capturar", + "category": "Categoria", + "chart": "Gráfico", + "chartLowercase": "gráfico", + "choose_file_to_upload": "escolha o arquivo para enviar", + "clear": "Limpar", + "clearAll": "Limpar tudo", + "clearFilters": "Limpar filtros", + "close": "Fechar", + "closeDialog": "Fechar diálogo", + "color": "Cor", + "comfy": "Comfy", + "comfyOrgLogoAlt": "Logo do ComfyOrg", + "comingSoon": "Em breve", + "command": "Comando", + "commandProhibited": "O comando {command} é proibido. Entre em contato com um administrador para mais informações.", + "community": "Comunidade", + "completed": "Concluído", + "confirm": "Confirmar", + "confirmed": "Confirmado", + "content": "conteúdo", + "continue": "Continuar", + "control_after_generate": "controle após gerar", + "control_before_generate": "controle antes de gerar", + "copied": "Copiado", + "copy": "Copiar", + "copyAll": "Copiar tudo", + "copyJobId": "Copiar ID da tarefa", + "copyToClipboard": "Copiar para a área de transferência", + "copyURL": "Copiar URL", + "core": "Núcleo", + "currentUser": "Usuário atual", + "custom": "Personalizado", + "customBackground": "Plano de fundo personalizado", + "customize": "Personalizar", + "customizeFolder": "Personalizar pasta", + "decrement": "Diminuir", + "defaultBanner": "banner padrão", + "delete": "Excluir", + "deleteAudioFile": "Excluir arquivo de áudio", + "deleteImage": "Excluir imagem", + "deprecated": "OBSOLETO", + "description": "Descrição", + "devices": "Dispositivos", + "disableAll": "Desativar tudo", + "disableSelected": "Desativar selecionados", + "disableThirdParty": "Desativar terceiros", + "disabling": "Desativando {id}", + "dismiss": "Dispensar", + "download": "Baixar", + "downloadImage": "Baixar imagem", + "downloadVideo": "Baixar vídeo", + "downloading": "Baixando", + "dropYourFileOr": "Solte seu arquivo ou", + "duplicate": "Duplicar", + "edit": "Editar", + "editImage": "Editar imagem", + "editOrMaskImage": "Editar ou mascarar imagem", + "emDash": "—", + "empty": "Vazio", + "enableAll": "Ativar tudo", + "enableOrDisablePack": "Ativar ou desativar pacote", + "enableSelected": "Ativar selecionados", + "enabled": "Ativado", + "enabling": "Ativando {id}", + "enterBaseName": "Digite o nome base", + "enterNewName": "Digite o novo nome", + "error": "Erro", + "errorLoadingImage": "Erro ao carregar imagem", + "errorLoadingVideo": "Erro ao carregar vídeo", + "experimental": "BETA", + "export": "Exportar", + "extensionName": "Nome da extensão", + "failed": "Falhou", + "failedToCopyJobId": "Falha ao copiar o ID da tarefa", + "failedToDownloadImage": "Falha ao baixar imagem", + "failedToDownloadVideo": "Falha ao baixar vídeo", + "feedback": "Feedback", + "file": "arquivo", + "filter": "Filtrar", + "findIssues": "Encontrar problemas", + "frameNodes": "Enquadrar nós", + "frontendNewer": "A versão do frontend {frontendVersion} pode não ser compatível com a versão do backend {backendVersion}.", + "frontendOutdated": "A versão do frontend {frontendVersion} está desatualizada. O backend requer {requiredVersion} ou superior.", + "galleryImage": "Imagem da galeria", + "galleryThumbnail": "Miniatura da galeria", + "goToNode": "Ir para o nó", + "graphNavigation": "Navegação no grafo", + "halfSpeed": "0,5x", + "hideLeftPanel": "Ocultar painel esquerdo", + "hideRightPanel": "Ocultar painel direito", + "icon": "Ícone", + "imageFailedToLoad": "Falha ao carregar imagem", + "imagePreview": "Pré-visualização da imagem - Use as setas para navegar entre as imagens", + "imageUrl": "URL da imagem", + "import": "Importar", + "inProgress": "Em andamento", + "increment": "Aumentar", + "info": "Informações do nó", + "insert": "Inserir", + "install": "Instalar", + "installed": "Instalado", + "installing": "Instalando", + "interrupted": "Interrompido", + "itemSelected": "{selectedCount} item selecionado", + "itemsCopiedToClipboard": "Itens copiados para a área de transferência", + "itemsSelected": "{selectedCount} itens selecionados", + "job": "Tarefa", + "jobIdCopied": "ID da tarefa copiado para a área de transferência", + "keybinding": "Atalho", + "keybindingAlreadyExists": "Atalho já existe em", + "learnMore": "Saiba mais", + "listening": "Ouvindo...", + "liveSamplingPreview": "Pré-visualização de amostragem ao vivo", + "loadAllFolders": "Carregar todas as pastas", + "loadWorkflow": "Carregar fluxo de trabalho", + "loading": "Carregando", + "loadingPanel": "Carregando painel {panel}...", + "login": "Entrar", + "logoAlt": "Logo do ComfyUI", + "logs": "Logs", + "markdown": "markdown", + "micPermissionDenied": "Permissão do microfone negada", + "migrate": "Migrar", + "missing": "Faltando", + "more": "Mais", + "moreOptions": "Mais opções", + "moreWorkflows": "Mais fluxos de trabalho", + "multiSelectDropdown": "Menu suspenso de seleção múltipla", + "name": "Nome", + "newFolder": "Nova pasta", + "next": "Próximo", + "nightly": "NIGHTLY", + "no": "Não", + "noAudioRecorded": "Nenhum áudio gravado", + "noItems": "Nenhum item", + "noResults": "Sem resultados", + "noResultsFound": "Nenhum resultado encontrado", + "noTasksFound": "Nenhuma tarefa encontrada", + "noTasksFoundMessage": "Não há tarefas na fila.", + "noWorkflowsFound": "Nenhum fluxo de trabalho encontrado.", + "nodeContentError": "Erro no conteúdo do nó", + "nodeHeaderError": "Erro no cabeçalho do nó", + "nodeRenderError": "Erro ao renderizar nó", + "nodeSlotsError": "Erro nos slots do nó", + "nodeWidgetsError": "Erro nos widgets do nó", + "nodes": "Nós", + "nodesCount": "{count} nós | {count} nó | {count} nós", + "nodesRunning": "nós em execução", + "none": "Nenhum", + "nothingToCopy": "Nada para copiar", + "nothingToDelete": "Nada para excluir", + "nothingToDuplicate": "Nada para duplicar", + "nothingToRename": "Nada para renomear", + "ok": "OK", + "openManager": "Abrir gerenciador", + "openNewIssue": "Abrir novo problema", + "or": "ou", + "overwrite": "Sobrescrever", + "playPause": "Reproduzir/Pausar", + "playRecording": "Reproduzir gravação", + "playbackSpeed": "Velocidade de reprodução", + "playing": "Reproduzindo", + "pressKeysForNewBinding": "Pressione as teclas para novo atalho", + "preview": "PRÉVIA", + "profile": "Perfil", + "progressCountOf": "de", + "queued": "Na fila", + "ready": "Pronto", + "reconnected": "Reconectado", + "reconnecting": "Reconectando", + "refresh": "Atualizar", + "refreshNode": "Atualizar nó", + "relativeTime": { + "daysAgo": "há {count}d", + "hoursAgo": "há {count}h", + "minutesAgo": "há {count}min", + "monthsAgo": "há {count}m", + "now": "agora", + "weeksAgo": "há {count}s", + "yearsAgo": "há {count}a" + }, + "releaseTitle": "Lançamento {package} {version}", + "reloadToApplyChanges": "Recarregue para aplicar as alterações", + "removeImage": "Remover imagem", + "removeTag": "Remover tag", + "removeVideo": "Remover vídeo", + "rename": "Renomear", + "reportIssue": "Enviar relatório", + "reportIssueTooltip": "Enviar o relatório de erro para a Comfy Org", + "reportSent": "Relatório enviado", + "reset": "Redefinir", + "resetAll": "Redefinir tudo", + "resetAllKeybindingsTooltip": "Redefinir todos os atalhos para o padrão", + "resizeFromBottomLeft": "Redimensionar pelo canto inferior esquerdo", + "resizeFromBottomRight": "Redimensionar pelo canto inferior direito", + "resizeFromTopLeft": "Redimensionar pelo canto superior esquerdo", + "resizeFromTopRight": "Redimensionar pelo canto superior direito", + "restart": "Reiniciar", + "resultsCount": "{count} resultados encontrados", + "running": "Executando", + "save": "Salvar", + "saving": "Salvando", + "scrollLeft": "Rolar para a esquerda", + "scrollRight": "Rolar para a direita", + "search": "Buscar", + "searchExtensions": "Buscar extensões", + "searchFailedMessage": "Não encontramos nenhuma configuração correspondente à sua busca. Tente ajustar os termos de pesquisa.", + "searchKeybindings": "Buscar atalhos", + "searchModels": "Buscar modelos", + "searchNodes": "Buscar nós", + "searchPlaceholder": "Buscar...", + "searchSettings": "Buscar configurações", + "searchWorkflows": "Buscar fluxos de trabalho", + "seeTutorial": "Veja um tutorial", + "selectItemsToCopy": "Selecione itens para copiar", + "selectItemsToDelete": "Selecione itens para excluir", + "selectItemsToDuplicate": "Selecione itens para duplicar", + "selectItemsToRename": "Selecione itens para renomear", + "selectedFile": "Arquivo selecionado", + "setAsBackground": "Definir como plano de fundo", + "settings": "Configurações", + "showLeftPanel": "Mostrar painel esquerdo", + "showReport": "Mostrar relatório", + "showRightPanel": "Mostrar painel direito", + "singleSelectDropdown": "Menu suspenso de seleção única", + "sort": "Ordenar", + "source": "Fonte", + "startRecording": "Iniciar gravação", + "status": "Status", + "stopPlayback": "Parar reprodução", + "stopRecording": "Parar gravação", + "submit": "Enviar", + "success": "Sucesso", + "systemInfo": "Informações do sistema", + "terminal": "Terminal", + "title": "Título", + "triggerPhrase": "Frase de gatilho", + "unknownError": "Erro desconhecido", + "untitled": "Sem título", + "update": "Atualizar", + "updateAvailable": "Atualização disponível", + "updateFrontend": "Atualizar frontend", + "updated": "Atualizado", + "updating": "Atualizando {id}", + "upload": "Enviar", + "usageHint": "Dica de uso", + "use": "Usar", + "user": "Usuário", + "versionMismatchWarning": "Aviso de compatibilidade de versão", + "versionMismatchWarningMessage": "{warning}: {detail} Visite https://docs.comfy.org/installation/update_comfyui#common-update-issues para instruções de atualização.", + "videoFailedToLoad": "Falha ao carregar vídeo", + "videoPreview": "Pré-visualização do vídeo - Use as setas para navegar entre os vídeos", + "viewImageOfTotal": "Visualizar imagem {index} de {total}", + "viewVideoOfTotal": "Visualizar vídeo {index} de {total}", + "volume": "Volume", + "warning": "Aviso", + "workflow": "Fluxo de trabalho", + "you": "Você" + }, + "graphCanvasMenu": { + "fitView": "Ajustar à Tela", + "focusMode": "Modo de Foco", + "hand": "Mão", + "hideLinks": "Ocultar Conexões", + "panMode": "Modo de Panorâmica", + "resetView": "Redefinir Visão", + "select": "Selecionar", + "selectMode": "Modo de Seleção", + "showLinks": "Mostrar Conexões", + "toggleLinkVisibility": "Alternar Visibilidade das Conexões", + "toggleMinimap": "Alternar Minimapa", + "zoomIn": "Aproximar", + "zoomOptions": "Opções de Zoom", + "zoomOut": "Afastar" + }, + "groupNode": { + "create": "Criar nó de grupo", + "enterName": "Digite o nome" + }, + "help": { + "helpCenterMenu": "Menu do Centro de Ajuda", + "recentReleases": "Lançamentos recentes" + }, + "helpCenter": { + "clickToLearnMore": "Clique para saber mais →", + "desktopUserGuide": "Guia do Usuário Desktop", + "docs": "Documentação", + "feedback": "Enviar feedback", + "github": "Github", + "help": "Ajuda & Suporte", + "loadingReleases": "Carregando versões...", + "managerExtension": "Gerenciar Extensão", + "more": "Mais...", + "noRecentReleases": "Nenhuma versão recente", + "openDevTools": "Abrir Ferramentas de Desenvolvedor", + "recentReleases": "Versões recentes", + "reinstall": "Reinstalar", + "updateAvailable": "Atualizar", + "updateComfyUI": "Atualizar ComfyUI", + "updateComfyUIFailed": "Falha ao atualizar o ComfyUI. Por favor, tente novamente.", + "updateComfyUIStarted": "Atualização iniciada", + "updateComfyUIStartedDetail": "A atualização do ComfyUI foi adicionada à fila. Por favor, aguarde...", + "updateComfyUISuccess": "Atualização concluída", + "updateComfyUISuccessDetail": "ComfyUI foi atualizado. Reiniciando...", + "whatsNew": "Novidades" + }, + "icon": { + "bookmark": "Favorito", + "box": "Caixa", + "briefcase": "Maleta", + "exclamation-triangle": "Aviso", + "file": "Arquivo", + "folder": "Pasta", + "heart": "Coração", + "inbox": "Caixa de Entrada", + "star": "Estrela" + }, + "imageCompare": { + "noImages": "Nenhuma imagem para comparar" + }, + "imageCrop": { + "cropPreviewAlt": "Pré-visualização do recorte", + "loading": "Carregando...", + "noInputImage": "Nenhuma imagem de entrada conectada" + }, + "importFailed": { + "copyError": "Erro ao Copiar", + "title": "Falha na Importação" + }, + "install": { + "appDataLocationTooltip": "Diretório de dados do aplicativo ComfyUI. Armazena:\n- Logs\n- Configurações do servidor", + "appPathLocationTooltip": "Diretório de ativos do aplicativo ComfyUI. Armazena o código e os ativos do ComfyUI", + "cannotWrite": "Não foi possível gravar no caminho selecionado", + "chooseInstallationLocation": "Escolher Local de Instalação", + "customNodes": "Nós Personalizados", + "customNodesDescription": "Reinstale nós personalizados de instalações existentes do ComfyUI.", + "desktopAppSettings": "Configurações do Aplicativo Desktop", + "desktopAppSettingsDescription": "Configure como o ComfyUI se comporta no seu desktop. Você pode alterar essas configurações depois.", + "desktopSettings": "Configurações do Desktop", + "failedToSelectDirectory": "Falha ao selecionar o diretório", + "gpu": "GPU", + "gpuPicker": { + "amdDescription": "Use sua GPU AMD com aceleração ROCm™ para o melhor desempenho.", + "amdSubtitle": "AMD ROCm™", + "appleMetalDescription": "Aproveita a GPU do seu Mac para maior velocidade e uma experiência geral melhor", + "cpuDescription": "Use o modo CPU para compatibilidade quando a aceleração por GPU não estiver disponível", + "cpuSubtitle": "Modo CPU", + "manualDescription": "Configure o ComfyUI manualmente para configurações avançadas ou hardware não suportado", + "manualSubtitle": "Configuração Manual", + "nvidiaDescription": "Use sua GPU NVIDIA com aceleração CUDA para o melhor desempenho.", + "nvidiaSubtitle": "NVIDIA CUDA", + "recommended": "RECOMENDADO", + "title": "Escolha sua configuração de hardware" + }, + "gpuSelection": { + "cpuMode": "Modo CPU", + "cpuModeDescription": "O modo CPU é destinado apenas para desenvolvedores e casos raros.", + "cpuModeDescription2": "Se você não tem certeza absoluta de que precisa disso, ignore esta caixa e selecione sua GPU acima.", + "customComfyNeedsPython": "O ComfyUI não funcionará até que o python seja configurado", + "customInstallRequirements": "Instalar todos os requisitos e dependências (ex: torch customizado)", + "customManualVenv": "Configurar manualmente o venv do python", + "customMayNotWork": "Isto não é suportado e pode simplesmente não funcionar", + "customSkipsPython": "Esta opção pula a configuração normal do python.", + "enableCpuMode": "Ativar Modo CPU", + "mpsDescription": "Apple Metal Performance Shaders são suportados usando pytorch nightly.", + "nvidiaDescription": "Dispositivos NVIDIA são suportados diretamente usando builds pytorch CUDA.", + "selectGpu": "Selecionar GPU", + "selectGpuDescription": "Selecione o tipo de GPU que você possui" + }, + "helpImprove": "Por favor, ajude a melhorar o ComfyUI", + "insideAppInstallDir": "Esta pasta está dentro do pacote do aplicativo ComfyUI Desktop e será excluída durante as atualizações. Escolha um diretório fora da pasta de instalação, como Documentos/ComfyUI.", + "insideUpdaterCache": "Esta pasta está dentro do cache de atualização do ComfyUI, que é limpo a cada atualização. Selecione um local diferente para seus dados.", + "installLocation": "Local de Instalação", + "installLocationDescription": "Selecione o diretório para os dados do usuário do ComfyUI. Um ambiente Python será instalado no local selecionado.", + "installLocationTooltip": "Diretório de dados do usuário do ComfyUI. Armazena:\n- Ambiente Python\n- Modelos\n- Nós personalizados\n", + "insufficientFreeSpace": "Espaço insuficiente - espaço livre mínimo", + "isOneDrive": "OneDrive não é suportado. Por favor, instale o ComfyUI em outro local.", + "locationPicker": { + "chooseDownloadServers": "Escolher servidores de download manualmente", + "downloadServersDescription": "Selecione servidores espelho específicos para baixar Python, pacotes PyPI e PyTorch de acordo com sua localização.", + "migrateDescription": "Copie ou vincule seus modelos, nós personalizados e configurações de uma instalação anterior do ComfyUI.", + "migrateFromExisting": "Migrar de uma instalação existente", + "migrationPathPlaceholder": "Selecione uma instalação existente do ComfyUI (opcional)", + "pathPlaceholder": "/Users/username/Documents/ComfyUI", + "subtitle": "Escolha uma pasta para os arquivos do ComfyUI. Também configuraremos o Python automaticamente lá.", + "title": "Escolha onde instalar o ComfyUI" + }, + "manualConfiguration": { + "createVenv": "Você precisará criar um ambiente virtual no seguinte diretório", + "requirements": "Requisitos", + "restartWhenFinished": "Quando terminar de configurar o ambiente virtual, por favor reinicie o ComfyUI.", + "title": "Configuração Manual", + "virtualEnvironmentPath": "Caminho do ambiente virtual" + }, + "metricsDisabled": "Métricas Desativadas", + "metricsEnabled": "Métricas Ativadas", + "migrateFromExistingInstallation": "Migrar de Instalação Existente", + "migration": "Migração", + "migrationOptional": "A migração é opcional. Se você não possui uma instalação existente, pode pular esta etapa.", + "migrationSourcePathDescription": "Se você já possui uma instalação do ComfyUI, podemos copiar/vincular seus arquivos de usuário e modelos existentes para a nova instalação. Sua instalação atual do ComfyUI não será afetada.", + "moreInfo": "Para mais informações, leia nossa", + "nonDefaultDrive": "Por favor, instale o ComfyUI na unidade do sistema (ex: C:\\). Unidades com sistemas de arquivos diferentes podem causar problemas imprevisíveis. Modelos e outros arquivos podem ser armazenados em outras unidades após a instalação.", + "parentMissing": "O caminho não existe - crie o diretório de nível superior primeiro", + "pathExists": "O diretório já existe - certifique-se de ter feito backup de todos os dados", + "pathValidationFailed": "Falha ao validar o caminho", + "privacyPolicy": "política de privacidade", + "selectItemsToMigrate": "Selecione Itens para Migrar", + "settings": { + "allowMetrics": "Métricas de Uso", + "allowMetricsDescription": "Ajude a melhorar o ComfyUI enviando métricas de uso anônimas. Nenhuma informação pessoal ou conteúdo de fluxo de trabalho será coletado.", + "autoUpdate": "Atualizações Automáticas", + "autoUpdateDescription": "Baixe atualizações automaticamente quando estiverem disponíveis. Você será notificado antes das atualizações serem instaladas.", + "checkingMirrors": "Verificando acesso à rede para os espelhos do python...", + "dataCollectionDialog": { + "collect": { + "errorReports": "Mensagem de erro e rastreamento de pilha", + "systemInfo": "Hardware, tipo de SO e versão do aplicativo", + "userJourneyEvents": "Eventos da jornada do usuário" + }, + "doNotCollect": { + "customNodeConfigurations": "Configurações de nós personalizados", + "fileSystemInformation": "Informações do sistema de arquivos", + "personalInformation": "Informações pessoais", + "workflowContents": "Conteúdo do fluxo de trabalho" + }, + "title": "Sobre a Coleta de Dados", + "viewFullPolicy": "Ver política completa", + "whatWeCollect": "O que coletamos:", + "whatWeDoNotCollect": "O que não coletamos:" + }, + "errorUpdatingConsent": "Erro ao Atualizar Consentimento", + "errorUpdatingConsentDetail": "Falha ao atualizar as configurações de consentimento de métricas", + "learnMoreAboutData": "Saiba mais sobre a coleta de dados", + "mirrorSettings": "Configurações de Espelho", + "mirrorsReachable": "O acesso à rede para os espelhos do python está bom", + "mirrorsUnreachable": "O acesso à rede para alguns espelhos do python está ruim", + "pypiMirrorPlaceholder": "Insira a URL do espelho do PyPI", + "pythonMirrorPlaceholder": "Insira a URL do espelho do Python" + }, + "systemLocations": "Locais do Sistema", + "unhandledError": "Erro desconhecido", + "updateConsent": "Você optou anteriormente por relatar falhas. Agora estamos rastreando métricas baseadas em eventos para ajudar a identificar bugs e melhorar o aplicativo. Nenhuma informação pessoal identificável é coletada." + }, + "issueReport": { + "helpFix": "Ajude a Corrigir Isto" + }, + "linearMode": { + "beta": "Beta - Envie seu feedback", + "downloadAll": "Baixar tudo", + "dragAndDropImage": "Arraste e solte uma imagem", + "graphMode": "Modo Gráfico", + "linearMode": "Modo Simples", + "rerun": "Executar novamente", + "reuseParameters": "Reutilizar parâmetros", + "runCount": "Número de execuções:" + }, + "load3d": { + "applyingTexture": "Aplicando Textura...", + "backgroundColor": "Cor de Fundo", + "camera": "Câmera", + "cameraType": { + "orthographic": "Ortográfica", + "perspective": "Perspectiva" + }, + "clearRecording": "Limpar Gravação", + "dropToLoad": "Arraste o modelo 3D para carregar", + "edgeThreshold": "Limite de Borda", + "export": "Exportar", + "exportModel": "Exportar Modelo", + "exportRecording": "Exportar Gravação", + "exportingModel": "Exportando modelo...", + "fov": "Campo de Visão (FOV)", + "light": "Luz", + "lightIntensity": "Intensidade da Luz", + "loadingBackgroundImage": "Carregando Imagem de Fundo", + "loadingModel": "Carregando Modelo 3D...", + "materialMode": "Modo de Material", + "materialModes": { + "depth": "Profundidade", + "lineart": "Lineart", + "normal": "Normal", + "original": "Original", + "pointCloud": "Nuvem de Pontos", + "wireframe": "Wireframe" + }, + "model": "Modelo", + "openIn3DViewer": "Abrir no Visualizador 3D", + "panoramaMode": "Panorama", + "previewOutput": "Visualizar Saída", + "reloadingModel": "Recarregando modelo...", + "removeBackgroundImage": "Remover Imagem de Fundo", + "resizeNodeMatchOutput": "Redimensionar Nó para corresponder à saída", + "scene": "Cena", + "showGrid": "Mostrar Grade", + "showSkeleton": "Mostrar Esqueleto", + "startRecording": "Iniciar Gravação", + "stopRecording": "Parar Gravação", + "switchCamera": "Alternar Câmera", + "switchingMaterialMode": "Alternando Modo de Material...", + "tiledMode": "Mosaico", + "unsupportedFileType": "Tipo de arquivo não suportado (suporta .gltf, .glb, .obj, .fbx, .stl)", + "upDirection": "Direção Para Cima", + "upDirections": { + "original": "Original" + }, + "uploadBackgroundImage": "Enviar Imagem de Fundo", + "uploadTexture": "Enviar Textura", + "uploadingModel": "Enviando modelo 3D...", + "viewer": { + "apply": "Aplicar", + "cameraSettings": "Configurações da Câmera", + "cameraType": "Tipo de Câmera", + "cancel": "Cancelar", + "exportSettings": "Configurações de Exportação", + "lightSettings": "Configurações de Luz", + "modelSettings": "Configurações do Modelo", + "sceneSettings": "Configurações da Cena", + "title": "Visualizador 3D (Beta)" + } + }, + "loadWorkflowWarning": { + "coreNodesFromVersion": "Nós principais da versão {version}:", + "outdatedVersion": "Este fluxo de trabalho foi criado com uma versão mais recente do ComfyUI ({version}). Alguns nós podem não funcionar corretamente.", + "outdatedVersionGeneric": "Este fluxo de trabalho foi criado com uma versão mais recente do ComfyUI. Alguns nós podem não funcionar corretamente." + }, + "maintenance": { + "None": "Nenhum", + "OK": "OK", + "Skipped": "Ignorado", + "allOk": "Nenhum problema foi detectado.", + "confirmTitle": "Tem certeza?", + "consoleLogs": "Logs do Console", + "detected": "Detectado", + "error": { + "cannotContinue": "Não é possível continuar - ainda há erros", + "defaultDescription": "Ocorreu um erro ao executar uma tarefa de manutenção.", + "taskFailed": "Falha ao executar a tarefa.", + "toastTitle": "Erro na tarefa" + }, + "refreshing": "Atualizando", + "showManual": "Mostrar tarefas de manutenção", + "status": "Status", + "terminalDefaultMessage": "Ao executar um comando de solução de problemas, qualquer saída será exibida aqui.", + "title": "Manutenção", + "unsafeMigration": { + "action": "Use a tarefa de manutenção \"Caminho base\" abaixo para mover o ComfyUI para um local seguro.", + "appInstallDir": "Seu caminho base está dentro do pacote do aplicativo ComfyUI Desktop. Esta pasta pode ser excluída ou sobrescrita durante as atualizações. Escolha um diretório fora da pasta de instalação, como Documentos/ComfyUI.", + "generic": "Seu caminho base atual do ComfyUI está em um local que pode ser excluído ou modificado durante as atualizações. Para evitar perda de dados, mova-o para uma pasta segura.", + "oneDrive": "Seu caminho base está no OneDrive, o que pode causar problemas de sincronização e perda acidental de dados. Escolha uma pasta local que não seja gerenciada pelo OneDrive.", + "title": "Local de instalação inseguro detectado", + "updaterCache": "Seu caminho base está dentro do cache de atualização do ComfyUI, que é limpo a cada atualização. Escolha um local diferente para seus dados." + } + }, + "manager": { + "allMissingNodesInstalled": "Todos os nodes ausentes foram instalados com sucesso", + "applyChanges": "Aplicar Alterações", + "changingVersion": "Alterando versão de {from} para pt-BR", + "clickToFinishSetup": "Clique", + "conflicts": { + "conflictInfoTitle": "Por que isso está acontecendo?", + "conflictMessages": { + "accelerator": "GPU/Acelerador não suportado (disponível: {current}, requerido: {required})", + "banned": "Este pacote está banido por motivos de segurança", + "comfyui_version": "Versão do ComfyUI incompatível (atual: {current}, requerida: {required})", + "frontend_version": "Versão do frontend incompatível (atual: {current}, requerida: {required})", + "generic": "Problema de compatibilidade (atual: {current}, requerido: {required})", + "import_failed": "Falha na Importação", + "os": "Sistema operacional não suportado (atual: {current}, requerido: {required})", + "pending": "Verificação de segurança pendente - compatibilidade não pode ser verificada" + }, + "conflicts": "Conflitos", + "description": "Detectamos conflitos entre algumas de suas extensões e a nova versão do ComfyUI. Ao atualizar, você corre o risco de quebrar workflows que dependem dessas extensões.", + "enableAnyway": "Ativar Mesmo Assim", + "extensionAtRisk": "Extensão em Risco", + "importFailedExtensions": "Extensões com Falha na Importação", + "info": "Se continuar com a atualização, as extensões conflitantes serão desativadas automaticamente. Você pode revisá-las e gerenciá-las a qualquer momento no Gerenciador do ComfyUI.", + "installAnyway": "Instalar Mesmo Assim", + "title": "Problemas com Node Pack Detectados!", + "understood": "Entendi", + "warningBanner": { + "button": "Saiba Mais...", + "message": "Essas extensões requerem versões de pacotes do sistema que diferem da sua configuração atual. Instalá-las pode sobrescrever dependências principais e afetar outras extensões ou workflows.", + "title": "Algumas extensões estão desativadas devido à incompatibilidade com sua configuração atual" + }, + "warningTooltip": "Este pacote pode ter problemas de compatibilidade com seu ambiente atual" + }, + "createdBy": "Criado por", + "dependencies": "Dependências", + "disabledNodesWontUpdate": "Nodes desativados não serão atualizados", + "discoverCommunityContent": "Descubra Node Packs, Extensões e mais criados pela comunidade...", + "downloads": "Downloads", + "enablePackToChangeVersion": "Ative este pacote para alterar versões", + "errorConnecting": "Erro ao conectar ao Registro de Nodes do Comfy.", + "extensionsSuccessfullyInstalled": "Extensão(ões) instalada(s) com sucesso e pronta(s) para uso!", + "failed": "Falhou", + "failedToInstall": "Falha ao Instalar", + "filter": { + "disabled": "Desativado", + "enabled": "Ativado", + "nodePack": "Node Pack" + }, + "gettingInfo": "Obtendo informações...", + "importFailedGenericError": "Falha ao importar o pacote. Verifique o console para mais detalhes.", + "inWorkflow": "No Workflow", + "infoPanelEmpty": "Clique em um item para ver as informações", + "installAllMissingNodes": "Instalar Todos", + "installError": "Erro de Instalação", + "installSelected": "Instalar Selecionados", + "installationQueue": "Fila de Instalação", + "installingDependencies": "Instalando dependências...", + "lastUpdated": "Última Atualização", + "latestVersion": "Mais Recente", + "legacyManagerUI": "Usar UI Legada", + "legacyManagerUIDescription": "Para usar a UI Legada do Gerenciador, inicie o ComfyUI com --enable-manager-legacy-ui", + "legacyMenuNotAvailable": "O menu do gerenciador legado não está disponível, usando o novo menu do gerenciador por padrão.", + "license": "Licença", + "loadingVersions": "Carregando versões...", + "mixedSelectionMessage": "Não é possível realizar ação em massa em seleção mista", + "nightlyVersion": "Noturna", + "noDescription": "Nenhuma descrição disponível", + "noNodesFound": "Nenhum node encontrado", + "noNodesFoundDescription": "Os nodes do pacote não puderam ser analisados ou o pacote é apenas uma extensão de frontend e não possui nodes.", + "noResultsFound": "Nenhum resultado encontrado para sua busca.", + "nodePack": "Node Pack", + "notAvailable": "Não Disponível", + "packsSelected": "pacotes selecionados", + "repository": "Repositório", + "restartToApplyChanges": "Para aplicar as alterações, reinicie o ComfyUI", + "restartingBackend": "Reiniciando backend para aplicar as alterações...", + "searchPlaceholder": "Buscar", + "selectVersion": "Selecionar Versão", + "sort": { + "created": "Mais Novos", + "downloads": "Mais Populares", + "publisher": "Publicador", + "updated": "Atualizados Recentemente" + }, + "status": { + "active": "Ativo", + "banned": "Banido", + "conflicting": "Conflitante", + "deleted": "Excluído", + "flagged": "Marcado", + "importFailed": "Erro de Instalação", + "pending": "Pendente", + "unknown": "Desconhecido" + }, + "title": "Gerenciador de Nodes Personalizados", + "toFinishSetup": "para finalizar a configuração", + "totalNodes": "Total de Nodes", + "tryAgainLater": "Por favor, tente novamente mais tarde.", + "tryDifferentSearch": "Por favor, tente uma busca diferente.", + "tryUpdate": "Tentar Atualizar", + "tryUpdateTooltip": "Buscar as últimas alterações do repositório. Versões noturnas podem ter atualizações que não são detectadas automaticamente.", + "uninstall": "Desinstalar", + "uninstallSelected": "Desinstalar Selecionados", + "uninstalling": "Desinstalando {id}", + "update": "Atualizar", + "updateAll": "Atualizar Todos", + "updateSelected": "Atualizar Selecionados", + "updatingAllPacks": "Atualizando todos os pacotes", + "version": "Versão" + }, + "maskEditor": { + "activateLayer": "Ativar camada", + "applyToWholeImage": "Aplicar à imagem inteira", + "baseImageLayer": "Camada de imagem base", + "baseLayerPreview": "Pré-visualização da camada base", + "black": "Preto", + "brushSettings": "Configurações do pincel", + "brushShape": "Forma do pincel", + "clear": "Limpar", + "clickToResetZoom": "Clique para redefinir o zoom", + "colorSelectSettings": "Configurações de seleção de cor", + "colorSelector": "Seletor de cor", + "fillOpacity": "Opacidade do preenchimento", + "hardness": "Dureza", + "imageLayer": "Camada de imagem", + "invert": "Inverter", + "layers": "Camadas", + "livePreview": "Pré-visualização ao vivo", + "maskBlendingOptions": "Opções de mesclagem da máscara", + "maskLayer": "Camada de máscara", + "maskOpacity": "Opacidade da máscara", + "maskTolerance": "Tolerância da máscara", + "method": "Método", + "mirrorHorizontal": "Espelhar horizontalmente", + "mirrorVertical": "Espelhar verticalmente", + "negative": "Negativo", + "opacity": "Opacidade", + "paintBucketSettings": "Configurações do balde de tinta", + "paintLayer": "Camada de pintura", + "redo": "Refazer", + "resetToDefault": "Restaurar padrão", + "rotateLeft": "Girar para a esquerda", + "rotateRight": "Girar para a direita", + "selectionOpacity": "Opacidade da seleção", + "smoothingPrecision": "Precisão de suavização", + "stepSize": "Tamanho do passo", + "stopAtMask": "Parar na máscara", + "thickness": "Espessura", + "title": "Editor de máscara", + "tolerance": "Tolerância", + "undo": "Desfazer", + "white": "Branco" + }, + "mediaAsset": { + "actions": { + "copyJobId": "Copiar ID do trabalho", + "delete": "Excluir", + "download": "Baixar", + "exportWorkflow": "Exportar fluxo de trabalho", + "insertAsNodeInWorkflow": "Inserir como nó no fluxo de trabalho", + "inspect": "Inspecionar recurso", + "more": "Mais opções", + "moreOptions": "Mais opções", + "openWorkflow": "Abrir como fluxo de trabalho em nova aba", + "seeMoreOutputs": "Ver mais resultados", + "zoom": "Ampliar" + }, + "assetDeletedSuccessfully": "Recurso excluído com sucesso", + "deleteAssetDescription": "Este recurso será removido permanentemente.", + "deleteAssetTitle": "Excluir este recurso?", + "deleteSelectedDescription": "{count} recurso(s) será(ão) removido(s) permanentemente.", + "deleteSelectedTitle": "Excluir recursos selecionados?", + "deletingImportedFilesCloudOnly": "A exclusão de arquivos importados é suportada apenas na versão em nuvem", + "failedToCreateNode": "Falha ao criar nó", + "failedToDeleteAsset": "Falha ao excluir o recurso", + "failedToExportWorkflow": "Falha ao exportar fluxo de trabalho", + "jobIdToast": { + "copied": "Copiado", + "error": "Erro", + "jobIdCopied": "ID do trabalho copiado para a área de transferência", + "jobIdCopyFailed": "Falha ao copiar o ID do trabalho" + }, + "noJobIdFound": "Nenhum ID de trabalho encontrado para este recurso", + "noWorkflowDataFound": "Nenhum dado de fluxo de trabalho encontrado neste recurso", + "nodeAddedToWorkflow": "Nó {nodeType} adicionado ao fluxo de trabalho", + "nodeTypeNotFound": "Tipo de nó {nodeType} não encontrado", + "selection": { + "assetsDeletedSuccessfully": "{count} recurso(s) excluído(s) com sucesso", + "deleteSelected": "Excluir", + "deleteSelectedAll": "Excluir todos", + "deselectAll": "Desmarcar todos", + "downloadSelected": "Baixar", + "downloadSelectedAll": "Baixar todos", + "downloadStarted": "Baixando {count} arquivos...", + "downloadsStarted": "Iniciada a transferência de {count} arquivo(s)", + "exportWorkflowAll": "Exportar todos os fluxos de trabalho", + "failedToAddNodes": "Falha ao adicionar nós ao fluxo de trabalho", + "failedToDeleteAssets": "Falha ao excluir os recursos selecionados", + "insertAllAssetsAsNodes": "Inserir todos os ativos como nós", + "multipleSelectedAssets": "Vários ativos selecionados", + "noWorkflowsFound": "Nenhum dado de fluxo de trabalho encontrado nos ativos selecionados", + "noWorkflowsToExport": "Nenhum dado de fluxo de trabalho encontrado para exportar", + "nodesAddedToWorkflow": "{count} nó(s) adicionado(s) ao fluxo de trabalho", + "openWorkflowAll": "Abrir todos os fluxos de trabalho", + "partialAddNodesSuccess": "{succeeded} adicionado(s) com sucesso, {failed} falharam", + "partialDeleteSuccess": "{succeeded} excluído(s) com sucesso, {failed} falha(s)", + "partialWorkflowsExported": "{succeeded} exportado(s) com sucesso, {failed} falharam", + "partialWorkflowsOpened": "{succeeded} fluxo(s) de trabalho aberto(s), {failed} falharam", + "selectedCount": "Recursos selecionados: {count}", + "workflowsExported": "{count} fluxo(s) de trabalho exportado(s) com sucesso", + "workflowsOpened": "{count} fluxo(s) de trabalho aberto(s) em novas abas" + }, + "unsupportedFileType": "Tipo de arquivo não suportado para o nó de carregamento", + "workflowExportedSuccessfully": "Fluxo de trabalho exportado com sucesso", + "workflowOpenedInNewTab": "Fluxo de trabalho aberto em nova aba" + }, + "menu": { + "autoQueue": "Fila automática", + "batchCount": "Quantidade de lotes", + "batchCountTooltip": "O número de vezes que a geração do fluxo de trabalho deve ser enfileirada", + "clear": "Limpar fluxo de trabalho", + "clipspace": "Abrir Clipspace", + "customNodesManager": "Gerenciador de nós personalizados", + "dark": "Escuro", + "disabled": "Desativado", + "disabledTooltip": "O fluxo de trabalho não será enfileirado automaticamente", + "execute": "Executar", + "help": "Ajuda", + "helpAndFeedback": "Ajuda e feedback", + "hideMenu": "Ocultar menu", + "instant": "Instantâneo", + "instantTooltip": "O fluxo de trabalho será enfileirado instantaneamente após uma geração terminar", + "interrupt": "Cancelar execução atual", + "light": "Claro", + "manageExtensions": "Gerenciar extensões", + "onChange": "Ao alterar", + "onChangeTooltip": "O fluxo de trabalho será enfileirado quando uma alteração for feita", + "queue": "Painel de fila", + "refresh": "Atualizar definições de nós", + "resetView": "Redefinir visualização da tela", + "run": "Executar", + "runWorkflow": "Executar fluxo de trabalho (Shift para enfileirar na frente)", + "runWorkflowDisabled": "O fluxo de trabalho contém nós não suportados (destacados em vermelho). Remova-os para executar o fluxo de trabalho.", + "runWorkflowFront": "Executar fluxo de trabalho (Enfileirar na frente)", + "settings": "Configurações", + "showMenu": "Mostrar menu", + "theme": "Tema", + "toggleBottomPanel": "Alternar painel inferior" + }, + "menuLabels": { + "About ComfyUI": "Sobre o ComfyUI", + "Assets": "Ativos", + "Bottom Panel": "Painel inferior", + "Browse Templates": "Navegar por modelos", + "Bypass/Unbypass Selected Nodes": "Ignorar/Não ignorar nós selecionados", + "Canvas Performance": "Desempenho da tela", + "Canvas Toggle Lock": "Alternar trava da tela", + "Check for Custom Node Updates": "Verificar atualizações de nós personalizados", + "Check for Updates": "Verificar atualizações", + "Clear Pending Tasks": "Limpar tarefas pendentes", + "Clear Workflow": "Limpar fluxo de trabalho", + "Clipspace": "Clipspace", + "Close Current Workflow": "Fechar fluxo de trabalho atual", + "Collapse/Expand Selected Nodes": "Recolher/Expandir nós selecionados", + "Comfy-Org Discord": "Comfy-Org Discord", + "ComfyUI Docs": "Documentação do ComfyUI", + "ComfyUI Forum": "Fórum do ComfyUI", + "ComfyUI Issues": "Problemas do ComfyUI", + "Contact Support": "Contatar suporte", + "Convert Selection to Subgraph": "Converter seleção em subgrafo", + "Convert selected nodes to group node": "Converter nós selecionados em nó de grupo", + "Custom Nodes (Legacy)": "Nós personalizados (Legado)", + "Custom Nodes Manager": "Gerenciador de nós personalizados", + "Decrease Brush Size in MaskEditor": "Diminuir tamanho do pincel no MaskEditor", + "Delete Selected Items": "Excluir itens selecionados", + "Desktop User Guide": "Guia do usuário (Desktop)", + "Duplicate Current Workflow": "Duplicar fluxo de trabalho atual", + "Edit": "Editar", + "Edit Subgraph Widgets": "Editar widgets do subgrafo", + "Exit Subgraph": "Sair do subgrafo", + "Experimental: Browse Model Assets": "Experimental: Navegar por ativos de modelo", + "Experimental: Enable AssetAPI": "Experimental: Ativar AssetAPI", + "Experimental: Enable Nodes 2_0": "Experimental: Ativar Nodes 2.0", + "Export": "Exportar", + "Export (API)": "Exportar (API)", + "File": "Arquivo", + "Fit Group To Contents": "Ajustar grupo ao conteúdo", + "Focus Mode": "Modo de foco", + "Group Selected Nodes": "Agrupar nós selecionados", + "Help": "Ajuda", + "Help Center": "Central de ajuda", + "Increase Brush Size in MaskEditor": "Aumentar tamanho do pincel no MaskEditor", + "Install Missing Custom Nodes": "Instalar nós personalizados ausentes", + "Interrupt": "Interromper", + "Job History": "Histórico de tarefas", + "Load Default Workflow": "Carregar fluxo de trabalho padrão", + "Lock Canvas": "Travar tela", + "Manage group nodes": "Gerenciar nós de grupo", + "Manager": "Gerenciador", + "Manager Menu (Legacy)": "Menu do gerenciador (Legado)", + "Minimap": "Minimapa", + "Mirror Horizontal in MaskEditor": "Espelhar horizontalmente no MaskEditor", + "Mirror Vertical in MaskEditor": "Espelhar verticalmente no MaskEditor", + "Model Library": "Biblioteca de modelos", + "Move Selected Nodes Down": "Mover nós selecionados para baixo", + "Move Selected Nodes Left": "Mover nós selecionados para a esquerda", + "Move Selected Nodes Right": "Mover nós selecionados para a direita", + "Move Selected Nodes Up": "Mover nós selecionados para cima", + "Mute/Unmute Selected Nodes": "Silenciar/Ativar som dos nós selecionados", + "New": "Novo", + "Next Opened Workflow": "Próximo fluxo de trabalho aberto", + "Node Library": "Biblioteca de nós", + "Node Links": "Ligações de nós", + "Open": "Abrir", + "Open 3D Viewer (Beta) for Selected Node": "Abrir visualizador 3D (Beta) para o nó selecionado", + "Open Color Picker in MaskEditor": "Abrir seletor de cores no MaskEditor", + "Open Custom Nodes Folder": "Abrir pasta de nós personalizados", + "Open DevTools": "Abrir DevTools", + "Open Inputs Folder": "Abrir pasta de entradas", + "Open Logs Folder": "Abrir pasta de logs", + "Open Mask Editor for Selected Node": "Abrir Mask Editor para o nó selecionado", + "Open Models Folder": "Abrir pasta de modelos", + "Open Outputs Folder": "Abrir pasta de saídas", + "Open Sign In Dialog": "Abrir diálogo de login", + "Open extra_model_paths_yaml": "Abrir extra_model_paths.yaml", + "Pin/Unpin Selected Items": "Fixar/Desafixar itens selecionados", + "Pin/Unpin Selected Nodes": "Fixar/Desafixar nós selecionados", + "Previous Opened Workflow": "Fluxo de trabalho anterior aberto", + "Publish": "Publicar", + "Queue Prompt": "Adicionar à fila", + "Queue Prompt (Front)": "Adicionar à fila (início)", + "Queue Selected Output Nodes": "Adicionar nós de saída selecionados à fila", + "Quit": "Sair", + "Redo": "Refazer", + "Refresh Node Definitions": "Atualizar definições de nós", + "Reinstall": "Reinstalar", + "Rename": "Renomear", + "Reset View": "Redefinir visualização", + "Resize Selected Nodes": "Redimensionar nós selecionados", + "Restart": "Reiniciar", + "Rotate Left in MaskEditor": "Girar para a esquerda no MaskEditor", + "Rotate Right in MaskEditor": "Girar para a direita no MaskEditor", + "Save": "Salvar", + "Save As": "Salvar como", + "Show Keybindings Dialog": "Mostrar diálogo de atalhos", + "Show Model Selector (Dev)": "Mostrar seletor de modelo (Dev)", + "Show Settings Dialog": "Mostrar diálogo de configurações", + "Sign Out": "Sair", + "Toggle Essential Bottom Panel": "Alternar painel inferior essencial", + "Toggle Logs Bottom Panel": "Alternar painel inferior de logs", + "Toggle Queue Panel V2": "Alternar painel de fila V2", + "Toggle Search Box": "Alternar caixa de pesquisa", + "Toggle Simple Mode": "Alternar Modo Simples", + "Toggle Terminal Bottom Panel": "Alternar painel inferior do terminal", + "Toggle Theme (Dark/Light)": "Alternar tema (Escuro/Claro)", + "Toggle View Controls Bottom Panel": "Alternar painel inferior de controles de visualização", + "Toggle promotion of hovered widget": "Alternar promoção do widget destacado", + "Undo": "Desfazer", + "Ungroup selected group nodes": "Desagrupar nós de grupo selecionados", + "Unload Models": "Descarregar modelos", + "Unload Models and Execution Cache": "Descarregar modelos e cache de execução", + "Unlock Canvas": "Destravar tela", + "Unpack the selected Subgraph": "Desempacotar o subgrafo selecionado", + "View": "Visualizar", + "Workflows": "Fluxos de trabalho", + "Zoom In": "Aumentar zoom", + "Zoom Out": "Diminuir zoom", + "Zoom to fit": "Ajustar zoom" + }, + "minimap": { + "nodeColors": "Cores dos Nós", + "renderBypassState": "Exibir Estado de Ignorar", + "renderErrorState": "Exibir Estado de Erro", + "showGroups": "Mostrar Quadros/Grupos", + "showLinks": "Mostrar Conexões" + }, + "missingModelsDialog": { + "doNotAskAgain": "Não mostrar novamente", + "missingModels": "Modelos ausentes", + "missingModelsMessage": "Ao carregar o grafo, os seguintes modelos não foram encontrados" + }, + "missingNodes": { + "cloud": { + "description": "Este fluxo de trabalho utiliza nós personalizados que ainda não são suportados na versão em nuvem.", + "gotIt": "Ok, entendi", + "learnMore": "Saiba mais", + "priorityMessage": "Marcamos automaticamente esses nós para priorizarmos a adição deles.", + "replacementInstruction": "Enquanto isso, substitua esses nós (destacados em vermelho no canvas) por outros suportados, se possível, ou tente um fluxo de trabalho diferente.", + "title": "Esses nós ainda não estão disponíveis no Comfy Cloud" + }, + "oss": { + "description": "Este fluxo de trabalho utiliza nós personalizados que você ainda não instalou.", + "replacementInstruction": "Instale esses nós para executar este fluxo de trabalho ou substitua-os por alternativas já instaladas. Nós ausentes estão destacados em vermelho no canvas.", + "title": "Este fluxo de trabalho possui nós ausentes" + } + }, + "nightly": { + "badge": { + "label": "Versão de Prévia", + "tooltip": "Você está usando uma versão nightly do ComfyUI. Por favor, use o botão de feedback para compartilhar suas opiniões sobre esses recursos." + } + }, + "nodeCategories": { + "": "", + "3d": "3d", + "3d_models": "modelos_3d", + "BFL": "BFL", + "Bria": "Bria", + "ByteDance": "ByteDance", + "Gemini": "Gemini", + "Ideogram": "Ideogram", + "Kling": "Kling", + "LTXV": "LTXV", + "Luma": "Luma", + "Meshy": "Meshy", + "MiniMax": "MiniMax", + "Moonvalley Marey": "Moonvalley Marey", + "OpenAI": "OpenAI", + "PixVerse": "PixVerse", + "Recraft": "Recraft", + "Rodin": "Rodin", + "Runway": "Runway", + "Sora": "Sora", + "Stability AI": "Stability AI", + "Tencent": "Tencent", + "Topaz": "Topaz", + "Tripo": "Tripo", + "Veo": "Veo", + "Vidu": "Vidu", + "Wan": "Wan", + "WaveSpeed": "WaveSpeed", + "_for_testing": "_for_testing", + "advanced": "avançado", + "animation": "animação", + "api": "api", + "api node": "nó da API", + "attention_experiments": "experimentos_de_atenção", + "audio": "áudio", + "batch": "lote", + "camera": "câmera", + "chroma_radiance": "radiância_de_croma", + "clip": "clip", + "combine": "combinar", + "compositing": "composição", + "cond pair": "par_de_condições", + "cond single": "condição_simples", + "conditioning": "condicionamento", + "context": "contexto", + "controlnet": "controlnet", + "create": "criar", + "custom_sampling": "amostragem_personalizada", + "dataset": "conjunto_de_dados", + "debug": "depuração", + "deprecated": "obsoleto", + "edit_models": "editar_modelos", + "flux": "flux", + "gligen": "gligen", + "guidance": "orientação", + "guiders": "guias", + "hooks": "ganchos", + "image": "imagem", + "inpaint": "restauração", + "instructpix2pix": "instructpix2pix", + "kandinsky5": "kandinsky5", + "latent": "latente", + "loaders": "carregadores", + "logic": "lógica", + "lotus": "lotus", + "ltxv": "ltxv", + "mask": "máscara", + "model": "modelo", + "model_merging": "mesclagem_de_modelos", + "model_patches": "correções_de_modelo", + "model_specific": "específico_do_modelo", + "noise": "ruído", + "operations": "operações", + "photomaker": "photomaker", + "postprocessing": "pós-processamento", + "preprocessors": "pré-processadores", + "primitive": "primitivo", + "qwen": "qwen", + "samplers": "amostradores", + "sampling": "amostragem", + "save": "salvar", + "schedulers": "agendadores", + "scheduling": "agendamento", + "sd": "sd", + "sd3": "sd3", + "sigmas": "sigmas", + "stable_cascade": "stable_cascade", + "string": "string", + "style_model": "modelo_de_estilo", + "text": "texto", + "training": "treinamento", + "transform": "transformar", + "unet": "unet", + "upscale_diffusion": "difusão_de_aumento", + "upscaling": "aumento_de_resolução", + "utils": "utilitários", + "video": "vídeo", + "video_models": "modelos_de_vídeo", + "zimage": "zimage" + }, + "nodeErrors": { + "content": "Erro de Conteúdo do Nó", + "header": "Erro de Cabeçalho do Nó", + "render": "Erro de Renderização do Nó", + "slots": "Erro de Slots do Nó", + "widgets": "Erro de Widgets do Nó" + }, + "nodeHelpPage": { + "documentationPage": "página de documentação", + "inputs": "Entradas", + "loadError": "Falha ao carregar ajuda: {error}", + "moreHelp": "Para mais ajuda, visite a", + "outputs": "Saídas", + "type": "Tipo" + }, + "nodeTemplates": { + "enterName": "Digite o nome", + "saveAsTemplate": "Salvar como modelo" + }, + "notSupported": { + "continue": "Continuar", + "continueTooltip": "Tenho certeza de que meu dispositivo é suportado", + "illustrationAlt": "Ilustração de garota triste", + "learnMore": "Saiba Mais", + "message": "Apenas os seguintes dispositivos são suportados:", + "reportIssue": "Reportar Problema", + "supportedDevices": { + "macos": "MacOS (M1 ou posterior)", + "windows": "Windows (GPU Nvidia com suporte a CUDA)" + }, + "title": "Seu dispositivo não é suportado" + }, + "progressToast": { + "allDownloadsCompleted": "Todos os downloads concluídos", + "downloadingModel": "Baixando modelo...", + "downloadsFailed": "{count} downloads falharam | {count} download falhou | {count} downloads falharam", + "failed": "Falhou", + "filter": { + "all": "Todos", + "completed": "Concluídos", + "failed": "Falharam" + }, + "finished": "Concluído", + "importingModels": "Importando modelos", + "noImportsInQueue": "Nenhum {filter} na fila", + "pending": "Pendente", + "progressCount": "{completed} de {total}" + }, + "queue": { + "completedIn": "Finalizado em {duration}", + "inQueue": "Na fila...", + "initializingAlmostReady": "Inicializando - Quase pronto", + "jobAddedToQueue": "Tarefa adicionada à fila", + "jobDetails": { + "computeHoursUsed": "Horas de computação usadas", + "errorMessage": "Mensagem de erro", + "estimatedFinishIn": "Estimado para finalizar em", + "estimatedStartIn": "Estimado para iniciar em", + "eta": { + "minutes": "~{count} minuto | ~{count} minutos", + "minutesRange": "~{lo}-{hi} minutos", + "seconds": "~{count} segundo | ~{count} segundos", + "secondsRange": "~{lo}-{hi} segundos" + }, + "failedAfter": "Falhou após", + "generatedOn": "Gerado em", + "header": "Detalhes da tarefa", + "jobId": "ID da tarefa", + "queuePosition": "Posição na fila", + "queuePositionValue": "~{count} tarefa à frente da sua | ~{count} tarefas à frente da sua", + "queuedAt": "Enfileirado em", + "report": "Reportar", + "timeElapsed": "Tempo decorrido", + "totalGenerationTime": "Tempo total de geração", + "workflow": "Fluxo de trabalho" + }, + "jobHistory": "Histórico de tarefas", + "jobList": { + "sortComputeHoursUsed": "Horas de computação usadas (maior primeiro)", + "sortMostRecent": "Mais recente", + "sortTotalGenerationTime": "Tempo total de geração (maior primeiro)", + "undated": "Sem data" + }, + "jobMenu": { + "addToCurrentWorkflow": "Adicionar ao fluxo de trabalho atual", + "cancelJob": "Cancelar tarefa", + "copyErrorMessage": "Copiar mensagem de erro", + "copyJobId": "Copiar ID da tarefa", + "delete": "Excluir", + "deleteAsset": "Excluir ativo", + "download": "Baixar", + "exportWorkflow": "Exportar fluxo de trabalho", + "inspectAsset": "Inspecionar ativo", + "openAsWorkflowNewTab": "Abrir como fluxo de trabalho em nova aba", + "openWorkflowNewTab": "Abrir fluxo de trabalho em nova aba", + "removeJob": "Remover tarefa", + "reportError": "Reportar erro" + }, + "toggleJobHistory": "Alternar histórico de tarefas" + }, + "releaseToast": { + "description": "Confira as últimas melhorias e recursos nesta atualização.", + "newVersionAvailable": "Nova atualização disponível!", + "skip": "Pular", + "update": "Atualizar", + "whatsNew": "Veja as novidades" + }, + "rightSidePanel": { + "addFavorite": "Favoritar", + "advancedInputs": "ENTRADAS AVANÇADAS", + "bypass": "Ignorar", + "color": "Cor do nó", + "fallbackGroupTitle": "Grupo", + "fallbackNodeTitle": "Nó", + "favorites": "ENTRADAS FAVORITAS", + "favoritesNone": "NENHUMA ENTRADA FAVORITA", + "favoritesNoneDesc": "Entradas que você favoritar aparecerão aqui", + "favoritesNoneTooltip": "Marque widgets com estrela para acessá-los rapidamente sem selecionar nós", + "globalSettings": { + "canvas": "CANVAS", + "connectionLinks": "CONEXÕES", + "gridSpacing": "Espaçamento da grade", + "linkShape": "Formato do link", + "nodes": "NÓS", + "nodes2": "Nós 2.0", + "searchPlaceholder": "Buscar configurações rápidas...", + "showAdvanced": "Mostrar parâmetros avançados", + "showAdvancedTooltip": "Esta é uma configuração importante que, quando ativada, revela todos os parâmetros avançados dos nós", + "showConnectedLinks": "Mostrar links conectados", + "showInfoBadges": "Mostrar selos de informação", + "showToolbox": "Mostrar caixa de ferramentas ao selecionar", + "snapNodesToGrid": "Alinhar nós à grade", + "title": "Configurações Globais", + "viewAllSettings": "Ver todas as configurações" + }, + "groupSettings": "Configurações de Grupo", + "groups": "Grupos", + "hideAdvancedInputsButton": "Ocultar entradas avançadas", + "hideInput": "Ocultar entrada", + "info": "Informações", + "inputs": "ENTRADAS", + "inputsNone": "SEM ENTRADAS", + "inputsNoneTooltip": "O nó não possui entradas", + "locateNode": "Localizar nó no canvas", + "mute": "Silenciar", + "noSelection": "Selecione um nó para ver suas propriedades e informações.", + "nodeState": "Estado do nó", + "nodes": "Nós", + "nodesNoneDesc": "NENHUM NÓ", + "noneSearchDesc": "Nenhum item corresponde à sua busca", + "normal": "Normal", + "parameters": "Parâmetros", + "pinned": "Fixado", + "properties": "Propriedades", + "removeFavorite": "Desfavoritar", + "settings": "Configurações", + "showAdvancedInputsButton": "Mostrar entradas avançadas", + "showInput": "Mostrar entrada", + "title": "Nenhum nó selecionado | 1 nó selecionado | {count} nós selecionados", + "togglePanel": "Alternar painel de propriedades", + "workflowOverview": "Visão geral do fluxo de trabalho" + }, + "selectionToolbox": { + "Bypass Group Nodes": "Ignorar Nós de Grupo", + "Set Group Nodes to Always": "Definir Nós de Grupo para Sempre", + "Set Group Nodes to Never": "Definir Nós de Grupo para Nunca", + "executeButton": { + "disabledTooltip": "Nenhum nó de saída selecionado", + "tooltip": "Executar para os nós de saída selecionados (destacados com borda laranja)" + } + }, + "serverConfig": { + "modifiedConfigs": "Você modificou as seguintes configurações do servidor. Reinicie para aplicar as alterações.", + "restart": "Reiniciar", + "restartRequiredToastDetail": "Reinicie o aplicativo para aplicar as alterações de configuração do servidor.", + "restartRequiredToastSummary": "Reinicialização necessária", + "revertChanges": "Reverter alterações" + }, + "serverConfigCategories": { + "Attention": "Attention", + "CUDA": "CUDA", + "Cache": "Cache", + "Directories": "Diretórios", + "General": "Geral", + "Inference": "Inferência", + "Memory": "Memória", + "Network": "Rede", + "Preview": "Pré-visualização" + }, + "serverConfigItems": { + "cache-classic": { + "name": "Usar sistema de cache clássico" + }, + "cache-lru": { + "name": "Usar cache LRU com um máximo de N resultados de nós em cache.", + "tooltip": "Pode usar mais RAM/VRAM." + }, + "cpu-vae": { + "name": "Executar VAE na CPU" + }, + "cross-attention-method": { + "name": "Método de cross attention" + }, + "cuda-device": { + "name": "Índice do dispositivo CUDA a ser usado" + }, + "cuda-malloc": { + "name": "Usar CUDA malloc para alocação de memória" + }, + "default-hashing-function": { + "name": "Função de hash padrão para arquivos de modelo" + }, + "deterministic": { + "name": "Fazer o pytorch usar algoritmos determinísticos mais lentos quando possível.", + "tooltip": "Observe que isso pode não tornar as imagens determinísticas em todos os casos." + }, + "directml": { + "name": "Índice do dispositivo DirectML" + }, + "disable-all-custom-nodes": { + "name": "Desativar carregamento de todos os nós personalizados." + }, + "disable-ipex-optimize": { + "name": "Desativar otimização IPEX" + }, + "disable-metadata": { + "name": "Desativar salvamento de metadados do prompt nos arquivos." + }, + "disable-smart-memory": { + "name": "Desativar gerenciamento inteligente de memória", + "tooltip": "Força o ComfyUI a descarregar agressivamente para a RAM comum em vez de manter modelos na VRAM quando possível." + }, + "disable-xformers": { + "name": "Desativar otimização xFormers" + }, + "dont-print-server": { + "name": "Não imprimir saída do servidor no console." + }, + "dont-upcast-attention": { + "name": "Prevenir upcast de attention" + }, + "enable-cors-header": { + "name": "Ativar cabeçalho CORS: Use \"*\" para todas as origens ou especifique o domínio" + }, + "enable-manager-legacy-ui": { + "name": "Usar interface Manager legada", + "tooltip": "Usa a interface ComfyUI-Manager legada em vez da nova interface." + }, + "fast": { + "name": "Ativar algumas otimizações não testadas e que podem deteriorar a qualidade." + }, + "force-channels-last": { + "name": "Forçar formato de memória channels-last" + }, + "force-upcast-attention": { + "name": "Forçar upcast de attention" + }, + "global-precision": { + "name": "Precisão global de ponto flutuante", + "tooltip": "Precisão global de ponto flutuante" + }, + "input-directory": { + "name": "Diretório de entrada" + }, + "listen": { + "name": "Host: O endereço IP para escutar" + }, + "log-level": { + "name": "Nível de verbosidade do log" + }, + "max-upload-size": { + "name": "Tamanho máximo de upload (MB)" + }, + "output-directory": { + "name": "Diretório de saída" + }, + "port": { + "name": "Porta: A porta para escutar" + }, + "preview-method": { + "name": "Método usado para pré-visualizações latentes" + }, + "preview-size": { + "name": "Tamanho das imagens de pré-visualização" + }, + "reserve-vram": { + "name": "VRAM reservada (GB)", + "tooltip": "Defina a quantidade de VRAM em GB que você deseja reservar para uso do seu sistema operacional/outros softwares. Por padrão, uma quantidade é reservada dependendo do seu sistema operacional." + }, + "text-encoder-precision": { + "name": "Precisão do codificador de texto", + "tooltip": "Precisão do codificador de texto" + }, + "tls-certfile": { + "name": "Arquivo de Certificado TLS: Caminho para o arquivo de certificado TLS para HTTPS" + }, + "tls-keyfile": { + "name": "Arquivo de Chave TLS: Caminho para o arquivo de chave TLS para HTTPS" + }, + "unet-precision": { + "name": "Precisão do UNET", + "tooltip": "Precisão do UNET" + }, + "vae-precision": { + "name": "Precisão do VAE", + "tooltip": "Precisão do VAE" + }, + "vram-management": { + "name": "Modo de gerenciamento de VRAM" + } + }, + "serverStart": { + "copyAllTooltip": "Copiar tudo", + "copySelectionTooltip": "Copiar seleção", + "errorMessage": "Não foi possível iniciar o ComfyUI Desktop", + "installation": { + "title": "Instalando ComfyUI" + }, + "openLogs": "Abrir logs", + "process": { + "error": "Não foi possível iniciar o ComfyUI Desktop", + "initial-state": "Carregando...", + "python-setup": "Configurando ambiente Python...", + "ready": "Carregando interface humana", + "starting-server": "Iniciando servidor ComfyUI..." + }, + "reportIssue": "Reportar problema", + "showTerminal": "Mostrar terminal", + "title": "Iniciando ComfyUI", + "troubleshoot": "Solucionar problemas" + }, + "settingsCategories": { + "3D": "3D", + "3DViewer": "Visualizador 3D", + "API Nodes": "Nós de API", + "About": "Sobre", + "Appearance": "Aparência", + "BrushAdjustment": "Ajuste do Pincel", + "Camera": "Câmera", + "Canvas": "Canvas", + "Canvas Navigation": "Navegação no Canvas", + "ColorPalette": "Paleta de Cores", + "Comfy": "Comfy", + "Comfy-Desktop": "Comfy-Desktop", + "ContextMenu": "Menu de Contexto", + "Credits": "Créditos", + "CustomColorPalettes": "Paletas de Cores Personalizadas", + "DevMode": "Modo Desenvolvedor", + "EditTokenWeight": "Editar Peso do Token", + "Execution": "Execução", + "Extension": "Extensão", + "General": "Geral", + "Graph": "Grafo", + "Group": "Grupo", + "Keybinding": "Atalho de Teclado", + "Light": "Luz", + "Link": "Link", + "LinkRelease": "Soltar Link", + "LiteGraph": "Lite Graph", + "Load 3D": "Carregar 3D & Animação", + "Locale": "Idioma", + "Mask Editor": "Editor de Máscara", + "Menu": "Menu", + "ModelLibrary": "Biblioteca de Modelos", + "Node": "Nó", + "Node Search Box": "Caixa de Pesquisa de Nós", + "Node Widget": "Widget de Nó", + "NodeLibrary": "Biblioteca de Nós", + "Nodes 2_0": "Nodes 2.0", + "Notification Preferences": "Preferências de Notificação", + "Other": "Outros", + "PLY": "PLY", + "PlanCredits": "Plano & Créditos", + "Pointer": "Ponteiro", + "Queue": "Fila", + "QueueButton": "Botão de Fila", + "Reroute": "Redirecionar", + "RerouteBeta": "Redirecionar Beta", + "Scene": "Cena", + "Server": "Servidor", + "Server-Config": "Configuração do Servidor", + "Settings": "Configurações", + "Sidebar": "Barra Lateral", + "Tree Explorer": "Explorador de Árvore", + "UV": "UV", + "User": "Usuário", + "Validation": "Validação", + "Vue Nodes": "Nodes 2.0", + "VueNodes": "Nodes 2.0", + "Window": "Janela", + "Workflow": "Fluxo de Trabalho", + "Workspace": "Espaço de trabalho" + }, + "shape": { + "CARD": "Cartão", + "arrow": "Seta", + "box": "Caixa", + "circle": "Círculo", + "default": "Padrão", + "round": "Arredondado" + }, + "shortcuts": { + "essentials": "Essencial", + "keyboardShortcuts": "Atalhos de Teclado", + "manageShortcuts": "Gerenciar Atalhos", + "noKeybinding": "Sem atalho", + "shortcuts": "Atalhos", + "subcategories": { + "node": "Nó", + "panelControls": "Controles do Painel", + "queue": "Fila", + "view": "Visualização", + "workflow": "Fluxo de Trabalho" + }, + "viewControls": "Controles de Visualização" + }, + "sideToolbar": { + "activeJobStatus": "Tarefa ativa: {status}", + "assets": "Ativos", + "backToAssets": "Voltar para todos os ativos", + "browseTemplates": "Explorar modelos de exemplo", + "downloads": "Downloads", + "generatedAssetsHeader": "Ativos gerados", + "helpCenter": "Central de Ajuda", + "importedAssetsHeader": "Ativos importados", + "labels": { + "assets": "Ativos", + "console": "Console", + "generated": "Gerado", + "imported": "Importado", + "menu": "Menu", + "models": "Modelos", + "nodes": "Nós", + "queue": "Fila", + "templates": "Modelos", + "workflows": "Workflows" + }, + "logout": "Sair", + "mediaAssets": { + "filter3D": "3D", + "filterAudio": "Áudio", + "filterImage": "Imagem", + "filterText": "Texto", + "filterVideo": "Vídeo", + "sortFastestFirst": "Tempo de geração (menor primeiro)", + "sortLongestFirst": "Tempo de geração (maior primeiro)", + "sortNewestFirst": "Mais recentes primeiro", + "sortOldestFirst": "Mais antigos primeiro", + "title": "Ativos de Mídia" + }, + "modelLibrary": "Biblioteca de modelos", + "newBlankWorkflow": "Criar um novo workflow em branco", + "noFilesFound": "Nenhum arquivo encontrado", + "noFilesFoundMessage": "Envie arquivos ou gere conteúdo para vê-los aqui", + "noGeneratedFiles": "Nenhum arquivo gerado encontrado", + "noImportedFiles": "Nenhum arquivo importado encontrado", + "nodeLibrary": "Biblioteca de nós", + "nodeLibraryTab": { + "groupBy": "Agrupar por", + "groupStrategies": { + "category": "Categoria", + "categoryDesc": "Agrupar por categoria do nó", + "module": "Módulo", + "moduleDesc": "Agrupar por origem do módulo", + "source": "Fonte", + "sourceDesc": "Agrupar por tipo de fonte (Core, Custom, API)" + }, + "resetView": "Restaurar visualização padrão", + "sortBy": { + "alphabetical": "Alfabética", + "alphabeticalDesc": "Ordenar alfabeticamente dentro dos grupos", + "original": "Original", + "originalDesc": "Manter ordem original" + }, + "sortMode": "Modo de ordenação" + }, + "openWorkflow": "Abrir workflow no sistema de arquivos local", + "queue": "Fila", + "queueProgressOverlay": { + "activeJobs": "{count} trabalho ativo | {count} trabalhos ativos", + "activeJobsShort": "{count} ativo(s) | {count} ativo(s)", + "activeJobsSuffix": "trabalhos ativos", + "cancelJobTooltip": "Cancelar trabalho", + "clearHistory": "Limpar histórico da fila de trabalhos", + "clearHistoryDialogAssetsNote": "Os ativos gerados por esses trabalhos não serão excluídos e sempre poderão ser visualizados no painel de ativos.", + "clearHistoryDialogDescription": "Todos os trabalhos finalizados ou com falha abaixo serão removidos deste painel de fila de trabalhos.", + "clearHistoryDialogTitle": "Limpar o histórico da fila de trabalhos?", + "clearQueueTooltip": "Limpar fila", + "clearQueued": "Limpar fila", + "colonPercent": ": {percent}", + "currentNode": "Nó atual:", + "expandCollapsedQueue": "Expandir fila de trabalhos", + "filterAllWorkflows": "Todos os workflows", + "filterBy": "Filtrar por", + "filterCurrentWorkflow": "Workflow atual", + "filterJobs": "Filtrar trabalhos", + "interruptAll": "Interromper todos os trabalhos em execução", + "jobQueue": "Fila de trabalhos", + "jobsCompleted": "{count} trabalho concluído | {count} trabalhos concluídos", + "jobsFailed": "{count} trabalho falhou | {count} trabalhos falharam", + "moreOptions": "Mais opções", + "noActiveJobs": "Nenhum trabalho ativo", + "preview": "Pré-visualização", + "queuedSuffix": "na fila", + "running": "executando", + "showAssets": "Mostrar ativos", + "showAssetsPanel": "Mostrar painel de ativos", + "sortBy": "Ordenar por", + "sortJobs": "Ordenar trabalhos", + "stubClipTextEncode": "CLIP Text Encode:", + "title": "Progresso da Fila", + "total": "Total: {percent}", + "viewAllJobs": "Ver todos os trabalhos", + "viewGrid": "Visualização em grade", + "viewJobHistory": "Ver histórico de trabalhos", + "viewList": "Visualização em lista" + }, + "searchAssets": "Buscar ativos", + "sidebar": "Barra lateral", + "templates": "Modelos", + "themeToggle": "Alternar tema", + "workflowTab": { + "confirmDelete": "Tem certeza de que deseja excluir este workflow?", + "confirmDeleteTitle": "Excluir workflow?", + "confirmOverwrite": "O arquivo abaixo já existe. Deseja sobrescrevê-lo?", + "confirmOverwriteTitle": "Sobrescrever arquivo existente?", + "deleteFailed": "Falha ao tentar excluir o workflow.", + "deleteFailedTitle": "Falha ao excluir", + "deleted": "Workflow excluído", + "dirtyClose": "Os arquivos abaixo foram alterados. Deseja salvá-los antes de fechar?", + "dirtyCloseHint": "Segure Shift para fechar sem perguntar", + "dirtyCloseTitle": "Salvar alterações?", + "workflowTreeType": { + "bookmarks": "Favoritos", + "browse": "Explorar", + "open": "Abrir" + } + }, + "workflows": "Workflows" + }, + "subgraphStore": { + "blueprintName": "Nome do subgrafo", + "confirmDelete": "Esta ação removerá permanentemente o blueprint da sua biblioteca", + "confirmDeleteTitle": "Excluir blueprint?", + "hidden": "Parâmetros ocultos/aninhados", + "hideAll": "Ocultar tudo", + "loadFailure": "Falha ao carregar blueprints de subgrafo", + "overwriteBlueprint": "Salvar irá sobrescrever o blueprint atual com suas alterações", + "overwriteBlueprintTitle": "Sobrescrever blueprint existente?", + "promoteOutsideSubgraph": "Não é possível promover o widget fora do subgrafo", + "publish": "Publicar Subgrafo", + "publishSuccess": "Salvo na Biblioteca de Nós", + "publishSuccessMessage": "Você pode encontrar seu blueprint de subgrafo na biblioteca de nós em \"Blueprints de Subgrafo\"", + "saveBlueprint": "Salvar Subgrafo na Biblioteca", + "showAll": "Mostrar tudo", + "showRecommended": "Mostrar widgets recomendados", + "shown": "Exibido no nó" + }, + "subscription": { + "addApiCredits": "Adicionar créditos de API", + "addCredits": "Adicionar créditos", + "addCreditsLabel": "Adicione mais créditos quando quiser", + "benefits": { + "benefit1": "$10 em créditos mensais para Partner Nodes — recarregue quando necessário", + "benefit2": "Até 30 min de execução por tarefa" + }, + "beta": "BETA", + "billedMonthly": "Cobrado mensalmente", + "billedYearly": "{total} Cobrado anualmente", + "billingComingSoon": { + "message": "A cobrança para equipes estará disponível em breve. Você poderá assinar um plano para seu workspace com preço por usuário. Fique atento para novidades.", + "title": "Em breve" + }, + "cancelSubscription": "Cancelar assinatura", + "changeTo": "Mudar para {plan}", + "comfyCloud": "Comfy Cloud", + "comfyCloudLogo": "Logo do Comfy Cloud", + "contactOwnerToSubscribe": "Entre em contato com o proprietário do espaço de trabalho para assinar", + "contactUs": "Fale conosco", + "creditsRemainingThisMonth": "Créditos restantes neste mês", + "creditsRemainingThisYear": "Créditos restantes neste ano", + "creditsYouveAdded": "Créditos que você adicionou", + "currentPlan": "Plano Atual", + "customLoRAsLabel": "Importe seus próprios LoRAs", + "description": "Escolha o melhor plano para você", + "expiresDate": "Expira em {date}", + "gpuLabel": "RTX 6000 Pro (96GB VRAM)", + "haveQuestions": "Tem dúvidas ou interesse em soluções empresariais?", + "invoiceHistory": "Histórico de faturas", + "learnMore": "Saiba mais", + "managePayment": "Gerenciar pagamento", + "managePlan": "Gerenciar plano", + "manageSubscription": "Gerenciar assinatura", + "maxDuration": { + "creator": "30 min", + "founder": "30 min", + "pro": "1 h", + "standard": "30 min" + }, + "maxDurationLabel": "Duração máxima de cada execução de workflow", + "messageSupport": "Falar com o suporte", + "monthly": "Mensal", + "monthlyBonusDescription": "Bônus mensal de créditos", + "monthlyCreditsInfo": "Esses créditos são renovados mensalmente e não acumulam", + "monthlyCreditsLabel": "Créditos mensais", + "monthlyCreditsRollover": "Esses créditos serão transferidos para o próximo mês", + "mostPopular": "Mais popular", + "nextBillingCycle": "próximo ciclo de cobrança", + "partnerNodesBalance": "Saldo de Créditos \"Partner Nodes\"", + "partnerNodesCredits": "Preços dos Partner Nodes", + "partnerNodesDescription": "Para executar modelos comerciais/proprietários", + "perMonth": "/ mês", + "plansAndPricing": "Planos e preços", + "prepaidCreditsInfo": "Créditos pré-pagos expiram após 1 ano da data de compra.", + "prepaidDescription": "Créditos pré-pagos", + "renewsDate": "Renova em {date}", + "required": { + "subscribe": "Assinar", + "title": "Assinar", + "waitingForSubscription": "Conclua sua assinatura na nova aba. Detectaremos automaticamente quando você terminar!" + }, + "subscribeNow": "Assine Agora", + "subscribeTo": "Assinar {plan}", + "subscribeToComfyCloud": "Assine o Comfy Cloud", + "subscribeToRun": "Assinar", + "subscribeToRunFull": "Assine para Executar", + "subscriptionRequiredMessage": "Uma assinatura é necessária para que os membros executem fluxos de trabalho na Nuvem", + "tierNameYearly": "{name} Anual", + "tiers": { + "creator": { + "name": "Criador" + }, + "founder": { + "name": "Edição do Fundador" + }, + "pro": { + "name": "Pro" + }, + "standard": { + "name": "Padrão" + } + }, + "title": "Assinatura", + "titleUnsubscribed": "Assine o Comfy Cloud", + "totalCredits": "Total de créditos", + "upgrade": "ATUALIZAR", + "upgradePlan": "Atualizar Plano", + "upgradeTo": "Atualizar para {plan}", + "usdPerMonth": "USD / mês", + "videoEstimateExplanation": "Essas estimativas são baseadas no template Wan 2.2 Image-to-Video usando as configurações padrão (5 segundos, 640x640, 16fps, amostragem de 4 etapas).", + "videoEstimateHelp": "Mais detalhes sobre este template", + "videoEstimateLabel": "Quantidade aprox. de vídeos de 5s gerados com o template Wan 2.2 Image-to-Video", + "videoEstimateTryTemplate": "Experimente este template", + "videoTemplateBasedCredits": "Vídeos gerados com Wan 2.2 Image to Video", + "viewEnterprise": "Ver soluções empresariais", + "viewMoreDetails": "Ver mais detalhes", + "viewMoreDetailsPlans": "Veja mais detalhes sobre planos e preços", + "viewUsageHistory": "Ver histórico de uso", + "workspaceNotSubscribed": "Este espaço de trabalho não possui uma assinatura", + "yearly": "Anual", + "yearlyCreditsLabel": "Total de créditos anuais", + "yearlyDiscount": "20% DE DESCONTO", + "yourPlanIncludes": "Seu plano inclui:" + }, + "tabMenu": { + "addToBookmarks": "Adicionar aos favoritos", + "closeOtherTabs": "Fechar outras abas", + "closeTab": "Fechar aba", + "closeTabsToLeft": "Fechar abas à esquerda", + "closeTabsToRight": "Fechar abas à direita", + "duplicateTab": "Duplicar aba", + "removeFromBookmarks": "Remover dos favoritos" + }, + "templateWidgets": { + "sort": { + "searchPlaceholder": "Buscar..." + } + }, + "templateWorkflows": { + "activeFilters": "Filtros:", + "allTemplates": "Todos os Modelos", + "categories": "Categorias", + "category": { + "3D": "3D", + "All": "Todos os Modelos", + "Area Composition": "Composição de Área", + "Audio": "Áudio", + "Basics": "Básico", + "ComfyUI Examples": "Exemplos ComfyUI", + "ControlNet": "ControlNet", + "Custom Nodes": "Nós Personalizados", + "Extensions": "Extensões", + "Flux": "Flux", + "Generation Type": "Tipo de Geração", + "GettingStarted": "Primeiros Passos", + "Image": "Imagem", + "Image API": "API de Imagem", + "LLM API": "API de LLM", + "LLMs": "LLMs", + "Partner Nodes": "Nós Parceiros", + "Upscaling": "Aprimoramento", + "Video": "Vídeo", + "Video API": "API de Vídeo" + }, + "error": { + "templateNotFound": "Modelo \"{templateName}\" não encontrado" + }, + "licenseFilter": "Licença", + "loading": "Carregando modelos...", + "loadingMore": "Carregando mais modelos...", + "modelFilter": "Filtro de Modelo", + "modelsSelected": "{count} Modelos", + "noResults": "Nenhum modelo encontrado", + "noResultsHint": "Tente ajustar sua busca ou filtros", + "resetFilters": "Limpar Filtros", + "resultsCount": "Mostrando {count} de {total} modelos", + "runsOnFilter": "Executa em", + "runsOnSelected": "{count} Execuções em", + "searchPlaceholder": "Buscar modelos...", + "sort": { + "alphabetical": "A → Z", + "default": "Padrão", + "modelSizeLowToHigh": "Tamanho do Modelo (Menor para Maior)", + "newest": "Mais Recentes", + "popular": "Popular", + "recommended": "Recomendado", + "searchPlaceholder": "Buscar...", + "vramLowToHigh": "Uso de VRAM (Menor para Maior)" + }, + "sorting": "Ordenar por", + "title": "Comece com um Modelo", + "useCaseFilter": "Tarefas", + "useCasesSelected": "{count} Casos de Uso" + }, + "toastMessages": { + "cannotCreateSubgraph": "Não é possível criar subgrafo", + "couldNotDetermineFileType": "Não foi possível determinar o tipo de arquivo", + "dropFileError": "Não foi possível processar o item arrastado: {error}", + "emptyCanvas": "Tela vazia", + "errorCopyImage": "Erro ao copiar imagem: {error}", + "errorLoadingModel": "Erro ao carregar o modelo", + "errorSaveSetting": "Erro ao salvar configuração {id}: {err}", + "exportSuccess": "Modelo exportado com sucesso como {format}", + "failedExecutionPathResolution": "Não foi possível resolver o caminho para os nós selecionados", + "failedToAccessBillingPortal": "Falha ao acessar o portal de cobrança: {error}", + "failedToApplyTexture": "Falha ao aplicar textura", + "failedToConvertToSubgraph": "Falha ao converter itens para subgrafo", + "failedToCreateCustomer": "Falha ao criar cliente: {error}", + "failedToDownloadFile": "Falha ao baixar o arquivo", + "failedToExportModel": "Falha ao exportar o modelo como {format}", + "failedToFetchBalance": "Falha ao buscar saldo: {error}", + "failedToFetchLogs": "Falha ao buscar logs do servidor", + "failedToFetchSubscription": "Falha ao buscar status da assinatura: {error}", + "failedToInitializeLoad3dViewer": "Falha ao inicializar o Visualizador 3D", + "failedToInitiateCreditPurchase": "Falha ao iniciar compra de créditos: {error}", + "failedToInitiateSubscription": "Falha ao iniciar assinatura: {error}", + "failedToLoadBackgroundImage": "Falha ao carregar imagem de fundo", + "failedToLoadModel": "Falha ao carregar modelo 3D", + "failedToPurchaseCredits": "Falha ao comprar créditos: {error}", + "failedToQueue": "Falha ao enfileirar", + "failedToToggleCamera": "Falha ao alternar câmera", + "failedToToggleGrid": "Falha ao alternar grade", + "failedToUpdateBackgroundColor": "Falha ao atualizar cor de fundo", + "failedToUpdateBackgroundImage": "Falha ao atualizar imagem de fundo", + "failedToUpdateBackgroundRenderMode": "Falha ao atualizar modo de renderização de fundo para {mode}", + "failedToUpdateEdgeThreshold": "Falha ao atualizar limite de borda", + "failedToUpdateFOV": "Falha ao atualizar campo de visão", + "failedToUpdateLightIntensity": "Falha ao atualizar intensidade da luz", + "failedToUpdateMaterialMode": "Falha ao atualizar modo de material", + "failedToUpdateUpDirection": "Falha ao atualizar direção para cima", + "failedToUploadBackgroundImage": "Falha ao fazer upload da imagem de fundo", + "fileLoadError": "Não foi possível encontrar o fluxo de trabalho em {fileName}", + "fileTooLarge": "Arquivo muito grande ({size} MB). O tamanho máximo suportado é {maxSize} MB", + "fileUploadFailed": "Falha no upload do arquivo", + "interrupted": "Execução interrompida", + "legacyMaskEditorDeprecated": "O editor de máscara legado está obsoleto e será removido em breve.", + "migrateToLitegraphReroute": "Nós de redirecionamento serão removidos em versões futuras. Clique para migrar para o redirecionamento nativo do litegraph.", + "modelLoadedSuccessfully": "Modelo 3D carregado com sucesso", + "no3dScene": "Nenhuma cena 3D para aplicar textura", + "no3dSceneToExport": "Nenhuma cena 3D para exportar", + "noTemplatesToExport": "Nenhum template para exportar", + "nodeDefinitionsUpdated": "Definições de nós atualizadas", + "nothingSelected": "Nada selecionado", + "nothingToGroup": "Nada para agrupar", + "nothingToQueue": "Nada para enfileirar", + "pendingTasksDeleted": "Tarefas pendentes excluídas", + "pleaseSelectNodesToGroup": "Por favor, selecione os nós (ou outros grupos) para criar um grupo", + "pleaseSelectOutputNodes": "Por favor, selecione os nós de saída", + "unableToGetModelFilePath": "Não foi possível obter o caminho do arquivo do modelo", + "unauthorizedDomain": "Seu domínio {domain} não está autorizado a usar este serviço. Por favor, entre em contato com {email} para adicionar seu domínio à lista de permissões.", + "updateRequested": "Atualização solicitada", + "useApiKeyTip": "Dica: Não consegue acessar o login normal? Use a opção Comfy API Key.", + "userNotAuthenticated": "Usuário não autenticado" + }, + "userSelect": { + "enterUsername": "Digite um nome de usuário", + "existingUser": "Usuário existente", + "newUser": "Novo usuário", + "next": "Próximo", + "selectUser": "Selecione um usuário" + }, + "userSettings": { + "accountSettings": "Configurações da conta", + "email": "E-mail", + "name": "Nome", + "notSet": "Não definido", + "provider": "Provedor de login", + "title": "Configurações da Minha Conta", + "updatePassword": "Atualizar senha", + "workspaceSettings": "Configurações do espaço de trabalho" + }, + "validation": { + "descriptionRequired": "Descrição é obrigatória", + "invalidEmail": "Endereço de e-mail inválido", + "length": "Deve ter {length} caracteres", + "maxLength": "Deve ter no máximo {length} caracteres", + "minLength": "Deve ter pelo menos {length} caracteres", + "password": { + "lowercase": "Deve conter pelo menos uma letra minúscula", + "match": "As senhas devem coincidir", + "minLength": "Deve ter entre 8 e 32 caracteres", + "number": "Deve conter pelo menos um número", + "requirements": "Requisitos da senha", + "special": "Deve conter pelo menos um caractere especial", + "uppercase": "Deve conter pelo menos uma letra maiúscula" + }, + "personalDataConsentRequired": "Você deve concordar com o processamento dos seus dados pessoais.", + "prefix": "Deve começar com {prefix}", + "required": "Obrigatório" + }, + "versionMismatchWarning": { + "dismiss": "Dispensar", + "frontendNewer": "A versão do frontend {frontendVersion} pode não ser compatível com a versão do backend {backendVersion}.", + "frontendOutdated": "A versão do frontend {frontendVersion} está desatualizada. O backend requer a versão {requiredVersion} ou superior.", + "title": "Aviso de Compatibilidade de Versão", + "updateFrontend": "Atualizar Frontend" + }, + "vueNodesBanner": { + "desc": "– Fluxos de trabalho mais flexíveis, novos widgets poderosos, feito para extensibilidade", + "title": "Apresentando Nodes 2.0", + "tryItOut": "Experimente" + }, + "vueNodesMigration": { + "button": "Voltar", + "message": "Prefere o design antigo?" + }, + "vueNodesMigrationMainMenu": { + "message": "Volte para Nodes 2.0 a qualquer momento pelo menu principal." + }, + "welcome": { + "getStarted": "Começar", + "title": "Bem-vindo ao ComfyUI" + }, + "whatsNewPopup": { + "later": "Depois", + "learnMore": "Saiba mais", + "noReleaseNotes": "Nenhuma nota de versão disponível." + }, + "widgetFileUpload": { + "browseFiles": "Procurar Arquivos", + "dropPrompt": "Solte seu arquivo ou" + }, + "widgets": { + "node2only": "Apenas Node 2.0", + "selectModel": "Selecionar modelo", + "uploadSelect": { + "placeholder": "Selecionar...", + "placeholderAudio": "Selecionar áudio...", + "placeholderImage": "Selecionar imagem...", + "placeholderModel": "Selecionar modelo...", + "placeholderUnknown": "Selecionar mídia...", + "placeholderVideo": "Selecionar vídeo..." + }, + "valueControl": { + "decrement": "Decrementar Valor", + "decrementDesc": "Subtrai 1 do valor ou seleciona a opção anterior", + "editSettings": "Editar configurações de controle", + "fixed": "Valor Fixo", + "fixedDesc": "Mantém o valor inalterado", + "header": { + "after": "DEPOIS", + "before": "ANTES", + "postfix": "de executar o fluxo de trabalho:", + "prefix": "Atualizar o valor automaticamente" + }, + "increment": "Incrementar Valor", + "incrementDesc": "Adiciona 1 ao valor ou seleciona a próxima opção", + "linkToGlobal": "Vincular a", + "linkToGlobalDesc": "Valor único vinculado à configuração de controle do Valor Global", + "linkToGlobalSeed": "Valor Global", + "randomize": "Valor Aleatório", + "randomizeDesc": "Embaralha o valor aleatoriamente após cada geração" + } + }, + "workflowService": { + "enterFilename": "Digite o nome do arquivo", + "exportWorkflow": "Exportar Fluxo de Trabalho", + "saveWorkflow": "Salvar fluxo de trabalho" + }, + "workspace": { + "addedToWorkspace": "Você foi adicionado ao {workspaceName}", + "inviteAccepted": "Convite aceito", + "inviteFailed": "Falha ao aceitar convite", + "unsavedChanges": { + "message": "Você tem alterações não salvas. Deseja descartá-las e trocar de espaço de trabalho?", + "title": "Alterações não salvas" + } + }, + "workspaceAuth": { + "errors": { + "accessDenied": "Você não tem acesso a este espaço de trabalho", + "invalidFirebaseToken": "Falha na autenticação. Por favor, tente fazer login novamente.", + "notAuthenticated": "Você precisa estar logado para acessar os espaços de trabalho", + "tokenExchangeFailed": "Falha ao autenticar com o espaço de trabalho: {error}", + "workspaceNotFound": "Espaço de trabalho não encontrado" + } + }, + "workspacePanel": { + "createWorkspaceDialog": { + "create": "Criar", + "message": "Espaços de trabalho permitem que membros compartilhem um único saldo de créditos. Você se tornará o proprietário após criar este.", + "nameLabel": "Nome do espaço de trabalho*", + "namePlaceholder": "Digite o nome do espaço de trabalho", + "title": "Criar um novo espaço de trabalho" + }, + "dashboard": { + "placeholder": "Configurações do workspace do painel" + }, + "deleteDialog": { + "message": "Quaisquer créditos não utilizados ou ativos não salvos serão perdidos. Esta ação não pode ser desfeita.", + "messageWithName": "Excluir \"{name}\"? Quaisquer créditos não utilizados ou ativos não salvos serão perdidos. Esta ação não pode ser desfeita.", + "title": "Excluir este espaço de trabalho?" + }, + "editWorkspaceDialog": { + "nameLabel": "Nome do espaço de trabalho", + "save": "Salvar", + "title": "Editar detalhes do espaço de trabalho" + }, + "invite": "Convidar", + "inviteLimitReached": "Você atingiu o limite máximo de 50 membros", + "inviteMember": "Convidar membro", + "inviteMemberDialog": { + "createLink": "Criar link", + "linkCopied": "Copiado", + "linkCopyFailed": "Falha ao copiar link", + "linkStep": { + "copyLink": "Copiar link", + "done": "Concluído", + "message": "Certifique-se de que a conta dela usa este e-mail.", + "title": "Envie este link para a pessoa" + }, + "message": "Crie um link de convite compartilhável para enviar a alguém", + "placeholder": "Digite o e-mail da pessoa", + "title": "Convidar uma pessoa para este workspace" + }, + "leaveDialog": { + "leave": "Sair", + "message": "Você não poderá entrar novamente a menos que entre em contato com o proprietário do espaço de trabalho.", + "title": "Sair deste espaço de trabalho?" + }, + "members": { + "actions": { + "copyLink": "Copiar link do convite", + "removeMember": "Remover membro", + "revokeInvite": "Revogar convite" + }, + "columns": { + "expiryDate": "Data de expiração", + "inviteDate": "Data do convite", + "joinDate": "Data de entrada" + }, + "createNewWorkspace": "crie um novo.", + "membersCount": "{count}/50 Membros", + "noInvites": "Nenhum convite pendente", + "noMembers": "Nenhum membro", + "pendingInvitesCount": "{count} convite pendente | {count} convites pendentes", + "personalWorkspaceMessage": "No momento, você não pode convidar outros membros para seu workspace pessoal. Para adicionar membros a um workspace,", + "tabs": { + "active": "Ativo", + "pendingCount": "Pendente ({count})" + } + }, + "menu": { + "deleteWorkspace": "Excluir espaço de trabalho", + "deleteWorkspaceDisabledTooltip": "Cancele a assinatura ativa do seu espaço de trabalho primeiro", + "editWorkspace": "Editar detalhes do espaço de trabalho", + "leaveWorkspace": "Sair do espaço de trabalho" + }, + "removeMemberDialog": { + "error": "Falha ao remover membro", + "message": "Este membro será removido do seu workspace. Os créditos utilizados por ele não serão reembolsados.", + "remove": "Remover membro", + "success": "Membro removido", + "title": "Remover este membro?" + }, + "revokeInviteDialog": { + "message": "Este membro não poderá mais entrar no seu workspace. O link de convite será invalidado.", + "revoke": "Desfazer convite", + "title": "Desfazer convite para esta pessoa?" + }, + "tabs": { + "dashboard": "Painel", + "membersCount": "Membros ({count})", + "planCredits": "Plano e Créditos" + }, + "toast": { + "failedToCreateWorkspace": "Falha ao criar o espaço de trabalho", + "failedToDeleteWorkspace": "Falha ao excluir o espaço de trabalho", + "failedToFetchWorkspaces": "Falha ao carregar workspaces", + "failedToLeaveWorkspace": "Falha ao sair do espaço de trabalho", + "failedToUpdateWorkspace": "Falha ao atualizar o espaço de trabalho", + "workspaceCreated": { + "message": "Assine um plano, convide colegas e comece a colaborar.", + "subscribe": "Assinar", + "title": "Workspace criado" + }, + "workspaceDeleted": { + "message": "O workspace foi excluído permanentemente.", + "title": "Workspace excluído" + }, + "workspaceLeft": { + "message": "Você saiu do workspace.", + "title": "Saiu do workspace" + }, + "workspaceUpdated": { + "message": "Os detalhes do espaço de trabalho foram salvos.", + "title": "Espaço de trabalho atualizado" + } + } + }, + "workspaceSwitcher": { + "createWorkspace": "Criar novo espaço de trabalho", + "maxWorkspacesReached": "Você só pode possuir 10 espaços de trabalho. Exclua um para criar um novo.", + "personal": "Pessoal", + "roleMember": "Membro", + "roleOwner": "Proprietário", + "subscribe": "Assinar", + "switchWorkspace": "Trocar espaço de trabalho" + }, + "zoomControls": { + "hideMinimap": "Ocultar Minimapa", + "label": "Controles de Zoom", + "showMinimap": "Mostrar Minimapa", + "zoomToFit": "Ajustar ao Espaço" + } +} diff --git a/src/locales/pt-BR/nodeDefs.json b/src/locales/pt-BR/nodeDefs.json new file mode 100644 index 000000000..159d951f6 --- /dev/null +++ b/src/locales/pt-BR/nodeDefs.json @@ -0,0 +1,16552 @@ +{ + "APG": { + "display_name": "Orientação Projetada Adaptativa", + "inputs": { + "eta": { + "name": "eta", + "tooltip": "Controla a escala do vetor de orientação paralela. Comportamento padrão do CFG com valor 1." + }, + "model": { + "name": "model" + }, + "momentum": { + "name": "momento", + "tooltip": "Controla uma média móvel da orientação durante a difusão, desativado com valor 0." + }, + "norm_threshold": { + "name": "limite_normalização", + "tooltip": "Normaliza o vetor de orientação para este valor, normalização desativada com valor 0." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AddNoise": { + "display_name": "AddNoise", + "inputs": { + "latent_image": { + "name": "imagem_latente" + }, + "model": { + "name": "model" + }, + "noise": { + "name": "ruído" + }, + "sigmas": { + "name": "sigmas" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AddTextPrefix": { + "display_name": "Adicionar Prefixo de Texto", + "inputs": { + "prefix": { + "name": "prefixo", + "tooltip": "Prefixo a ser adicionado." + }, + "texts": { + "name": "textos", + "tooltip": "Texto a ser processado." + } + }, + "outputs": { + "0": { + "name": "textos", + "tooltip": "Textos processados" + } + } + }, + "AddTextSuffix": { + "display_name": "Adicionar Sufixo de Texto", + "inputs": { + "suffix": { + "name": "sufixo", + "tooltip": "Sufixo a ser adicionado." + }, + "texts": { + "name": "textos", + "tooltip": "Texto a ser processado." + } + }, + "outputs": { + "0": { + "name": "textos", + "tooltip": "Textos processados" + } + } + }, + "AdjustBrightness": { + "display_name": "Ajustar Brilho", + "inputs": { + "factor": { + "name": "fator", + "tooltip": "Fator de brilho. 1.0 = sem alteração, <1.0 = mais escuro, >1.0 = mais claro." + }, + "images": { + "name": "imagens", + "tooltip": "Imagem a ser processada." + } + }, + "outputs": { + "0": { + "name": "imagens", + "tooltip": "Imagens processadas" + } + } + }, + "AdjustContrast": { + "display_name": "Ajustar Contraste", + "inputs": { + "factor": { + "name": "fator", + "tooltip": "Fator de contraste. 1.0 = sem alteração, <1.0 = menos contraste, >1.0 = mais contraste." + }, + "images": { + "name": "imagens", + "tooltip": "Imagem a ser processada." + } + }, + "outputs": { + "0": { + "name": "imagens", + "tooltip": "Imagens processadas" + } + } + }, + "AlignYourStepsScheduler": { + "display_name": "AlignYourStepsScheduler", + "inputs": { + "denoise": { + "name": "reduzir_ruído" + }, + "model_type": { + "name": "tipo_de_modelo" + }, + "steps": { + "name": "passos" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AudioAdjustVolume": { + "display_name": "Ajustar Volume do Áudio", + "inputs": { + "audio": { + "name": "áudio" + }, + "volume": { + "name": "volume", + "tooltip": "Ajuste de volume em decibéis (dB). 0 = sem alteração, +6 = dobra, -6 = metade, etc" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AudioConcat": { + "description": "Concatena o áudio1 ao áudio2 na direção especificada.", + "display_name": "Concatenação de Áudio", + "inputs": { + "audio1": { + "name": "audio1" + }, + "audio2": { + "name": "audio2" + }, + "direction": { + "name": "direção", + "tooltip": "Se o áudio2 será adicionado após ou antes do áudio1." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AudioEncoderEncode": { + "display_name": "Codificar com AudioEncoder", + "inputs": { + "audio": { + "name": "áudio" + }, + "audio_encoder": { + "name": "audio_encoder" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AudioEncoderLoader": { + "display_name": "Carregar AudioEncoder", + "inputs": { + "audio_encoder_name": { + "name": "audio_encoder_name" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AudioMerge": { + "description": "Combina duas faixas de áudio sobrepondo suas formas de onda.", + "display_name": "Mesclar Áudio", + "inputs": { + "audio1": { + "name": "audio1" + }, + "audio2": { + "name": "audio2" + }, + "merge_method": { + "name": "método_de_mesclagem", + "tooltip": "O método utilizado para combinar as formas de onda dos áudios." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BasicGuider": { + "display_name": "Guia Básico", + "inputs": { + "conditioning": { + "name": "condicionamento" + }, + "model": { + "name": "modelo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BasicScheduler": { + "display_name": "Agendador Básico", + "inputs": { + "denoise": { + "name": "reduzir_ruído" + }, + "model": { + "name": "modelo" + }, + "scheduler": { + "name": "agendador" + }, + "steps": { + "name": "etapas" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchImagesNode": { + "display_name": "Imagens em Lote", + "inputs": { + "images": { + "name": "imagens" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchLatentsNode": { + "display_name": "Latents em Lote", + "inputs": { + "latents": { + "name": "latents" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchMasksNode": { + "display_name": "Máscaras em Lote", + "inputs": { + "masks": { + "name": "máscaras" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BetaSamplingScheduler": { + "display_name": "Agendador de Amostragem Beta", + "inputs": { + "alpha": { + "name": "alfa" + }, + "beta": { + "name": "beta" + }, + "model": { + "name": "modelo" + }, + "steps": { + "name": "etapas" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BriaImageEditNode": { + "description": "Edite imagens usando o modelo mais recente da Bria", + "display_name": "Bria Image Edit", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "guidance_scale": { + "name": "escala_de_guia", + "tooltip": "Um valor mais alto faz com que a imagem siga o prompt mais de perto." + }, + "image": { + "name": "imagem" + }, + "mask": { + "name": "máscara", + "tooltip": "Se omitido, a edição será aplicada à imagem inteira." + }, + "model": { + "name": "modelo" + }, + "moderation": { + "name": "moderação", + "tooltip": "Configurações de moderação" + }, + "moderation_prompt_content_moderation": { + "name": "moderação_do_conteúdo_do_prompt" + }, + "moderation_visual_input_moderation": { + "name": "moderação_da_entrada_visual" + }, + "moderation_visual_output_moderation": { + "name": "moderação_da_saida_visual" + }, + "negative_prompt": { + "name": "prompt_negativo" + }, + "prompt": { + "name": "prompt", + "tooltip": "Instrução para editar a imagem" + }, + "seed": { + "name": "semente" + }, + "steps": { + "name": "passos" + }, + "structured_prompt": { + "name": "prompt_estruturado", + "tooltip": "Uma string contendo o prompt de edição estruturado em formato JSON. Use isso em vez do prompt comum para controle preciso e programático." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "prompt_estruturado", + "tooltip": null + } + } + }, + "ByteDanceFirstLastFrameNode": { + "description": "Gere vídeo usando um prompt e os quadros inicial e final.", + "display_name": "ByteDance Primeiro-Último-Frame para Vídeo", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "A proporção do vídeo de saída." + }, + "camera_fixed": { + "name": "camera_fixed", + "tooltip": "Especifica se a câmera deve ser fixa. O aplicativo adiciona uma instrução para fixar a câmera ao seu prompt, mas não garante o efeito real." + }, + "control_after_generate": { + "name": "control after generate" + }, + "duration": { + "name": "duration", + "tooltip": "A duração do vídeo de saída em segundos." + }, + "first_frame": { + "name": "first_frame", + "tooltip": "Primeiro quadro a ser usado para o vídeo." + }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "Este parâmetro é ignorado para qualquer modelo, exceto seedance-1-5-pro." + }, + "last_frame": { + "name": "last_frame", + "tooltip": "Último quadro a ser usado para o vídeo." + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "O prompt de texto usado para gerar o vídeo." + }, + "resolution": { + "name": "resolution", + "tooltip": "A resolução do vídeo de saída." + }, + "seed": { + "name": "seed", + "tooltip": "Seed a ser usada para a geração." + }, + "watermark": { + "name": "watermark", + "tooltip": "Se deve adicionar uma marca d'água \"Gerado por IA\" ao vídeo." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ByteDanceImageEditNode": { + "description": "Edite imagens usando modelos ByteDance via API com base em um prompt", + "display_name": "ByteDance Edição de Imagem", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "guidance_scale": { + "name": "guidance_scale", + "tooltip": "Um valor mais alto faz a imagem seguir o prompt mais de perto" + }, + "image": { + "name": "image", + "tooltip": "A imagem base para editar" + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "Instrução para editar a imagem" + }, + "seed": { + "name": "seed", + "tooltip": "Seed a ser usada para a geração" + }, + "watermark": { + "name": "watermark", + "tooltip": "Se deve adicionar uma marca d'água \"Gerado por IA\" à imagem" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ByteDanceImageNode": { + "description": "Gere imagens usando modelos ByteDance via API com base em um prompt", + "display_name": "ByteDance Imagem", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "guidance_scale": { + "name": "guidance_scale", + "tooltip": "Um valor mais alto faz a imagem seguir o prompt mais de perto" + }, + "height": { + "name": "height", + "tooltip": "Altura personalizada para a imagem. O valor só funciona se `size_preset` estiver definido como `Personalizado`" + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "O prompt de texto usado para gerar a imagem" + }, + "seed": { + "name": "seed", + "tooltip": "Seed a ser usada para a geração" + }, + "size_preset": { + "name": "size_preset", + "tooltip": "Escolha um tamanho recomendado. Selecione Personalizado para usar a largura e altura abaixo" + }, + "watermark": { + "name": "watermark", + "tooltip": "Se deve adicionar uma marca d'água \"Gerado por IA\" à imagem" + }, + "width": { + "name": "width", + "tooltip": "Largura personalizada para a imagem. O valor só funciona se `size_preset` estiver definido como `Personalizado`" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ByteDanceImageReferenceNode": { + "description": "Gerar vídeo usando prompt e imagens de referência.", + "display_name": "Imagens de Referência ByteDance para Vídeo", + "inputs": { + "aspect_ratio": { + "name": "proporção", + "tooltip": "A proporção do vídeo de saída." + }, + "control_after_generate": { + "name": "controle após gerar" + }, + "duration": { + "name": "duração", + "tooltip": "A duração do vídeo de saída em segundos." + }, + "images": { + "name": "imagens", + "tooltip": "De uma a quatro imagens." + }, + "model": { + "name": "modelo" + }, + "prompt": { + "name": "prompt", + "tooltip": "O prompt de texto usado para gerar o vídeo." + }, + "resolution": { + "name": "resolução", + "tooltip": "A resolução do vídeo de saída." + }, + "seed": { + "name": "semente", + "tooltip": "Semente a ser usada para a geração." + }, + "watermark": { + "name": "marca d'água", + "tooltip": "Se deve adicionar uma marca d'água \"Gerado por IA\" ao vídeo." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ByteDanceImageToVideoNode": { + "description": "Gerar vídeo usando modelos ByteDance via API com base em imagem e prompt", + "display_name": "Imagem para Vídeo ByteDance", + "inputs": { + "aspect_ratio": { + "name": "proporção", + "tooltip": "A proporção do vídeo de saída." + }, + "camera_fixed": { + "name": "câmera fixa", + "tooltip": "Especifica se a câmera deve ser fixa. O aplicativo da plataforma adiciona uma instrução para fixar a câmera ao seu prompt, mas não garante o efeito real." + }, + "control_after_generate": { + "name": "controle após gerar" + }, + "duration": { + "name": "duração", + "tooltip": "A duração do vídeo de saída em segundos." + }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "Este parâmetro é ignorado para qualquer modelo, exceto seedance-1-5-pro." + }, + "image": { + "name": "imagem", + "tooltip": "Primeiro quadro a ser usado para o vídeo." + }, + "model": { + "name": "modelo" + }, + "prompt": { + "name": "prompt", + "tooltip": "O prompt de texto usado para gerar o vídeo." + }, + "resolution": { + "name": "resolução", + "tooltip": "A resolução do vídeo de saída." + }, + "seed": { + "name": "semente", + "tooltip": "Semente a ser usada para a geração." + }, + "watermark": { + "name": "marca d'água", + "tooltip": "Se deve adicionar uma marca d'água \"Gerado por IA\" ao vídeo." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ByteDanceSeedreamNode": { + "description": "Geração unificada de texto para imagem e edição precisa de frases únicas em até 4K de resolução.", + "display_name": "ByteDance Seedream 4.5", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "fail_on_partial": { + "name": "fail_on_partial", + "tooltip": "Se ativado, aborta a execução se alguma das imagens solicitadas estiver faltando ou retornar erro." + }, + "height": { + "name": "height", + "tooltip": "Altura personalizada para a imagem. O valor só funciona se `size_preset` estiver definido como `Personalizado`" + }, + "image": { + "name": "image", + "tooltip": "Imagem(ns) de entrada para geração de imagem a partir de imagem. Lista de 1 a 10 imagens para geração de referência única ou múltipla." + }, + "max_images": { + "name": "max_images", + "tooltip": "Número máximo de imagens a serem geradas quando sequential_image_generation='auto'. O total de imagens (entrada + geradas) não pode exceder 15." + }, + "model": { + "name": "model", + "tooltip": "Nome do modelo" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto para criar ou editar uma imagem." + }, + "seed": { + "name": "seed", + "tooltip": "Seed a ser usada para geração." + }, + "sequential_image_generation": { + "name": "sequential_image_generation", + "tooltip": "Modo de geração de imagens em grupo. 'desativado' gera uma única imagem. 'auto' permite que o modelo decida se deve gerar várias imagens relacionadas (ex: cenas de histórias, variações de personagens)." + }, + "size_preset": { + "name": "size_preset", + "tooltip": "Escolha um tamanho recomendado. Selecione Personalizado para usar a largura e altura abaixo." + }, + "watermark": { + "name": "watermark", + "tooltip": "Se deve adicionar uma marca d'água \"Gerado por IA\" à imagem." + }, + "width": { + "name": "width", + "tooltip": "Largura personalizada para a imagem. O valor só funciona se `size_preset` estiver definido como `Personalizado`" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ByteDanceTextToVideoNode": { + "description": "Gere vídeo usando modelos ByteDance via API com base no prompt", + "display_name": "ByteDance Texto para Vídeo", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "A proporção do vídeo de saída." + }, + "camera_fixed": { + "name": "camera_fixed", + "tooltip": "Especifica se a câmera deve ser fixa. O aplicativo adiciona uma instrução para fixar a câmera ao seu prompt, mas não garante o efeito real." + }, + "control_after_generate": { + "name": "control after generate" + }, + "duration": { + "name": "duration", + "tooltip": "A duração do vídeo de saída em segundos." + }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "Este parâmetro é ignorado para qualquer modelo, exceto seedance-1-5-pro." + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "O prompt de texto usado para gerar o vídeo." + }, + "resolution": { + "name": "resolution", + "tooltip": "A resolução do vídeo de saída." + }, + "seed": { + "name": "seed", + "tooltip": "Seed a ser usada para geração." + }, + "watermark": { + "name": "watermark", + "tooltip": "Se deve adicionar uma marca d'água \"Gerado por IA\" ao vídeo." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CFGGuider": { + "display_name": "CFGGuider", + "inputs": { + "cfg": { + "name": "cfg" + }, + "model": { + "name": "modelo" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CFGNorm": { + "display_name": "CFGNorm", + "inputs": { + "model": { + "name": "modelo" + }, + "strength": { + "name": "força" + } + }, + "outputs": { + "0": { + "name": "modelo_patchado", + "tooltip": null + } + } + }, + "CFGZeroStar": { + "display_name": "CFGZeroStar", + "inputs": { + "model": { + "name": "modelo" + } + }, + "outputs": { + "0": { + "name": "modelo_patchado", + "tooltip": null + } + } + }, + "CLIPAttentionMultiply": { + "display_name": "CLIPAttentionMultiply", + "inputs": { + "clip": { + "name": "clip" + }, + "k": { + "name": "k" + }, + "out": { + "name": "out" + }, + "q": { + "name": "q" + }, + "v": { + "name": "v" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CLIPLoader": { + "description": "[Receitas]\n\nstable_diffusion: clip-l\nstable_cascade: clip-g\nsd3: t5 xxl/ clip-g / clip-l\nstable_audio: t5 base\nmochi: t5 xxl\ncosmos: old t5 xxl\nlumina2: gemma 2 2B\nwan: umt5 xxl\n hidream: llama-3.1 (Recomendado) ou t5\nomnigen2: qwen vl 2.5 3B", + "display_name": "Carregar CLIP", + "inputs": { + "clip_name": { + "name": "clip_name" + }, + "device": { + "name": "device" + }, + "type": { + "name": "type" + } + } + }, + "CLIPMergeAdd": { + "display_name": "CLIPMergeAdd", + "inputs": { + "clip1": { + "name": "clip1" + }, + "clip2": { + "name": "clip2" + } + } + }, + "CLIPMergeSimple": { + "display_name": "CLIPMergeSimple", + "inputs": { + "clip1": { + "name": "clip1" + }, + "clip2": { + "name": "clip2" + }, + "ratio": { + "name": "ratio" + } + } + }, + "CLIPMergeSubtract": { + "display_name": "CLIPMergeSubtract", + "inputs": { + "clip1": { + "name": "clip1" + }, + "clip2": { + "name": "clip2" + }, + "multiplier": { + "name": "multiplier" + } + } + }, + "CLIPSave": { + "display_name": "CLIPSave", + "inputs": { + "clip": { + "name": "clip" + }, + "filename_prefix": { + "name": "filename_prefix" + } + } + }, + "CLIPSetLastLayer": { + "display_name": "CLIP Definir Última Camada", + "inputs": { + "clip": { + "name": "clip" + }, + "stop_at_clip_layer": { + "name": "stop_at_clip_layer" + } + } + }, + "CLIPTextEncode": { + "description": "Codifica um prompt de texto usando um modelo CLIP em um embedding que pode ser usado para guiar o modelo de difusão na geração de imagens específicas.", + "display_name": "Codificação de Texto CLIP (Prompt)", + "inputs": { + "clip": { + "name": "clip", + "tooltip": "O modelo CLIP usado para codificar o texto." + }, + "text": { + "name": "texto", + "tooltip": "O texto a ser codificado." + } + }, + "outputs": { + "0": { + "tooltip": "Um condicionamento contendo o texto embutido usado para guiar o modelo de difusão." + } + } + }, + "CLIPTextEncodeControlnet": { + "display_name": "CLIPTextEncodeControlnet", + "inputs": { + "clip": { + "name": "clip" + }, + "conditioning": { + "name": "condicionamento" + }, + "text": { + "name": "texto" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CLIPTextEncodeFlux": { + "display_name": "CLIPTextEncodeFlux", + "inputs": { + "clip": { + "name": "clip" + }, + "clip_l": { + "name": "clip_l" + }, + "guidance": { + "name": "orientação" + }, + "t5xxl": { + "name": "t5xxl" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CLIPTextEncodeHiDream": { + "display_name": "CLIPTextEncodeHiDream", + "inputs": { + "clip": { + "name": "clip" + }, + "clip_g": { + "name": "clip_g" + }, + "clip_l": { + "name": "clip_l" + }, + "llama": { + "name": "llama" + }, + "t5xxl": { + "name": "t5xxl" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CLIPTextEncodeHunyuanDiT": { + "display_name": "CLIPTextEncodeHunyuanDiT", + "inputs": { + "bert": { + "name": "bert" + }, + "clip": { + "name": "clip" + }, + "mt5xl": { + "name": "mt5xl" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CLIPTextEncodeKandinsky5": { + "display_name": "CLIPTextEncodeKandinsky5", + "inputs": { + "clip": { + "name": "clip" + }, + "clip_l": { + "name": "clip_l" + }, + "qwen25_7b": { + "name": "qwen25_7b" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CLIPTextEncodeLumina2": { + "description": "Codifica um prompt de sistema e um prompt de usuário usando um modelo CLIP em um embedding que pode ser usado para guiar o modelo de difusão na geração de imagens específicas.", + "display_name": "Codificação de Texto CLIP para Lumina2", + "inputs": { + "clip": { + "name": "clip", + "tooltip": "O modelo CLIP usado para codificar o texto." + }, + "system_prompt": { + "name": "prompt_do_sistema", + "tooltip": "Lumina2 fornece dois tipos de prompts de sistema: Superior: Você é um assistente projetado para gerar imagens superiores com o grau superior de alinhamento texto-imagem com base em prompts textuais ou prompts de usuário. Alinhamento: Você é um assistente projetado para gerar imagens de alta qualidade com o mais alto grau de alinhamento texto-imagem com base em prompts textuais." + }, + "user_prompt": { + "name": "prompt_do_usuário", + "tooltip": "O texto a ser codificado." + } + }, + "outputs": { + "0": { + "tooltip": "Um condicionamento contendo o texto embutido usado para guiar o modelo de difusão." + } + } + }, + "CLIPTextEncodePixArtAlpha": { + "description": "Codifica texto e define o condicionamento de resolução para PixArt Alpha. Não se aplica ao PixArt Sigma.", + "display_name": "CLIPTextEncodePixArtAlpha", + "inputs": { + "clip": { + "name": "clip" + }, + "height": { + "name": "altura" + }, + "text": { + "name": "texto" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CLIPTextEncodeSD3": { + "display_name": "CLIPTextEncodeSD3", + "inputs": { + "clip": { + "name": "clip" + }, + "clip_g": { + "name": "clip_g" + }, + "clip_l": { + "name": "clip_l" + }, + "empty_padding": { + "name": "preenchimento_vazio" + }, + "t5xxl": { + "name": "t5xxl" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CLIPTextEncodeSDXL": { + "display_name": "CLIPTextEncodeSDXL", + "inputs": { + "clip": { + "name": "clip" + }, + "crop_h": { + "name": "recorte_altura" + }, + "crop_w": { + "name": "recorte_largura" + }, + "height": { + "name": "altura" + }, + "target_height": { + "name": "altura_alvo" + }, + "target_width": { + "name": "largura_alvo" + }, + "text_g": { + "name": "texto_g" + }, + "text_l": { + "name": "texto_l" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CLIPTextEncodeSDXLRefiner": { + "display_name": "CLIPTextEncodeSDXLRefiner", + "inputs": { + "ascore": { + "name": "ascore" + }, + "clip": { + "name": "clip" + }, + "height": { + "name": "altura" + }, + "text": { + "name": "texto" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CLIPVisionEncode": { + "display_name": "CLIP Vision Encode", + "inputs": { + "clip_vision": { + "name": "clip_vision" + }, + "crop": { + "name": "recorte" + }, + "image": { + "name": "imagem" + } + } + }, + "CLIPVisionLoader": { + "display_name": "Carregar CLIP Vision", + "inputs": { + "clip_name": { + "name": "nome_do_clip" + } + } + }, + "Canny": { + "display_name": "Canny", + "inputs": { + "high_threshold": { + "name": "high_threshold" + }, + "image": { + "name": "image" + }, + "low_threshold": { + "name": "low_threshold" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CaseConverter": { + "display_name": "Conversor de Caixa", + "inputs": { + "mode": { + "name": "modo" + }, + "string": { + "name": "string" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CenterCropImages": { + "display_name": "Cortar Imagens ao Centro", + "inputs": { + "height": { + "name": "altura", + "tooltip": "Altura do corte." + }, + "images": { + "name": "imagens", + "tooltip": "Imagem para processar." + }, + "width": { + "name": "largura", + "tooltip": "Largura do corte." + } + }, + "outputs": { + "0": { + "name": "imagens", + "tooltip": "Imagens processadas" + } + } + }, + "CheckpointLoader": { + "display_name": "Carregar Checkpoint com Configuração (OBSOLETO)", + "inputs": { + "ckpt_name": { + "name": "nome_ckpt" + }, + "config_name": { + "name": "nome_config" + } + } + }, + "CheckpointLoaderSimple": { + "description": "Carrega um checkpoint de modelo de difusão, modelos de difusão são usados para remover ruído dos latents.", + "display_name": "Carregar Checkpoint", + "inputs": { + "ckpt_name": { + "name": "nome_ckpt", + "tooltip": "O nome do checkpoint (modelo) a ser carregado." + } + }, + "outputs": { + "0": { + "tooltip": "O modelo usado para remover ruído dos latents." + }, + "1": { + "tooltip": "O modelo CLIP usado para codificar prompts de texto." + }, + "2": { + "tooltip": "O modelo VAE usado para codificar e decodificar imagens para e do espaço latente." + } + } + }, + "CheckpointSave": { + "display_name": "Salvar Checkpoint", + "inputs": { + "clip": { + "name": "clip" + }, + "filename_prefix": { + "name": "prefixo_nome_arquivo" + }, + "model": { + "name": "modelo" + }, + "vae": { + "name": "vae" + } + } + }, + "ChromaRadianceOptions": { + "description": "Permite definir opções avançadas para o modelo Chroma Radiance.", + "display_name": "ChromaRadianceOptions", + "inputs": { + "end_sigma": { + "name": "end_sigma", + "tooltip": "Último sigma em que essas opções estarão em vigor." + }, + "model": { + "name": "model" + }, + "nerf_tile_size": { + "name": "nerf_tile_size", + "tooltip": "Permite substituir o tamanho padrão do tile NeRF. -1 significa usar o padrão (32). 0 significa usar o modo sem tiles (pode exigir muita VRAM)." + }, + "preserve_wrapper": { + "name": "preserve_wrapper", + "tooltip": "Quando ativado, delega para um wrapper de função de modelo existente, se houver. Geralmente deve permanecer ativado." + }, + "start_sigma": { + "name": "start_sigma", + "tooltip": "Primeiro sigma em que essas opções estarão em vigor." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CombineHooks2": { + "display_name": "Combinar Hooks [2]", + "inputs": { + "hooks_A": { + "name": "hooks_A" + }, + "hooks_B": { + "name": "hooks_B" + } + } + }, + "CombineHooks4": { + "display_name": "Combinar Hooks [4]", + "inputs": { + "hooks_A": { + "name": "hooks_A" + }, + "hooks_B": { + "name": "hooks_B" + }, + "hooks_C": { + "name": "hooks_C" + }, + "hooks_D": { + "name": "hooks_D" + } + } + }, + "CombineHooks8": { + "display_name": "Combinar Hooks [8]", + "inputs": { + "hooks_A": { + "name": "hooks_A" + }, + "hooks_B": { + "name": "hooks_B" + }, + "hooks_C": { + "name": "hooks_C" + }, + "hooks_D": { + "name": "hooks_D" + }, + "hooks_E": { + "name": "hooks_E" + }, + "hooks_F": { + "name": "hooks_F" + }, + "hooks_G": { + "name": "hooks_G" + }, + "hooks_H": { + "name": "hooks_H" + } + } + }, + "ComfySwitchNode": { + "display_name": "Alternar", + "inputs": { + "on_false": { + "name": "falso" + }, + "on_true": { + "name": "verdadeiro" + }, + "switch": { + "name": "alternar" + } + }, + "outputs": { + "0": { + "name": "saída", + "tooltip": null + } + } + }, + "ConditioningAverage": { + "display_name": "Média de Condicionamento", + "inputs": { + "conditioning_from": { + "name": "condicionamento_de" + }, + "conditioning_to": { + "name": "condicionamento_para" + }, + "conditioning_to_strength": { + "name": "força_condicionamento_para" + } + } + }, + "ConditioningCombine": { + "display_name": "Condicionamento (Combinar)", + "inputs": { + "conditioning_1": { + "name": "condicionamento_1" + }, + "conditioning_2": { + "name": "condicionamento_2" + } + } + }, + "ConditioningConcat": { + "display_name": "Condicionamento (Concatenar)", + "inputs": { + "conditioning_from": { + "name": "condicionamento_de" + }, + "conditioning_to": { + "name": "condicionamento_para" + } + } + }, + "ConditioningSetArea": { + "display_name": "Condicionamento (Definir Área)", + "inputs": { + "conditioning": { + "name": "condicionamento" + }, + "height": { + "name": "altura" + }, + "strength": { + "name": "força" + }, + "width": { + "name": "largura" + }, + "x": { + "name": "x" + }, + "y": { + "name": "y" + } + } + }, + "ConditioningSetAreaPercentage": { + "display_name": "Condicionamento (Definir Área com Percentual)", + "inputs": { + "conditioning": { + "name": "condicionamento" + }, + "height": { + "name": "altura" + }, + "strength": { + "name": "força" + }, + "width": { + "name": "largura" + }, + "x": { + "name": "x" + }, + "y": { + "name": "y" + } + } + }, + "ConditioningSetAreaPercentageVideo": { + "display_name": "CondicionamentoDefinirÁreaPercentualVídeo", + "inputs": { + "conditioning": { + "name": "condicionamento" + }, + "height": { + "name": "altura" + }, + "strength": { + "name": "força" + }, + "temporal": { + "name": "temporal" + }, + "width": { + "name": "largura" + }, + "x": { + "name": "x" + }, + "y": { + "name": "y" + }, + "z": { + "name": "z" + } + } + }, + "ConditioningSetAreaStrength": { + "display_name": "CondicionamentoDefinirForçaÁrea", + "inputs": { + "conditioning": { + "name": "condicionamento" + }, + "strength": { + "name": "força" + } + } + }, + "ConditioningSetDefaultCombine": { + "display_name": "Cond Definir Combinação Padrão", + "inputs": { + "cond": { + "name": "cond" + }, + "cond_DEFAULT": { + "name": "cond_PADRÃO" + }, + "hooks": { + "name": "hooks" + } + } + }, + "ConditioningSetMask": { + "display_name": "Condicionamento (Definir Mask)", + "inputs": { + "conditioning": { + "name": "condicionamento" + }, + "mask": { + "name": "mask" + }, + "set_cond_area": { + "name": "definir_área_cond" + }, + "strength": { + "name": "força" + } + } + }, + "ConditioningSetProperties": { + "display_name": "Props de Cond Set", + "inputs": { + "cond_NEW": { + "name": "cond_NOVO" + }, + "hooks": { + "name": "ganchos" + }, + "mask": { + "name": "mask" + }, + "set_cond_area": { + "name": "definir_área_cond" + }, + "strength": { + "name": "força" + }, + "timesteps": { + "name": "etapas_de_tempo" + } + } + }, + "ConditioningSetPropertiesAndCombine": { + "display_name": "Props de Cond Set Combinar", + "inputs": { + "cond": { + "name": "cond" + }, + "cond_NEW": { + "name": "cond_NOVO" + }, + "hooks": { + "name": "ganchos" + }, + "mask": { + "name": "mask" + }, + "set_cond_area": { + "name": "definir_área_cond" + }, + "strength": { + "name": "força" + }, + "timesteps": { + "name": "etapas_de_tempo" + } + } + }, + "ConditioningSetTimestepRange": { + "display_name": "CondicionamentoDefinirIntervaloDeTempo", + "inputs": { + "conditioning": { + "name": "condicionamento" + }, + "end": { + "name": "fim" + }, + "start": { + "name": "início" + } + } + }, + "ConditioningStableAudio": { + "display_name": "CondicionamentoStable Audio", + "inputs": { + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "seconds_start": { + "name": "segundos_início" + }, + "seconds_total": { + "name": "segundos_total" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + } + } + }, + "ConditioningTimestepsRange": { + "display_name": "Intervalo de Etapas de Tempo", + "inputs": { + "end_percent": { + "name": "percentual_final" + }, + "start_percent": { + "name": "percentual_inicial" + } + }, + "outputs": { + "1": { + "name": "ANTES_DO_INTERVALO" + }, + "2": { + "name": "APÓS_O_INTERVALO" + } + } + }, + "ConditioningZeroOut": { + "display_name": "CondicionamentoZerar", + "inputs": { + "conditioning": { + "name": "condicionamento" + } + } + }, + "ContextWindowsManual": { + "description": "Defina manualmente as janelas de contexto.", + "display_name": "Janelas de Contexto (Manual)", + "inputs": { + "closed_loop": { + "name": "ciclo_fechado", + "tooltip": "Se deve fechar o ciclo da janela de contexto; aplicável apenas para agendamentos em loop." + }, + "context_length": { + "name": "comprimento_contexto", + "tooltip": "O comprimento da janela de contexto." + }, + "context_overlap": { + "name": "sobreposição_contexto", + "tooltip": "A sobreposição da janela de contexto." + }, + "context_schedule": { + "name": "agendamento_contexto", + "tooltip": "O passo da janela de contexto." + }, + "context_stride": { + "name": "passo_contexto", + "tooltip": "O passo da janela de contexto; aplicável apenas para agendamentos uniformes." + }, + "dim": { + "name": "dimensão", + "tooltip": "A dimensão à qual aplicar as janelas de contexto." + }, + "freenoise": { + "name": "freenoise", + "tooltip": "Se deve aplicar embaralhamento de ruído FreeNoise, melhora a mesclagem das janelas." + }, + "fuse_method": { + "name": "método_fusão", + "tooltip": "O método a ser usado para fundir as janelas de contexto." + }, + "model": { + "name": "modelo", + "tooltip": "O modelo ao qual aplicar as janelas de contexto durante a amostragem." + } + }, + "outputs": { + "0": { + "tooltip": "O modelo com janelas de contexto aplicadas durante a amostragem." + } + } + }, + "ControlNetApply": { + "display_name": "Aplicar ControlNet (ANTIGO)", + "inputs": { + "conditioning": { + "name": "condicionamento" + }, + "control_net": { + "name": "control_net" + }, + "image": { + "name": "imagem" + }, + "strength": { + "name": "força" + } + } + }, + "ControlNetApplyAdvanced": { + "display_name": "Aplicar ControlNet", + "inputs": { + "control_net": { + "name": "control_net" + }, + "end_percent": { + "name": "percentual_final" + }, + "image": { + "name": "imagem" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "start_percent": { + "name": "percentual_inicial" + }, + "strength": { + "name": "força" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "positivo" + }, + "1": { + "name": "negativo" + } + } + }, + "ControlNetApplySD3": { + "display_name": "Aplicar Controlnet com VAE", + "inputs": { + "control_net": { + "name": "control_net" + }, + "end_percent": { + "name": "percentual_final" + }, + "image": { + "name": "imagem" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "start_percent": { + "name": "percentual_inicial" + }, + "strength": { + "name": "força" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + } + } + }, + "ControlNetInpaintingAliMamaApply": { + "display_name": "Aplicar ControlNetInpaintingAliMama", + "inputs": { + "control_net": { + "name": "control_net" + }, + "end_percent": { + "name": "percentual_final" + }, + "image": { + "name": "imagem" + }, + "mask": { + "name": "mask" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "start_percent": { + "name": "percentual_inicial" + }, + "strength": { + "name": "força" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + } + } + }, + "ControlNetLoader": { + "display_name": "Carregar Modelo ControlNet", + "inputs": { + "control_net_name": { + "name": "control_net_name" + } + } + }, + "CosmosImageToVideoLatent": { + "display_name": "CosmosImageToVideoLatent", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "end_image": { + "name": "imagem_final" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "comprimento" + }, + "start_image": { + "name": "imagem_inicial" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CosmosPredict2ImageToVideoLatent": { + "display_name": "CosmosPredict2ImageToVideoLatent", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "end_image": { + "name": "imagem_final" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "comprimento" + }, + "start_image": { + "name": "imagem_inicial" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CreateHookKeyframe": { + "display_name": "Criar Quadro-chave de Gancho", + "inputs": { + "prev_hook_kf": { + "name": "quadro-chave_gancho_anterior" + }, + "start_percent": { + "name": "percentual_inicial" + }, + "strength_mult": { + "name": "multiplicador_de_força" + } + }, + "outputs": { + "0": { + "name": "HOOK_KF" + } + } + }, + "CreateHookKeyframesFromFloats": { + "display_name": "Criar Quadros-chave de Gancho a partir de Floats", + "inputs": { + "end_percent": { + "name": "percentual_final" + }, + "floats_strength": { + "name": "força_floats" + }, + "prev_hook_kf": { + "name": "quadro-chave_gancho_anterior" + }, + "print_keyframes": { + "name": "imprimir_quadros-chave" + }, + "start_percent": { + "name": "percentual_inicial" + } + }, + "outputs": { + "0": { + "name": "HOOK_KF" + } + } + }, + "CreateHookKeyframesInterpolated": { + "display_name": "Criar Interp. de Keyframes do Hook", + "inputs": { + "end_percent": { + "name": "percentual_final" + }, + "interpolation": { + "name": "interpolação" + }, + "keyframes_count": { + "name": "quantidade_keyframes" + }, + "prev_hook_kf": { + "name": "hook_kf_anterior" + }, + "print_keyframes": { + "name": "imprimir_keyframes" + }, + "start_percent": { + "name": "percentual_inicial" + }, + "strength_end": { + "name": "força_final" + }, + "strength_start": { + "name": "força_inicial" + } + }, + "outputs": { + "0": { + "name": "HOOK_KF" + } + } + }, + "CreateHookLora": { + "display_name": "Criar Hook LoRA", + "inputs": { + "lora_name": { + "name": "lora_name" + }, + "prev_hooks": { + "name": "hooks_anteriores" + }, + "strength_clip": { + "name": "força_clip" + }, + "strength_model": { + "name": "força_modelo" + } + } + }, + "CreateHookLoraModelOnly": { + "display_name": "Criar Hook LoRA (MO)", + "inputs": { + "lora_name": { + "name": "lora_name" + }, + "prev_hooks": { + "name": "hooks_anteriores" + }, + "strength_model": { + "name": "força_modelo" + } + } + }, + "CreateHookModelAsLora": { + "display_name": "Criar Hook Modelo como LoRA", + "inputs": { + "ckpt_name": { + "name": "ckpt_name" + }, + "prev_hooks": { + "name": "hooks_anteriores" + }, + "strength_clip": { + "name": "força_clip" + }, + "strength_model": { + "name": "força_modelo" + } + } + }, + "CreateHookModelAsLoraModelOnly": { + "display_name": "Criar Hook Modelo como LoRA (MO)", + "inputs": { + "ckpt_name": { + "name": "ckpt_name" + }, + "prev_hooks": { + "name": "hooks_anteriores" + }, + "strength_model": { + "name": "força_modelo" + } + } + }, + "CreateVideo": { + "description": "Crie um vídeo a partir de imagens.", + "display_name": "Criar Vídeo", + "inputs": { + "audio": { + "name": "áudio", + "tooltip": "O áudio a ser adicionado ao vídeo." + }, + "fps": { + "name": "fps" + }, + "images": { + "name": "imagens", + "tooltip": "As imagens para criar o vídeo." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CropMask": { + "display_name": "Cortar Máscara", + "inputs": { + "height": { + "name": "altura" + }, + "mask": { + "name": "mask" + }, + "width": { + "name": "largura" + }, + "x": { + "name": "x" + }, + "y": { + "name": "y" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "CustomCombo": { + "display_name": "Combo Personalizado", + "inputs": { + "choice": { + "name": "escolha" + }, + "index": { + }, + "option1": { + } + }, + "outputs": [ + null, + { + "name": "ÍNDICE", + "tooltip": null + } + ] + }, + "DiffControlNetLoader": { + "display_name": "Carregar Modelo ControlNet (diff)", + "inputs": { + "control_net_name": { + "name": "control_net_name" + }, + "model": { + "name": "modelo" + } + } + }, + "DifferentialDiffusion": { + "display_name": "Diffusão Diferencial", + "inputs": { + "model": { + "name": "modelo" + }, + "strength": { + "name": "força" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "DiffusersLoader": { + "display_name": "Carregador Diffusers", + "inputs": { + "model_path": { + "name": "caminho_do_modelo" + } + } + }, + "DisableNoise": { + "display_name": "Desativar Ruído", + "outputs": { + "0": { + "tooltip": null + } + } + }, + "DualCFGGuider": { + "display_name": "Guia DualCFG", + "inputs": { + "cfg_cond2_negative": { + "name": "cfg_cond2_negativo" + }, + "cfg_conds": { + "name": "cfg_conds" + }, + "cond1": { + "name": "cond1" + }, + "cond2": { + "name": "cond2" + }, + "model": { + "name": "modelo" + }, + "negative": { + "name": "negativo" + }, + "style": { + "name": "estilo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "DualCLIPLoader": { + "description": "[Receitas]\n\nsdxl: clip-l, clip-g\nsd3: clip-l, clip-g / clip-l, t5 / clip-g, t5\nflux: clip-l, t5\nhidream: pelo menos um de t5 ou llama, recomendado t5 e llama\nhunyuan_image: qwen2.5vl 7b e byt5 small\nnewbie: gemma-3-4b-it, jina clip v2", + "display_name": "Carregador DualCLIP", + "inputs": { + "clip_name1": { + "name": "clip_nome1" + }, + "clip_name2": { + "name": "clip_nome2" + }, + "device": { + "name": "dispositivo" + }, + "type": { + "name": "tipo" + } + } + }, + "EasyCache": { + "description": "Implementação nativa do EasyCache.", + "display_name": "EasyCache", + "inputs": { + "end_percent": { + "name": "percentual_final", + "tooltip": "A etapa relativa de amostragem para parar de usar o EasyCache." + }, + "model": { + "name": "modelo", + "tooltip": "O modelo ao qual adicionar o EasyCache." + }, + "reuse_threshold": { + "name": "limite_de_reutilização", + "tooltip": "O limite para reutilizar etapas em cache." + }, + "start_percent": { + "name": "percentual_inicial", + "tooltip": "A etapa relativa de amostragem para começar a usar o EasyCache." + }, + "verbose": { + "name": "detalhado", + "tooltip": "Se deve registrar informações detalhadas." + } + }, + "outputs": { + "0": { + "tooltip": "O modelo com EasyCache." + } + } + }, + "EmptyAceStepLatentAudio": { + "display_name": "Áudio Latente AceStep Vazio", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote", + "tooltip": "O número de imagens latentes no lote." + }, + "seconds": { + "name": "segundos" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyAudio": { + "display_name": "Áudio Vazio", + "inputs": { + "channels": { + "name": "canais", + "tooltip": "Número de canais de áudio (1 para mono, 2 para estéreo)." + }, + "duration": { + "name": "duração", + "tooltip": "Duração do áudio vazio em segundos" + }, + "sample_rate": { + "name": "taxa_de_amostragem", + "tooltip": "Taxa de amostragem do áudio vazio." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyChromaRadianceLatentImage": { + "display_name": "EmptyChromaRadianceLatentImage", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "height": { + "name": "altura" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyCosmosLatentVideo": { + "display_name": "EmptyCosmosLatentVideo", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "duração" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyFlux2LatentImage": { + "display_name": "Empty Flux 2 Latent", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "height": { + "name": "altura" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyHunyuanImageLatent": { + "display_name": "EmptyHunyuanImageLatent", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "height": { + "name": "altura" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyHunyuanLatentVideo": { + "display_name": "Empty HunyuanVideo 1.0 Latent", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "duração" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyHunyuanVideo15Latent": { + "display_name": "Empty HunyuanVideo 1.5 Latent", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "duração" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyImage": { + "display_name": "EmptyImage", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "color": { + "name": "cor" + }, + "height": { + "name": "altura" + }, + "width": { + "name": "largura" + } + } + }, + "EmptyLTXVLatentVideo": { + "display_name": "EmptyLTXVLatentVideo", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "comprimento" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyLatentAudio": { + "display_name": "Empty Latent Audio", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote", + "tooltip": "O número de imagens latentes no lote." + }, + "seconds": { + "name": "segundos" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyLatentHunyuan3Dv2": { + "display_name": "EmptyLatentHunyuan3Dv2", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote", + "tooltip": "O número de imagens latentes no lote." + }, + "resolution": { + "name": "resolução" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyLatentImage": { + "description": "Crie um novo lote de imagens latentes vazias para serem denoised via amostragem.", + "display_name": "Imagem Latente Vazia", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote", + "tooltip": "O número de imagens latentes no lote." + }, + "height": { + "name": "altura", + "tooltip": "A altura das imagens latentes em pixels." + }, + "width": { + "name": "largura", + "tooltip": "A largura das imagens latentes em pixels." + } + }, + "outputs": { + "0": { + "tooltip": "O lote de imagens latentes vazias." + } + } + }, + "EmptyMochiLatentVideo": { + "display_name": "EmptyMochiLatentVideo", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "comprimento" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyQwenImageLayeredLatentImage": { + "display_name": "Imagem Latente em Camadas Qwen Vazia", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "height": { + "name": "altura" + }, + "layers": { + "name": "camadas" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptySD3LatentImage": { + "display_name": "EmptySD3LatentImage", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "height": { + "name": "altura" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Epsilon Scaling": { + "display_name": "Escalonamento Epsilon", + "inputs": { + "model": { + "name": "modelo" + }, + "scaling_factor": { + "name": "fator_de_escalonamento" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ExponentialScheduler": { + "display_name": "Agendador Exponencial", + "inputs": { + "sigma_max": { + "name": "sigma_max" + }, + "sigma_min": { + "name": "sigma_min" + }, + "steps": { + "name": "passos" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ExtendIntermediateSigmas": { + "display_name": "Estender Sigmas Intermediários", + "inputs": { + "end_at_sigma": { + "name": "terminar_em_sigma" + }, + "sigmas": { + "name": "sigmas" + }, + "spacing": { + "name": "espaçamento" + }, + "start_at_sigma": { + "name": "iniciar_em_sigma" + }, + "steps": { + "name": "passos" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FeatherMask": { + "display_name": "FeatherMask", + "inputs": { + "bottom": { + "name": "inferior" + }, + "left": { + "name": "esquerda" + }, + "mask": { + "name": "mask" + }, + "right": { + "name": "direita" + }, + "top": { + "name": "topo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FlipSigmas": { + "display_name": "FlipSigmas", + "inputs": { + "sigmas": { + "name": "sigmas" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2MaxImageNode": { + "description": "Gera imagens de forma síncrona com base no prompt e na resolução.", + "display_name": "Flux.2 [max] Imagem", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "height": { + "name": "altura" + }, + "images": { + "name": "imagens", + "tooltip": "Até 9 imagens para serem usadas como referência." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para geração ou edição da imagem" + }, + "prompt_upsampling": { + "name": "upsampling do prompt", + "tooltip": "Se deve realizar upsampling no prompt. Se ativo, modifica automaticamente o prompt para uma geração mais criativa." + }, + "seed": { + "name": "semente", + "tooltip": "A semente aleatória usada para criar o ruído." + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2ProImageNode": { + "description": "Gera imagens de forma síncrona com base no prompt e na resolução.", + "display_name": "Flux.2 [pro] Imagem", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "height": { + "name": "altura" + }, + "images": { + "name": "imagens", + "tooltip": "Até 9 imagens para serem usadas como referência." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para geração ou edição da imagem" + }, + "prompt_upsampling": { + "name": "upsampling do prompt", + "tooltip": "Se deve realizar upsampling no prompt. Se ativo, modifica automaticamente o prompt para uma geração mais criativa." + }, + "seed": { + "name": "semente", + "tooltip": "A semente aleatória usada para criar o ruído." + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2Scheduler": { + "display_name": "Flux2Scheduler", + "inputs": { + "height": { + "name": "altura" + }, + "steps": { + "name": "etapas" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FluxDisableGuidance": { + "description": "Este nó desativa completamente o guidance embed no Flux e modelos similares ao Flux", + "display_name": "FluxDisableGuidance", + "inputs": { + "conditioning": { + "name": "condicionamento" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FluxGuidance": { + "display_name": "FluxGuidance", + "inputs": { + "conditioning": { + "name": "condicionamento" + }, + "guidance": { + "name": "guidance" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FluxKontextImageScale": { + "description": "Este nó redimensiona a imagem para um tamanho mais ideal para flux kontext.", + "display_name": "FluxKontextImageScale", + "inputs": { + "image": { + "name": "imagem" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FluxKontextMaxImageNode": { + "description": "Edita imagens usando Flux.1 Kontext [pro] via API com base no prompt e na proporção.", + "display_name": "Flux.1 Kontext [max] Image", + "inputs": { + "aspect_ratio": { + "name": "proporção", + "tooltip": "Proporção da imagem; deve estar entre 1:4 e 4:1." + }, + "control_after_generate": { + "name": "controle após gerar" + }, + "guidance": { + "name": "orientação", + "tooltip": "Força da orientação para o processo de geração da imagem" + }, + "input_image": { + "name": "imagem_de_entrada" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para a geração da imagem - especifique o que e como editar." + }, + "prompt_upsampling": { + "name": "amostragem_do_prompt", + "tooltip": "Se deve realizar upsampling no prompt. Se ativo, modifica automaticamente o prompt para uma geração mais criativa, mas os resultados são não determinísticos (a mesma semente não produzirá exatamente o mesmo resultado)." + }, + "seed": { + "name": "semente", + "tooltip": "A semente aleatória usada para criar o ruído." + }, + "steps": { + "name": "etapas", + "tooltip": "Número de etapas para o processo de geração da imagem" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FluxKontextMultiReferenceLatentMethod": { + "display_name": "Editar Método de Referência do Modelo", + "inputs": { + "conditioning": { + "name": "condicionamento" + }, + "reference_latents_method": { + "name": "método_de_latent_de_referência" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FluxKontextProImageNode": { + "description": "Edita imagens usando Flux.1 Kontext [pro] via API com base no prompt e na proporção.", + "display_name": "Flux.1 Kontext [pro] Image", + "inputs": { + "aspect_ratio": { + "name": "proporção", + "tooltip": "Proporção da imagem; deve estar entre 1:4 e 4:1." + }, + "control_after_generate": { + "name": "controle após gerar" + }, + "guidance": { + "name": "orientação", + "tooltip": "Força da orientação para o processo de geração da imagem" + }, + "input_image": { + "name": "imagem_de_entrada" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para a geração da imagem - especifique o que e como editar." + }, + "prompt_upsampling": { + "name": "amostragem_do_prompt", + "tooltip": "Se deve realizar upsampling no prompt. Se ativo, modifica automaticamente o prompt para uma geração mais criativa, mas os resultados são não determinísticos (a mesma semente não produzirá exatamente o mesmo resultado)." + }, + "seed": { + "name": "semente", + "tooltip": "A semente aleatória usada para criar o ruído." + }, + "steps": { + "name": "etapas", + "tooltip": "Número de etapas para o processo de geração da imagem" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FluxProExpandNode": { + "description": "Expande a imagem com base no prompt.", + "display_name": "Flux.1 Expandir Imagem", + "inputs": { + "bottom": { + "name": "inferior", + "tooltip": "Número de pixels para expandir na parte inferior da imagem" + }, + "control_after_generate": { + "name": "controle após gerar" + }, + "guidance": { + "name": "orientação", + "tooltip": "Força da orientação para o processo de geração da imagem" + }, + "image": { + "name": "imagem" + }, + "left": { + "name": "esquerda", + "tooltip": "Número de pixels para expandir no lado esquerdo da imagem" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para a geração da imagem" + }, + "prompt_upsampling": { + "name": "aumento de prompt", + "tooltip": "Se deve realizar upsampling no prompt. Se ativo, modifica automaticamente o prompt para uma geração mais criativa, mas os resultados são não determinísticos (a mesma semente não produzirá exatamente o mesmo resultado)." + }, + "right": { + "name": "direita", + "tooltip": "Número de pixels para expandir no lado direito da imagem" + }, + "seed": { + "name": "semente", + "tooltip": "A semente aleatória usada para criar o ruído." + }, + "steps": { + "name": "passos", + "tooltip": "Número de passos para o processo de geração da imagem" + }, + "top": { + "name": "superior", + "tooltip": "Número de pixels para expandir na parte superior da imagem" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FluxProFillNode": { + "description": "Preenche a imagem com base na mask e no prompt.", + "display_name": "Flux.1 Preencher Imagem", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "guidance": { + "name": "orientação", + "tooltip": "Força da orientação para o processo de geração da imagem" + }, + "image": { + "name": "imagem" + }, + "mask": { + "name": "mask" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para a geração da imagem" + }, + "prompt_upsampling": { + "name": "aumento de prompt", + "tooltip": "Se deve realizar upsampling no prompt. Se ativo, modifica automaticamente o prompt para uma geração mais criativa, mas os resultados são não determinísticos (a mesma semente não produzirá exatamente o mesmo resultado)." + }, + "seed": { + "name": "semente", + "tooltip": "A semente aleatória usada para criar o ruído." + }, + "steps": { + "name": "passos", + "tooltip": "Número de passos para o processo de geração da imagem" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FluxProUltraImageNode": { + "description": "Gera imagens usando Flux Pro 1.1 Ultra via API com base no prompt e na resolução.", + "display_name": "Flux 1.1 [pro] Ultra Image", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "Proporção da imagem; deve estar entre 1:4 e 4:1." + }, + "control_after_generate": { + "name": "control after generate" + }, + "image_prompt": { + "name": "image_prompt" + }, + "image_prompt_strength": { + "name": "image_prompt_strength", + "tooltip": "Mistura entre o prompt e o image prompt." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para a geração da imagem" + }, + "prompt_upsampling": { + "name": "prompt_upsampling", + "tooltip": "Se deve realizar upsampling no prompt. Se ativado, modifica automaticamente o prompt para uma geração mais criativa, mas os resultados são não determinísticos (a mesma seed não produzirá exatamente o mesmo resultado)." + }, + "raw": { + "name": "raw", + "tooltip": "Quando Verdadeiro, gera imagens menos processadas e com aparência mais natural." + }, + "seed": { + "name": "seed", + "tooltip": "A seed aleatória usada para criar o ruído." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FreSca": { + "description": "Aplica escalonamento dependente de frequência à orientação", + "display_name": "FreSca", + "inputs": { + "freq_cutoff": { + "name": "freq_cutoff", + "tooltip": "Número de índices de frequência ao redor do centro considerados como baixa frequência" + }, + "model": { + "name": "model" + }, + "scale_high": { + "name": "scale_high", + "tooltip": "Fator de escala para componentes de alta frequência" + }, + "scale_low": { + "name": "scale_low", + "tooltip": "Fator de escala para componentes de baixa frequência" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FreeU": { + "display_name": "FreeU", + "inputs": { + "b1": { + "name": "b1" + }, + "b2": { + "name": "b2" + }, + "model": { + "name": "model" + }, + "s1": { + "name": "s1" + }, + "s2": { + "name": "s2" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "FreeU_V2": { + "display_name": "FreeU_V2", + "inputs": { + "b1": { + "name": "b1" + }, + "b2": { + "name": "b2" + }, + "model": { + "name": "model" + }, + "s1": { + "name": "s1" + }, + "s2": { + "name": "s2" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "GITSScheduler": { + "display_name": "GITSScheduler", + "inputs": { + "coeff": { + "name": "coeficiente" + }, + "denoise": { + "name": "reduzir_ruído" + }, + "steps": { + "name": "etapas" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "GLIGENLoader": { + "display_name": "GLIGENLoader", + "inputs": { + "gligen_name": { + "name": "gligen_name" + } + } + }, + "GLIGENTextBoxApply": { + "display_name": "GLIGENTextBoxApply", + "inputs": { + "clip": { + "name": "clip" + }, + "conditioning_to": { + "name": "conditioning_to" + }, + "gligen_textbox_model": { + "name": "gligen_textbox_model" + }, + "height": { + "name": "altura" + }, + "text": { + "name": "texto" + }, + "width": { + "name": "largura" + }, + "x": { + "name": "x" + }, + "y": { + "name": "y" + } + } + }, + "GeminiImage2Node": { + "description": "Gere ou edite imagens de forma síncrona via Google Vertex API.", + "display_name": "Nano Banana Pro (Google Gemini Image)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "Se definido como 'auto', corresponde à proporção da imagem de entrada; se nenhuma imagem for fornecida, normalmente é gerado um quadrado 16:9." + }, + "control_after_generate": { + "name": "control after generate" + }, + "files": { + "name": "files", + "tooltip": "Arquivo(s) opcional(is) para usar como contexto para o modelo. Aceita entradas do nó Gemini Generate Content Input Files." + }, + "images": { + "name": "images", + "tooltip": "Imagem(ns) de referência opcional(is). Para incluir várias imagens, use o nó Batch Images (até 14)." + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto descrevendo a imagem a ser gerada ou as edições a serem aplicadas. Inclua quaisquer restrições, estilos ou detalhes que o modelo deve seguir." + }, + "resolution": { + "name": "resolution", + "tooltip": "Resolução de saída desejada. Para 2K/4K, o upscaler nativo do Gemini é utilizado." + }, + "response_modalities": { + "name": "response_modalities", + "tooltip": "Escolha 'IMAGE' para saída somente de imagem, ou 'IMAGE+TEXT' para retornar tanto a imagem gerada quanto uma resposta em texto." + }, + "seed": { + "name": "seed", + "tooltip": "Quando a seed é fixada em um valor específico, o modelo faz o melhor esforço para fornecer a mesma resposta em solicitações repetidas. A saída determinística não é garantida. Além disso, alterar o modelo ou configurações de parâmetros, como a temperatura, pode causar variações na resposta mesmo usando o mesmo valor de seed. Por padrão, um valor de seed aleatório é usado." + }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "Instruções fundamentais que ditam o comportamento da IA." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "tooltip": null + } + } + }, + "GeminiImageNode": { + "description": "Edite imagens de forma síncrona via Google API.", + "display_name": "Nano Banana (Google Gemini Image)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "Por padrão, corresponde ao tamanho da imagem de saída ao da sua imagem de entrada, ou gera quadrados 1:1." + }, + "control_after_generate": { + "name": "control after generate" + }, + "files": { + "name": "files", + "tooltip": "Arquivo(s) opcional(is) para usar como contexto para o modelo. Aceita entradas do nó Gemini Generate Content Input Files." + }, + "images": { + "name": "images", + "tooltip": "Imagem(ns) opcional(is) para usar como contexto para o modelo. Para incluir várias imagens, você pode usar o nó Batch Images." + }, + "model": { + "name": "model", + "tooltip": "O modelo Gemini a ser usado para gerar respostas." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto para geração" + }, + "response_modalities": { + "name": "response_modalities", + "tooltip": "Escolha 'IMAGE' para saída somente de imagem, ou 'IMAGE+TEXT' para retornar tanto a imagem gerada quanto uma resposta em texto." + }, + "seed": { + "name": "seed", + "tooltip": "Quando a seed é fixada em um valor específico, o modelo faz o melhor esforço para fornecer a mesma resposta em solicitações repetidas. A saída determinística não é garantida. Além disso, alterar o modelo ou configurações de parâmetros, como a temperatura, pode causar variações na resposta mesmo usando o mesmo valor de seed. Por padrão, um valor de seed aleatório é usado." + }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "Instruções fundamentais que ditam o comportamento da IA." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "tooltip": null + } + } + }, + "GeminiInputFiles": { + "description": "Carrega e prepara arquivos de entrada para incluir como inputs para os nós Gemini LLM. Os arquivos serão lidos pelo modelo Gemini ao gerar uma resposta. O conteúdo do arquivo de texto conta para o limite de tokens. 🛈 DICA: Pode ser encadeado com outros nós de Arquivo de Entrada Gemini.", + "display_name": "Arquivos de Entrada Gemini", + "inputs": { + "GEMINI_INPUT_FILES": { + "name": "GEMINI_INPUT_FILES", + "tooltip": "Um(s) arquivo(s) adicional(is) opcional(is) para agrupar junto com o arquivo carregado deste nó. Permite o encadeamento de arquivos de entrada para que uma única mensagem possa incluir múltiplos arquivos de entrada." + }, + "file": { + "name": "arquivo", + "tooltip": "Arquivos de entrada para incluir como contexto para o modelo. Aceita apenas arquivos de texto (.txt) e PDF (.pdf) no momento." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "GeminiNode": { + "description": "Gere respostas em texto com o modelo Gemini AI do Google. Você pode fornecer múltiplos tipos de entrada (texto, imagens, áudio, vídeo) como contexto para gerar respostas mais relevantes e significativas.", + "display_name": "Google Gemini", + "inputs": { + "audio": { + "name": "áudio", + "tooltip": "Áudio opcional para usar como contexto para o modelo." + }, + "control_after_generate": { + "name": "control after generate" + }, + "files": { + "name": "arquivos", + "tooltip": "Arquivo(s) opcional(is) para usar como contexto para o modelo. Aceita entradas do nó Gemini Generate Content Input Files." + }, + "images": { + "name": "imagens", + "tooltip": "Imagem(ns) opcional(is) para usar como contexto para o modelo. Para incluir múltiplas imagens, você pode usar o nó Batch Images." + }, + "model": { + "name": "modelo", + "tooltip": "O modelo Gemini a ser usado para gerar respostas." + }, + "prompt": { + "name": "prompt", + "tooltip": "Entradas de texto para o modelo, usadas para gerar uma resposta. Você pode incluir instruções detalhadas, perguntas ou contexto para o modelo." + }, + "seed": { + "name": "semente", + "tooltip": "Quando a semente é fixada em um valor específico, o modelo faz o melhor esforço para fornecer a mesma resposta para solicitações repetidas. A saída determinística não é garantida. Além disso, alterar o modelo ou configurações de parâmetros, como a temperatura, pode causar variações na resposta mesmo usando o mesmo valor de semente. Por padrão, é usada uma semente aleatória." + }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "Instruções fundamentais que ditam o comportamento da IA." + }, + "video": { + "name": "vídeo", + "tooltip": "Vídeo opcional para usar como contexto para o modelo." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "GenerateTracks": { + "display_name": "GenerateTracks", + "inputs": { + "bezier": { + "name": "bezier", + "tooltip": "Habilitar caminho de curva Bezier usando o ponto médio como ponto de controle." + }, + "end_x": { + "name": "fim_x", + "tooltip": "Coordenada X normalizada (0-1) para a posição final." + }, + "end_y": { + "name": "fim_y", + "tooltip": "Coordenada Y normalizada (0-1) para a posição final." + }, + "height": { + "name": "altura" + }, + "interpolation": { + "name": "interpolação", + "tooltip": "Controla o tempo/velocidade do movimento ao longo do caminho." + }, + "mid_x": { + "name": "meio_x", + "tooltip": "Ponto de controle X normalizado para a curva Bezier. Usado apenas quando 'bezier' está habilitado." + }, + "mid_y": { + "name": "meio_y", + "tooltip": "Ponto de controle Y normalizado para a curva Bezier. Usado apenas quando 'bezier' está habilitado." + }, + "num_frames": { + "name": "número_de_quadros" + }, + "num_tracks": { + "name": "número_de_trilhas" + }, + "start_x": { + "name": "início_x", + "tooltip": "Coordenada X normalizada (0-1) para a posição inicial." + }, + "start_y": { + "name": "início_y", + "tooltip": "Coordenada Y normalizada (0-1) para a posição inicial." + }, + "track_mask": { + "name": "máscara_de_trilha", + "tooltip": "Máscara opcional para indicar quadros visíveis." + }, + "track_spread": { + "name": "dispersão_de_trilhas", + "tooltip": "Distância normalizada entre as trilhas. As trilhas são distribuídas perpendicularmente à direção do movimento." + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "comprimento_da_trilha", + "tooltip": null + } + } + }, + "GetImageSize": { + "description": "Retorna a largura e altura da imagem, e a repassa inalterada.", + "display_name": "Obter Tamanho da Imagem", + "inputs": { + "image": { + "name": "imagem" + } + }, + "outputs": { + "0": { + "name": "largura", + "tooltip": null + }, + "1": { + "name": "altura", + "tooltip": null + }, + "2": { + "name": "tamanho_do_lote", + "tooltip": null + } + } + }, + "GetVideoComponents": { + "description": "Extrai todos os componentes de um vídeo: quadros, áudio e taxa de quadros.", + "display_name": "Obter Componentes do Vídeo", + "inputs": { + "video": { + "name": "vídeo", + "tooltip": "O vídeo do qual extrair os componentes." + } + }, + "outputs": { + "0": { + "name": "imagens", + "tooltip": null + }, + "1": { + "name": "áudio", + "tooltip": null + }, + "2": { + "name": "fps", + "tooltip": null + } + } + }, + "GrowMask": { + "display_name": "Expandir Máscara", + "inputs": { + "expand": { + "name": "expandir" + }, + "mask": { + "name": "máscara" + }, + "tapered_corners": { + "name": "cantos arredondados" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Hunyuan3Dv2Conditioning": { + "display_name": "Hunyuan3Dv2Conditioning", + "inputs": { + "clip_vision_output": { + "name": "clip_vision_output" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + } + } + }, + "Hunyuan3Dv2ConditioningMultiView": { + "display_name": "Hunyuan3Dv2ConditioningMultiView", + "inputs": { + "back": { + "name": "trás" + }, + "front": { + "name": "frente" + }, + "left": { + "name": "esquerda" + }, + "right": { + "name": "direita" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + } + } + }, + "HunyuanImageToVideo": { + "display_name": "HunyuanImageToVideo", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "guidance_type": { + "name": "tipo_de_guia" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "duração" + }, + "positive": { + "name": "positivo" + }, + "start_image": { + "name": "imagem_inicial" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "latente", + "tooltip": null + } + } + }, + "HunyuanRefinerLatent": { + "display_name": "HunyuanRefinerLatent", + "inputs": { + "latent": { + "name": "latente" + }, + "negative": { + "name": "negativo" + }, + "noise_augmentation": { + "name": "aumento_de_ruído" + }, + "positive": { + "name": "positivo" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latente", + "tooltip": null + } + } + }, + "HunyuanVideo15ImageToVideo": { + "display_name": "HunyuanVideo15ImageToVideo", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "duração" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "start_image": { + "name": "imagem_inicial" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latente", + "tooltip": null + } + } + }, + "HunyuanVideo15LatentUpscaleWithModel": { + "display_name": "Hunyuan Video 15 Latent Upscale With Model", + "inputs": { + "crop": { + "name": "corte" + }, + "height": { + "name": "altura" + }, + "model": { + "name": "modelo" + }, + "samples": { + "name": "amostras" + }, + "upscale_method": { + "name": "método_de_upscale" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "HunyuanVideo15SuperResolution": { + "display_name": "HunyuanVideo15SuperResolution", + "inputs": { + "clip_vision_output": { + "name": "clip_vision_output" + }, + "latent": { + "name": "latente" + }, + "negative": { + "name": "negativo" + }, + "noise_augmentation": { + "name": "aumento_de_ruído" + }, + "positive": { + "name": "positivo" + }, + "start_image": { + "name": "imagem_inicial" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latente", + "tooltip": null + } + } + }, + "HyperTile": { + "display_name": "HyperTile", + "inputs": { + "max_depth": { + "name": "profundidade_máxima" + }, + "model": { + "name": "modelo" + }, + "scale_depth": { + "name": "escala_de_profundidade" + }, + "swap_size": { + "name": "tamanho_da_troca" + }, + "tile_size": { + "name": "tamanho_do_tile" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "HypernetworkLoader": { + "display_name": "HypernetworkLoader", + "inputs": { + "hypernetwork_name": { + "name": "nome_da_hypernetwork" + }, + "model": { + "name": "modelo" + }, + "strength": { + "name": "força" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "IdeogramV1": { + "description": "Gera imagens usando o modelo Ideogram V1.", + "display_name": "Ideogram V1", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "A proporção da imagem para geração." + }, + "control_after_generate": { + "name": "control after generate" + }, + "magic_prompt_option": { + "name": "magic_prompt_option", + "tooltip": "Determina se o MagicPrompt deve ser usado na geração" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Descrição do que deve ser excluído da imagem" + }, + "num_images": { + "name": "num_images" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para a geração da imagem" + }, + "seed": { + "name": "seed" + }, + "turbo": { + "name": "turbo", + "tooltip": "Se deve usar o modo turbo (geração mais rápida, potencialmente menor qualidade)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "IdeogramV2": { + "description": "Gera imagens usando o modelo Ideogram V2.", + "display_name": "Ideogram V2", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "A proporção da imagem para geração. Ignorado se a resolução não estiver definida como AUTO." + }, + "control_after_generate": { + "name": "control after generate" + }, + "magic_prompt_option": { + "name": "magic_prompt_option", + "tooltip": "Determina se o MagicPrompt deve ser usado na geração" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Descrição do que deve ser excluído da imagem" + }, + "num_images": { + "name": "num_images" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para a geração da imagem" + }, + "resolution": { + "name": "resolution", + "tooltip": "A resolução para geração da imagem. Se não estiver definida como AUTO, esta opção sobrescreve a configuração de aspect_ratio." + }, + "seed": { + "name": "seed" + }, + "style_type": { + "name": "style_type", + "tooltip": "Tipo de estilo para geração (apenas V2)" + }, + "turbo": { + "name": "turbo", + "tooltip": "Se deve usar o modo turbo (geração mais rápida, potencialmente menor qualidade)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "IdeogramV3": { + "description": "Gera imagens usando o modelo Ideogram V3. Suporta tanto a geração de imagens a partir de prompts de texto quanto a edição de imagens com máscara.", + "display_name": "Ideogram V3", + "inputs": { + "aspect_ratio": { + "name": "proporção", + "tooltip": "A proporção para geração de imagem. Ignorado se a resolução não estiver definida como Automática." + }, + "character_image": { + "name": "imagem_personagem", + "tooltip": "Imagem para usar como referência de personagem." + }, + "character_mask": { + "name": "máscara_personagem", + "tooltip": "Máscara opcional para a imagem de referência do personagem." + }, + "control_after_generate": { + "name": "controle_após_gerar" + }, + "image": { + "name": "imagem", + "tooltip": "Imagem de referência opcional para edição de imagem." + }, + "magic_prompt_option": { + "name": "opção_magic_prompt", + "tooltip": "Determina se o MagicPrompt deve ser usado na geração" + }, + "mask": { + "name": "máscara", + "tooltip": "Máscara opcional para inpainting (áreas brancas serão substituídas)" + }, + "num_images": { + "name": "número_de_imagens" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para geração ou edição de imagem" + }, + "rendering_speed": { + "name": "velocidade_de_renderização", + "tooltip": "Controla o equilíbrio entre velocidade de geração e qualidade" + }, + "resolution": { + "name": "resolução", + "tooltip": "A resolução para geração de imagem. Se não estiver definida como Automática, esta opção sobrescreve a configuração de proporção." + }, + "seed": { + "name": "semente" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageAddNoise": { + "display_name": "Adicionar Ruído à Imagem", + "inputs": { + "control_after_generate": { + "name": "controle_após_gerar" + }, + "image": { + "name": "imagem" + }, + "seed": { + "name": "semente", + "tooltip": "A semente aleatória usada para criar o ruído." + }, + "strength": { + "name": "intensidade" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageBatch": { + "display_name": "Imagens em Lote", + "inputs": { + "image1": { + "name": "imagem1" + }, + "image2": { + "name": "imagem2" + } + } + }, + "ImageBlend": { + "display_name": "Misturar Imagens", + "inputs": { + "blend_factor": { + "name": "fator_de_mistura" + }, + "blend_mode": { + "name": "modo_de_mistura" + }, + "image1": { + "name": "imagem1" + }, + "image2": { + "name": "imagem2" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageBlur": { + "display_name": "Desfoque de Imagem", + "inputs": { + "blur_radius": { + "name": "raio_de_desfoque" + }, + "image": { + "name": "imagem" + }, + "sigma": { + "name": "sigma" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageColorToMask": { + "display_name": "Cor da Imagem para Máscara", + "inputs": { + "color": { + "name": "cor" + }, + "image": { + "name": "imagem" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageCompare": { + "description": "Compara duas imagens lado a lado com um controle deslizante.", + "display_name": "Comparar Imagens", + "inputs": { + "compare_view": { + "name": "compare_view" + }, + "image_a": { + "name": "imagem_a" + }, + "image_b": { + "name": "imagem_b" + } + } + }, + "ImageCompositeMasked": { + "display_name": "Composição de Imagem com Máscara", + "inputs": { + "destination": { + "name": "destino" + }, + "mask": { + "name": "máscara" + }, + "resize_source": { + "name": "redimensionar_fonte" + }, + "source": { + "name": "fonte" + }, + "x": { + "name": "x" + }, + "y": { + "name": "y" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageCrop": { + "display_name": "Cortar Imagem", + "inputs": { + "height": { + "name": "altura" + }, + "image": { + "name": "imagem" + }, + "width": { + "name": "largura" + }, + "x": { + "name": "x" + }, + "y": { + "name": "y" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageDeduplication": { + "display_name": "Remover Duplicatas de Imagem", + "inputs": { + "images": { + "name": "imagens", + "tooltip": "Lista de imagens para processar." + }, + "similarity_threshold": { + "name": "limite_de_semelhança", + "tooltip": "Limite de semelhança (0-1). Quanto maior, mais semelhantes. Imagens acima deste limite são consideradas duplicatas." + } + }, + "outputs": { + "0": { + "name": "imagens", + "tooltip": "Imagens processadas" + } + } + }, + "ImageFlip": { + "display_name": "Espelhar Imagem", + "inputs": { + "flip_method": { + "name": "método_de_espelhamento" + }, + "image": { + "name": "imagem" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageFromBatch": { + "display_name": "Imagem do Lote", + "inputs": { + "batch_index": { + "name": "índice_do_lote" + }, + "image": { + "name": "imagem" + }, + "length": { + "name": "comprimento" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageGrid": { + "display_name": "Grade de Imagens", + "inputs": { + "cell_height": { + "name": "altura_da_célula", + "tooltip": "Altura de cada célula na grade." + }, + "cell_width": { + "name": "largura_da_célula", + "tooltip": "Largura de cada célula na grade." + }, + "columns": { + "name": "colunas", + "tooltip": "Número de colunas na grade." + }, + "images": { + "name": "imagens", + "tooltip": "Lista de imagens para processar." + }, + "padding": { + "name": "espaçamento", + "tooltip": "Espaçamento entre as imagens." + } + }, + "outputs": { + "0": { + "name": "imagens", + "tooltip": "Imagens processadas" + } + } + }, + "ImageInvert": { + "display_name": "Inverter Imagem", + "inputs": { + "image": { + "name": "imagem" + } + } + }, + "ImageOnlyCheckpointLoader": { + "display_name": "Carregador de Checkpoint Somente Imagem (modelo img2vid)", + "inputs": { + "ckpt_name": { + "name": "nome_do_ckpt" + } + } + }, + "ImageOnlyCheckpointSave": { + "display_name": "Salvar Checkpoint Somente Imagem", + "inputs": { + "clip_vision": { + "name": "clip_vision" + }, + "filename_prefix": { + "name": "prefixo_do_arquivo" + }, + "model": { + "name": "modelo" + }, + "vae": { + "name": "vae" + } + } + }, + "ImagePadForOutpaint": { + "display_name": "Adicionar Bordas para Outpainting", + "inputs": { + "bottom": { + "name": "inferior" + }, + "feathering": { + "name": "suavização" + }, + "image": { + "name": "imagem" + }, + "left": { + "name": "esquerda" + }, + "right": { + "name": "direita" + }, + "top": { + "name": "superior" + } + } + }, + "ImageQuantize": { + "display_name": "Quantizar Imagem", + "inputs": { + "colors": { + "name": "cores" + }, + "dither": { + "name": "dithering" + }, + "image": { + "name": "imagem" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageRGBToYUV": { + "display_name": "Converter RGB para YUV", + "inputs": { + "image": { + "name": "imagem" + } + }, + "outputs": { + "0": { + "name": "Y", + "tooltip": null + }, + "1": { + "name": "U", + "tooltip": null + }, + "2": { + "name": "V", + "tooltip": null + } + } + }, + "ImageRotate": { + "display_name": "Rotacionar Imagem", + "inputs": { + "image": { + "name": "imagem" + }, + "rotation": { + "name": "rotação" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageScale": { + "display_name": "Aumentar Resolução da Imagem", + "inputs": { + "crop": { + "name": "cortar" + }, + "height": { + "name": "altura" + }, + "image": { + "name": "imagem" + }, + "upscale_method": { + "name": "método de upscaling" + }, + "width": { + "name": "largura" + } + } + }, + "ImageScaleBy": { + "display_name": "Aumentar Imagem Por", + "inputs": { + "image": { + "name": "imagem" + }, + "scale_by": { + "name": "fator de escala" + }, + "upscale_method": { + "name": "método de upscaling" + } + } + }, + "ImageScaleToMaxDimension": { + "display_name": "Redimensionar Imagem para Dimensão Máxima", + "inputs": { + "image": { + "name": "imagem" + }, + "largest_size": { + "name": "maior tamanho" + }, + "upscale_method": { + "name": "método de upscaling" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageScaleToTotalPixels": { + "display_name": "Redimensionar Imagem para Total de Pixels", + "inputs": { + "image": { + "name": "imagem" + }, + "megapixels": { + "name": "megapixels" + }, + "resolution_steps": { + "name": "etapas de resolução" + }, + "upscale_method": { + "name": "método de upscaling" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageSharpen": { + "display_name": "Aumentar Nitidez da Imagem", + "inputs": { + "alpha": { + "name": "alfa" + }, + "image": { + "name": "imagem" + }, + "sharpen_radius": { + "name": "raio de nitidez" + }, + "sigma": { + "name": "sigma" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageStitch": { + "description": "Costura a imagem2 na imagem1 na direção especificada.\nSe a imagem2 não for fornecida, retorna a imagem1 inalterada.\nUm espaçamento opcional pode ser adicionado entre as imagens.", + "display_name": "Costurar Imagem", + "inputs": { + "direction": { + "name": "direção" + }, + "image1": { + "name": "imagem1" + }, + "image2": { + "name": "imagem2" + }, + "match_image_size": { + "name": "ajustar_tamanho_imagem" + }, + "spacing_color": { + "name": "cor_espacamento" + }, + "spacing_width": { + "name": "largura_espacamento" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageToMask": { + "display_name": "Converter Imagem em Máscara", + "inputs": { + "channel": { + "name": "canal" + }, + "image": { + "name": "imagem" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageUpscaleWithModel": { + "display_name": "Aumentar Resolução da Imagem (usando Modelo)", + "inputs": { + "image": { + "name": "imagem" + }, + "upscale_model": { + "name": "modelo_upscale" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageYUVToRGB": { + "display_name": "ImageYUVToRGB", + "inputs": { + "U": { + "name": "U" + }, + "V": { + "name": "V" + }, + "Y": { + "name": "Y" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "InpaintModelConditioning": { + "display_name": "InpaintModelConditioning", + "inputs": { + "mask": { + "name": "máscara" + }, + "negative": { + "name": "negativo" + }, + "noise_mask": { + "name": "máscara_ruído", + "tooltip": "Adiciona uma máscara de ruído ao latent para que a amostragem ocorra apenas dentro da máscara. Pode melhorar os resultados ou causar falhas dependendo do modelo." + }, + "pixels": { + "name": "pixels" + }, + "positive": { + "name": "positivo" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "positivo" + }, + "1": { + "name": "negativo" + }, + "2": { + "name": "latent" + } + } + }, + "InstructPixToPixConditioning": { + "display_name": "InstructPixToPixConditioning", + "inputs": { + "negative": { + "name": "negativo" + }, + "pixels": { + "name": "pixels" + }, + "positive": { + "name": "positivo" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "InvertMask": { + "display_name": "Inverter Máscara", + "inputs": { + "mask": { + "name": "máscara" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "JoinAudioChannels": { + "description": "Une os canais de áudio mono esquerdo e direito em um áudio estéreo.", + "display_name": "Unir Canais de Áudio", + "inputs": { + "audio_left": { + "name": "áudio_esquerdo" + }, + "audio_right": { + "name": "áudio_direito" + } + }, + "outputs": { + "0": { + "name": "áudio", + "tooltip": null + } + } + }, + "JoinImageWithAlpha": { + "display_name": "Unir Imagem com Alfa", + "inputs": { + "alpha": { + "name": "alfa" + }, + "image": { + "name": "imagem" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KSampler": { + "description": "Usa o modelo fornecido, condicionamento positivo e negativo para remover o ruído da imagem latente.", + "display_name": "KSampler", + "inputs": { + "cfg": { + "name": "cfg", + "tooltip": "A escala de Classifier-Free Guidance equilibra criatividade e aderência ao prompt. Valores mais altos resultam em imagens mais próximas do prompt, porém valores muito altos podem impactar negativamente a qualidade." + }, + "control_after_generate": { + "name": "controlar após gerar" + }, + "denoise": { + "name": "denoise", + "tooltip": "A quantidade de remoção de ruído aplicada; valores menores mantêm a estrutura da imagem inicial, permitindo amostragem de imagem para imagem." + }, + "latent_image": { + "name": "imagem_latente", + "tooltip": "A imagem latente a ser denoised." + }, + "model": { + "name": "modelo", + "tooltip": "O modelo utilizado para remover o ruído do latent de entrada." + }, + "negative": { + "name": "negativo", + "tooltip": "O condicionamento que descreve os atributos que você deseja excluir da imagem." + }, + "positive": { + "name": "positivo", + "tooltip": "O condicionamento que descreve os atributos que você deseja incluir na imagem." + }, + "sampler_name": { + "name": "nome_do_sampler", + "tooltip": "O algoritmo utilizado na amostragem, isso pode afetar a qualidade, velocidade e estilo do resultado gerado." + }, + "scheduler": { + "name": "agendador", + "tooltip": "O agendador controla como o ruído é gradualmente removido para formar a imagem." + }, + "seed": { + "name": "semente", + "tooltip": "A semente aleatória usada para criar o ruído." + }, + "steps": { + "name": "passos", + "tooltip": "O número de passos usados no processo de remoção de ruído." + } + }, + "outputs": { + "0": { + "tooltip": "O latent denoised." + } + } + }, + "KSamplerAdvanced": { + "display_name": "KSampler (Avançado)", + "inputs": { + "add_noise": { + "name": "adicionar_ruído" + }, + "cfg": { + "name": "cfg" + }, + "control_after_generate": { + "name": "controlar após gerar" + }, + "end_at_step": { + "name": "terminar_no_passo" + }, + "latent_image": { + "name": "imagem_latente" + }, + "model": { + "name": "modelo" + }, + "negative": { + "name": "negativo" + }, + "noise_seed": { + "name": "semente_do_ruído" + }, + "positive": { + "name": "positivo" + }, + "return_with_leftover_noise": { + "name": "retornar_com_ruído_restante" + }, + "sampler_name": { + "name": "nome_do_sampler" + }, + "scheduler": { + "name": "agendador" + }, + "start_at_step": { + "name": "iniciar_no_passo" + }, + "steps": { + "name": "passos" + } + } + }, + "KSamplerSelect": { + "display_name": "KSamplerSelect", + "inputs": { + "sampler_name": { + "name": "nome_do_sampler" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Kandinsky5ImageToVideo": { + "display_name": "Kandinsky5ImageToVideo", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "duração" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "start_image": { + "name": "imagem_inicial" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latente", + "tooltip": "Latente de vídeo vazio" + }, + "3": { + "name": "latente_cond", + "tooltip": "Imagens iniciais codificadas limpas, usadas para substituir o início ruidoso dos latentes de saída do modelo" + } + } + }, + "KarrasScheduler": { + "display_name": "KarrasScheduler", + "inputs": { + "rho": { + "name": "rho" + }, + "sigma_max": { + "name": "sigma_max" + }, + "sigma_min": { + "name": "sigma_min" + }, + "steps": { + "name": "etapas" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingCameraControlI2VNode": { + "description": "Transforme imagens estáticas em vídeos cinematográficos com movimentos de câmera profissionais que simulam a cinematografia do mundo real. Controle ações virtuais da câmera, incluindo zoom, rotação, panorâmica, inclinação e visão em primeira pessoa, mantendo o foco na sua imagem original.", + "display_name": "Kling Imagem para Vídeo (Controle de Câmera)", + "inputs": { + "aspect_ratio": { + "name": "proporcao" + }, + "camera_control": { + "name": "controle_de_camera", + "tooltip": "Pode ser criado usando o nó Kling Camera Controls. Controla o movimento e a ação da câmera durante a geração do vídeo." + }, + "cfg_scale": { + "name": "cfg_scale" + }, + "negative_prompt": { + "name": "prompt_negativo", + "tooltip": "Prompt de texto negativo" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto positivo" + }, + "start_frame": { + "name": "quadro_inicial", + "tooltip": "Imagem de referência - URL ou string codificada em Base64, não pode exceder 10MB, resolução não inferior a 300*300px, proporção entre 1:2.5 ~ 2.5:1. Base64 não deve incluir o prefixo data:image." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "id_video", + "tooltip": null + }, + "2": { + "name": "duracao", + "tooltip": null + } + } + }, + "KlingCameraControlT2VNode": { + "description": "Transforme texto em vídeos cinematográficos com movimentos de câmera profissionais que simulam a cinematografia do mundo real. Controle ações da câmera virtual incluindo zoom, rotação, pan, tilt e visão em primeira pessoa, mantendo o foco no seu texto original.", + "display_name": "Kling Texto para Vídeo (Controle de Câmera)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "camera_control": { + "name": "camera_control", + "tooltip": "Pode ser criado usando o nó Controles de Câmera Kling. Controla o movimento e a ação da câmera durante a geração do vídeo." + }, + "cfg_scale": { + "name": "cfg_scale" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Prompt de texto negativo" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto positivo" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "video_id", + "tooltip": null + }, + "2": { + "name": "duration", + "tooltip": null + } + } + }, + "KlingCameraControls": { + "description": "Permite especificar opções de configuração para os Controles de Câmera Kling e efeitos de controle de movimento.", + "display_name": "Controles de Câmera Kling", + "inputs": { + "camera_control_type": { + "name": "camera_control_type" + }, + "horizontal_movement": { + "name": "horizontal_movement", + "tooltip": "Controla o movimento da câmera ao longo do eixo horizontal (eixo x). Negativo indica para a esquerda, positivo indica para a direita" + }, + "pan": { + "name": "pan", + "tooltip": "Controla a rotação da câmera no plano vertical (eixo x). Negativo indica rotação para baixo, positivo indica rotação para cima." + }, + "roll": { + "name": "roll", + "tooltip": "Controla a quantidade de rotação da câmera (eixo z). Negativo indica sentido anti-horário, positivo indica sentido horário." + }, + "tilt": { + "name": "tilt", + "tooltip": "Controla a rotação da câmera no plano horizontal (eixo y). Negativo indica rotação para a esquerda, positivo indica rotação para a direita." + }, + "vertical_movement": { + "name": "vertical_movement", + "tooltip": "Controla o movimento da câmera ao longo do eixo vertical (eixo y). Negativo indica para baixo, positivo indica para cima." + }, + "zoom": { + "name": "zoom", + "tooltip": "Controla a alteração da distância focal da câmera. Negativo indica campo de visão mais estreito, positivo indica campo de visão mais amplo." + } + }, + "outputs": { + "0": { + "name": "camera_control", + "tooltip": null + } + } + }, + "KlingDualCharacterVideoEffectNode": { + "description": "Alcance diferentes efeitos especiais ao gerar um vídeo com base no effect_scene. A primeira imagem será posicionada no lado esquerdo, a segunda no lado direito da composição.", + "display_name": "Efeitos de Vídeo com Dois Personagens Kling", + "inputs": { + "duration": { + "name": "duration" + }, + "effect_scene": { + "name": "effect_scene" + }, + "image_left": { + "name": "image_left", + "tooltip": "Imagem do lado esquerdo" + }, + "image_right": { + "name": "image_right", + "tooltip": "Imagem do lado direito" + }, + "mode": { + "name": "mode" + }, + "model_name": { + "name": "model_name" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "duration", + "tooltip": null + } + } + }, + "KlingImage2VideoNode": { + "display_name": "Kling Imagem (Primeiro Quadro) para Vídeo", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "cfg_scale": { + "name": "cfg_scale" + }, + "duration": { + "name": "duration" + }, + "mode": { + "name": "mode" + }, + "model_name": { + "name": "model_name" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Prompt de texto negativo" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto positivo" + }, + "start_frame": { + "name": "start_frame", + "tooltip": "A imagem de referência usada para gerar o vídeo." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "video_id", + "tooltip": null + }, + "2": { + "name": "duration", + "tooltip": null + } + } + }, + "KlingImageGenerationNode": { + "description": "Nó de Geração de Imagem Kling. Gere uma imagem a partir de um prompt de texto com uma imagem de referência opcional.", + "display_name": "Geração de Imagem Kling", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "human_fidelity": { + "name": "human_fidelity", + "tooltip": "Similaridade de referência do sujeito" + }, + "image": { + "name": "image" + }, + "image_fidelity": { + "name": "image_fidelity", + "tooltip": "Intensidade de referência para imagens enviadas pelo usuário" + }, + "image_type": { + "name": "image_type" + }, + "model_name": { + "name": "model_name" + }, + "n": { + "name": "n", + "tooltip": "Número de imagens geradas" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Prompt de texto negativo" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto positivo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingImageToVideoWithAudio": { + "display_name": "Kling Imagem (Primeiro Quadro) para Vídeo com Áudio", + "inputs": { + "duration": { + "name": "duration" + }, + "generate_audio": { + "name": "generate_audio" + }, + "mode": { + "name": "mode" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto positivo." + }, + "start_frame": { + "name": "start_frame" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingLipSyncAudioToVideoNode": { + "description": "Nó de Sincronização Labial Kling. Sincroniza os movimentos da boca em um arquivo de vídeo com o conteúdo de áudio de um arquivo de áudio. Ao usar, certifique-se de que o áudio contenha vocais claramente distinguíveis e que o vídeo contenha um rosto distinto. O arquivo de áudio não deve ser maior que 5MB. O arquivo de vídeo não deve ser maior que 100MB, deve ter altura/largura entre 720px e 1920px, e deve ter entre 2s e 10s de duração.", + "display_name": "Kling Sincronização Labial de Vídeo com Áudio", + "inputs": { + "audio": { + "name": "audio" + }, + "video": { + "name": "video" + }, + "voice_language": { + "name": "voice_language" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "video_id", + "tooltip": null + }, + "2": { + "name": "duration", + "tooltip": null + } + } + }, + "KlingLipSyncTextToVideoNode": { + "description": "Nó Kling Lip Sync Texto para Vídeo. Sincroniza os movimentos da boca em um arquivo de vídeo com um prompt de texto. O arquivo de vídeo não deve ser maior que 100MB, deve ter altura/largura entre 720px e 1920px, e duração entre 2s e 10s.", + "display_name": "Kling Lip Sync Vídeo com Texto", + "inputs": { + "text": { + "name": "texto", + "tooltip": "Conteúdo de texto para geração de vídeo com lip-sync. Obrigatório quando o modo for text2video. O comprimento máximo é de 120 caracteres." + }, + "video": { + "name": "vídeo" + }, + "voice": { + "name": "voz" + }, + "voice_speed": { + "name": "velocidade_da_voz", + "tooltip": "Velocidade da fala. Faixa válida: 0,8~2,0, com precisão de uma casa decimal." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "id_vídeo", + "tooltip": null + }, + "2": { + "name": "duração", + "tooltip": null + } + } + }, + "KlingMotionControl": { + "display_name": "Kling Controle de Movimento", + "inputs": { + "character_orientation": { + "name": "orientação_do_personagem", + "tooltip": "Controla de onde vem a orientação/posição do personagem.\nvídeo: movimentos, expressões, movimentos de câmera e orientação seguem o vídeo de referência de movimento (outros detalhes via prompt).\nimagem: movimentos e expressões ainda seguem o vídeo de referência de movimento, mas a orientação do personagem corresponde à imagem de referência (câmera/outros detalhes via prompt)." + }, + "keep_original_sound": { + "name": "manter_som_original" + }, + "mode": { + "name": "modo" + }, + "prompt": { + "name": "prompt" + }, + "reference_image": { + "name": "imagem_de_referência" + }, + "reference_video": { + "name": "vídeo_de_referência", + "tooltip": "Vídeo de referência de movimento usado para guiar o movimento/expressão.\nLimites de duração dependem de character_orientation:\n - imagem: 3–10s (máx 10s)\n - vídeo: 3–30s (máx 30s)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProEditVideoNode": { + "description": "Edite um vídeo existente com o modelo mais recente da Kling.", + "display_name": "Kling Omni Editar Vídeo (Pro)", + "inputs": { + "keep_original_sound": { + "name": "manter_som_original" + }, + "model_name": { + "name": "nome_do_modelo" + }, + "prompt": { + "name": "prompt", + "tooltip": "Um prompt de texto descrevendo o conteúdo do vídeo. Pode incluir descrições positivas e negativas." + }, + "reference_images": { + "name": "imagens_de_referência", + "tooltip": "Até 4 imagens de referência adicionais." + }, + "resolution": { + "name": "resolução" + }, + "video": { + "name": "vídeo", + "tooltip": "Vídeo para edição. O comprimento do vídeo de saída será o mesmo." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProFirstLastFrameNode": { + "description": "Use um quadro inicial, um quadro final opcional ou imagens de referência com o modelo mais recente da Kling.", + "display_name": "Kling Omni Quadro Inicial-Final para Vídeo (Pro)", + "inputs": { + "duration": { + "name": "duration" + }, + "end_frame": { + "name": "end_frame", + "tooltip": "Um quadro final opcional para o vídeo. Não pode ser usado simultaneamente com 'reference_images'." + }, + "first_frame": { + "name": "first_frame" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Um prompt de texto descrevendo o conteúdo do vídeo. Pode incluir descrições positivas e negativas." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "Até 6 imagens de referência adicionais." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageNode": { + "description": "Crie ou edite imagens com o modelo mais recente da Kling.", + "display_name": "Kling Omni Imagem (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Um prompt de texto descrevendo o conteúdo da imagem. Pode incluir descrições positivas e negativas." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "Até 10 imagens de referência adicionais." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageToVideoNode": { + "description": "Use até 7 imagens de referência para gerar um vídeo com o modelo mais recente da Kling.", + "display_name": "Kling Omni Imagem para Vídeo (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Um prompt de texto descrevendo o conteúdo do vídeo. Pode incluir descrições positivas e negativas." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "Até 7 imagens de referência." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProTextToVideoNode": { + "description": "Use prompts de texto para gerar vídeos com o modelo mais recente da Kling.", + "display_name": "Kling Omni Texto para Vídeo (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Um prompt de texto descrevendo o conteúdo do vídeo. Pode incluir descrições positivas e negativas." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProVideoToVideoNode": { + "description": "Use um vídeo e até 4 imagens de referência para gerar um vídeo com o modelo Kling mais recente.", + "display_name": "Kling Omni Vídeo para Vídeo (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "keep_original_sound": { + "name": "keep_original_sound" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Um prompt de texto descrevendo o conteúdo do vídeo. Pode incluir descrições positivas e negativas." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "Até 4 imagens de referência adicionais." + }, + "reference_video": { + "name": "reference_video", + "tooltip": "Vídeo para usar como referência." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingSingleImageVideoEffectNode": { + "description": "Alcance diferentes efeitos especiais ao gerar um vídeo com base no effect_scene.", + "display_name": "Efeitos de Vídeo Kling", + "inputs": { + "duration": { + "name": "duration" + }, + "effect_scene": { + "name": "effect_scene" + }, + "image": { + "name": "image", + "tooltip": "Imagem de referência. URL ou string codificada em Base64 (sem o prefixo data:image). O tamanho do arquivo não pode exceder 10MB, resolução não inferior a 300x300px, proporção entre 1:2.5 ~ 2.5:1" + }, + "model_name": { + "name": "model_name" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "video_id", + "tooltip": null + }, + "2": { + "name": "duration", + "tooltip": null + } + } + }, + "KlingStartEndFrameNode": { + "description": "Gere uma sequência de vídeo que faz a transição entre as imagens inicial e final fornecidas. O nó cria todos os quadros intermediários, produzindo uma transformação suave do primeiro ao último quadro.", + "display_name": "Kling Quadro Inicial-Final para Vídeo", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "cfg_scale": { + "name": "cfg_scale" + }, + "end_frame": { + "name": "end_frame", + "tooltip": "Imagem de referência - Controle do quadro final. URL ou string Base64, não pode exceder 10MB, resolução não inferior a 300x300px. Base64 não deve incluir o prefixo data:image." + }, + "mode": { + "name": "mode", + "tooltip": "A configuração a ser usada para a geração do vídeo seguindo o formato: mode / duration / model_name." + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Prompt de texto negativo" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto positivo" + }, + "start_frame": { + "name": "start_frame", + "tooltip": "Imagem de referência - URL ou string Base64, não pode exceder 10MB, resolução não inferior a 300x300px, proporção entre 1:2.5 ~ 2.5:1. Base64 não deve incluir o prefixo data:image." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "video_id", + "tooltip": null + }, + "2": { + "name": "duration", + "tooltip": null + } + } + }, + "KlingTextToVideoNode": { + "description": "Nó Kling Texto para Vídeo", + "display_name": "Kling Texto para Vídeo", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "cfg_scale": { + "name": "cfg_scale" + }, + "mode": { + "name": "mode", + "tooltip": "A configuração a ser usada para a geração do vídeo seguindo o formato: modo / duração / model_name." + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Prompt de texto negativo" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto positivo" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "video_id", + "tooltip": null + }, + "2": { + "name": "duration", + "tooltip": null + } + } + }, + "KlingTextToVideoWithAudio": { + "display_name": "Kling Texto para Vídeo com Áudio", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "generate_audio": { + "name": "generate_audio" + }, + "mode": { + "name": "mode" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto positivo." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingVideoExtendNode": { + "description": "Nó Kling Extender Vídeo. Estenda vídeos criados por outros nós Kling. O video_id é criado usando outros Nós Kling.", + "display_name": "Kling Extender Vídeo", + "inputs": { + "cfg_scale": { + "name": "cfg_scale" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Prompt de texto negativo para elementos a serem evitados no vídeo estendido" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto positivo para guiar a extensão do vídeo" + }, + "video_id": { + "name": "video_id", + "tooltip": "O ID do vídeo a ser estendido. Suporta vídeos gerados por texto para vídeo, imagem para vídeo e operações anteriores de extensão de vídeo. Não pode exceder 3 minutos de duração total após a extensão." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "video_id", + "tooltip": null + }, + "2": { + "name": "duration", + "tooltip": null + } + } + }, + "KlingVirtualTryOnNode": { + "description": "Nó Kling Prova Virtual. Insira uma imagem de uma pessoa e uma imagem de roupa para experimentar a roupa na pessoa. Você pode mesclar várias imagens de peças de roupa em uma única imagem com fundo branco.", + "display_name": "Kling Prova Virtual", + "inputs": { + "cloth_image": { + "name": "cloth_image" + }, + "human_image": { + "name": "human_image" + }, + "model_name": { + "name": "model_name" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LTXAVTextEncoderLoader": { + "description": "[Receitas]\n\nltxav: gemma 3 12B", + "display_name": "Carregador de Codificador de Texto LTXV Áudio", + "inputs": { + "ckpt_name": { + "name": "ckpt_name" + }, + "device": { + "name": "device" + }, + "text_encoder": { + "name": "text_encoder" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LTXVAddGuide": { + "display_name": "LTXVAddGuide", + "inputs": { + "frame_idx": { + "name": "frame_idx", + "tooltip": "Índice do quadro para iniciar o condicionamento. Para imagens de um quadro ou vídeos com 1-8 quadros, qualquer valor de frame_idx é aceitável. Para vídeos com 9+ quadros, frame_idx deve ser divisível por 8, caso contrário será arredondado para baixo para o múltiplo de 8 mais próximo. Valores negativos são contados a partir do final do vídeo." + }, + "image": { + "name": "image", + "tooltip": "Imagem ou vídeo para condicionar o vídeo latente. Deve ter 8*n + 1 quadros. Se o vídeo não tiver 8*n + 1 quadros, será cortado para o múltiplo mais próximo de 8*n + 1 quadros." + }, + "latent": { + "name": "latent" + }, + "negative": { + "name": "negative" + }, + "positive": { + "name": "positive" + }, + "strength": { + "name": "strength" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "positive", + "tooltip": null + }, + "1": { + "name": "negative", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "LTXVAudioVAEDecode": { + "display_name": "LTXV Decodificar Áudio VAE", + "inputs": { + "audio_vae": { + "name": "audio_vae", + "tooltip": "O modelo Audio VAE usado para decodificar o latent." + }, + "samples": { + "name": "amostras", + "tooltip": "O latent a ser decodificado." + } + }, + "outputs": { + "0": { + "name": "Áudio", + "tooltip": null + } + } + }, + "LTXVAudioVAEEncode": { + "display_name": "LTXV Codificar Áudio VAE", + "inputs": { + "audio": { + "name": "áudio", + "tooltip": "O áudio a ser codificado." + }, + "audio_vae": { + "name": "audio_vae", + "tooltip": "O modelo Audio VAE a ser usado para codificação." + } + }, + "outputs": { + "0": { + "name": "Latent de Áudio", + "tooltip": null + } + } + }, + "LTXVAudioVAELoader": { + "display_name": "LTXV Carregar Áudio VAE", + "inputs": { + "ckpt_name": { + "name": "nome_ckpt", + "tooltip": "Checkpoint do Audio VAE para carregar." + } + }, + "outputs": { + "0": { + "name": "Audio VAE", + "tooltip": null + } + } + }, + "LTXVConcatAVLatent": { + "display_name": "LTXVConcatAVLatent", + "inputs": { + "audio_latent": { + "name": "audio_latent" + }, + "video_latent": { + "name": "video_latent" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, + "LTXVConditioning": { + "display_name": "LTXVConditioning", + "inputs": { + "frame_rate": { + "name": "taxa_de_quadros" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + } + } + }, + "LTXVCropGuides": { + "display_name": "LTXVCropGuides", + "inputs": { + "latent": { + "name": "latent" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "LTXVEmptyLatentAudio": { + "display_name": "LTXV Áudio Latente Vazio", + "inputs": { + "audio_vae": { + "name": "audio_vae", + "tooltip": "O modelo Audio VAE do qual obter a configuração." + }, + "batch_size": { + "name": "batch_size", + "tooltip": "Quantidade de amostras de áudio latente no lote." + }, + "frame_rate": { + "name": "frame_rate", + "tooltip": "Número de quadros por segundo." + }, + "frames_number": { + "name": "frames_number", + "tooltip": "Número de quadros." + } + }, + "outputs": { + "0": { + "name": "Latent", + "tooltip": null + } + } + }, + "LTXVImgToVideo": { + "display_name": "LTXVImgToVideo", + "inputs": { + "batch_size": { + "name": "batch_size" + }, + "height": { + "name": "height" + }, + "image": { + "name": "image" + }, + "length": { + "name": "length" + }, + "negative": { + "name": "negative" + }, + "positive": { + "name": "positive" + }, + "strength": { + "name": "strength" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "width" + } + }, + "outputs": { + "0": { + "name": "positive", + "tooltip": null + }, + "1": { + "name": "negative", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "LTXVImgToVideoInplace": { + "display_name": "LTXVImgToVideoInplace", + "inputs": { + "bypass": { + "name": "bypass", + "tooltip": "Ignorar o condicionamento." + }, + "image": { + "name": "image" + }, + "latent": { + "name": "latent" + }, + "strength": { + "name": "strength" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, + "LTXVLatentUpsampler": { + "display_name": "LTXVLatentUpsampler", + "inputs": { + "samples": { + "name": "samples" + }, + "upscale_model": { + "name": "upscale_model" + }, + "vae": { + "name": "vae" + } + } + }, + "LTXVPreprocess": { + "display_name": "LTXVPreprocess", + "inputs": { + "image": { + "name": "image" + }, + "img_compression": { + "name": "img_compression", + "tooltip": "Quantidade de compressão a ser aplicada na imagem." + } + }, + "outputs": { + "0": { + "name": "output_image", + "tooltip": null + } + } + }, + "LTXVScheduler": { + "display_name": "LTXVScheduler", + "inputs": { + "base_shift": { + "name": "base_shift" + }, + "latent": { + "name": "latent" + }, + "max_shift": { + "name": "max_shift" + }, + "steps": { + "name": "steps" + }, + "stretch": { + "name": "stretch", + "tooltip": "Estique os sigmas para ficarem no intervalo [terminal, 1]." + }, + "terminal": { + "name": "terminal", + "tooltip": "O valor terminal dos sigmas após o estiramento." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LTXVSeparateAVLatent": { + "description": "LTXV Separar AV Latent", + "display_name": "LTXVSeparateAVLatent", + "inputs": { + "av_latent": { + "name": "av_latent" + } + }, + "outputs": { + "0": { + "name": "video_latent", + "tooltip": null + }, + "1": { + "name": "audio_latent", + "tooltip": null + } + } + }, + "LaplaceScheduler": { + "display_name": "LaplaceScheduler", + "inputs": { + "beta": { + "name": "beta" + }, + "mu": { + "name": "mu" + }, + "sigma_max": { + "name": "sigma_max" + }, + "sigma_min": { + "name": "sigma_min" + }, + "steps": { + "name": "passos" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentAdd": { + "display_name": "LatentAdd", + "inputs": { + "samples1": { + "name": "amostras1" + }, + "samples2": { + "name": "amostras2" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentApplyOperation": { + "display_name": "LatentApplyOperation", + "inputs": { + "operation": { + "name": "operação" + }, + "samples": { + "name": "amostras" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentApplyOperationCFG": { + "display_name": "LatentApplyOperationCFG", + "inputs": { + "model": { + "name": "modelo" + }, + "operation": { + "name": "operação" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentBatch": { + "display_name": "LatentBatch", + "inputs": { + "samples1": { + "name": "amostras1" + }, + "samples2": { + "name": "amostras2" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentBatchSeedBehavior": { + "display_name": "LatentBatchSeedBehavior", + "inputs": { + "samples": { + "name": "amostras" + }, + "seed_behavior": { + "name": "comportamento_da_semente" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentBlend": { + "display_name": "Latent Blend", + "inputs": { + "blend_factor": { + "name": "fator_de_mistura" + }, + "samples1": { + "name": "amostras1" + }, + "samples2": { + "name": "amostras2" + } + } + }, + "LatentComposite": { + "display_name": "Latent Composite", + "inputs": { + "feather": { + "name": "suavização" + }, + "samples_from": { + "name": "amostras_origem" + }, + "samples_to": { + "name": "amostras_destino" + }, + "x": { + "name": "x" + }, + "y": { + "name": "y" + } + } + }, + "LatentCompositeMasked": { + "display_name": "LatentCompositeMasked", + "inputs": { + "destination": { + "name": "destino" + }, + "mask": { + "name": "máscara" + }, + "resize_source": { + "name": "redimensionar_origem" + }, + "source": { + "name": "origem" + }, + "x": { + "name": "x" + }, + "y": { + "name": "y" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentConcat": { + "display_name": "LatentConcat", + "inputs": { + "dim": { + "name": "dimensão" + }, + "samples1": { + "name": "amostras1" + }, + "samples2": { + "name": "amostras2" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentCrop": { + "display_name": "Crop Latent", + "inputs": { + "height": { + "name": "altura" + }, + "samples": { + "name": "amostras" + }, + "width": { + "name": "largura" + }, + "x": { + "name": "x" + }, + "y": { + "name": "y" + } + } + }, + "LatentCut": { + "display_name": "LatentCut", + "inputs": { + "amount": { + "name": "quantidade" + }, + "dim": { + "name": "dimensão" + }, + "index": { + "name": "índice" + }, + "samples": { + "name": "amostras" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentCutToBatch": { + "display_name": "LatentCutToBatch", + "inputs": { + "dim": { + "name": "dimensão" + }, + "samples": { + "name": "amostras" + }, + "slice_size": { + "name": "tamanho_do_fatiamento" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentFlip": { + "display_name": "Inverter Latent", + "inputs": { + "flip_method": { + "name": "método_de_inversão" + }, + "samples": { + "name": "amostras" + } + } + }, + "LatentFromBatch": { + "display_name": "Latent de Lote", + "inputs": { + "batch_index": { + "name": "índice_do_lote" + }, + "length": { + "name": "comprimento" + }, + "samples": { + "name": "amostras" + } + } + }, + "LatentInterpolate": { + "display_name": "Interpolar Latent", + "inputs": { + "ratio": { + "name": "proporção" + }, + "samples1": { + "name": "amostras1" + }, + "samples2": { + "name": "amostras2" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentMultiply": { + "display_name": "Multiplicar Latent", + "inputs": { + "multiplier": { + "name": "multiplicador" + }, + "samples": { + "name": "amostras" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentOperationSharpen": { + "display_name": "LatentOperationSharpen", + "inputs": { + "alpha": { + "name": "alfa" + }, + "sharpen_radius": { + "name": "raio_de_nitidez" + }, + "sigma": { + "name": "sigma" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentOperationTonemapReinhard": { + "display_name": "LatentOperationTonemapReinhard", + "inputs": { + "multiplier": { + "name": "multiplicador" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentRotate": { + "display_name": "Rotacionar Latent", + "inputs": { + "rotation": { + "name": "rotação" + }, + "samples": { + "name": "amostras" + } + } + }, + "LatentSubtract": { + "display_name": "Subtrair Latent", + "inputs": { + "samples1": { + "name": "amostras1" + }, + "samples2": { + "name": "amostras2" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LatentUpscale": { + "display_name": "Aumentar Resolução Latent", + "inputs": { + "crop": { + "name": "corte" + }, + "height": { + "name": "altura" + }, + "samples": { + "name": "amostras" + }, + "upscale_method": { + "name": "método_de_upscale" + }, + "width": { + "name": "largura" + } + } + }, + "LatentUpscaleBy": { + "display_name": "Aumentar Resolução Latent Por", + "inputs": { + "samples": { + "name": "amostras" + }, + "scale_by": { + "name": "escalar_por" + }, + "upscale_method": { + "name": "método_de_upscale" + } + } + }, + "LatentUpscaleModelLoader": { + "display_name": "Carregar Modelo de Upscale Latent", + "inputs": { + "model_name": { + "name": "nome_do_modelo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LazyCache": { + "description": "Uma versão caseira do EasyCache - uma versão ainda 'mais fácil' do EasyCache de implementar. No geral, funciona pior que o EasyCache, mas melhor em alguns casos raros E tem compatibilidade universal com tudo no ComfyUI.", + "display_name": "LazyCache", + "inputs": { + "end_percent": { + "name": "percentual_final", + "tooltip": "A etapa relativa de amostragem para parar de usar o LazyCache." + }, + "model": { + "name": "modelo", + "tooltip": "O modelo ao qual adicionar o LazyCache." + }, + "reuse_threshold": { + "name": "limite_de_reutilização", + "tooltip": "O limite para reutilizar etapas em cache." + }, + "start_percent": { + "name": "percentual_inicial", + "tooltip": "A etapa relativa de amostragem para começar a usar o LazyCache." + }, + "verbose": { + "name": "detalhado", + "tooltip": "Se deve registrar informações detalhadas." + } + }, + "outputs": { + "0": { + "tooltip": "O modelo com LazyCache." + } + } + }, + "Load3D": { + "display_name": "Carregar 3D & Animação", + "inputs": { + "clear": { + }, + "height": { + "name": "altura" + }, + "image": { + "name": "imagem" + }, + "model_file": { + "name": "arquivo_do_modelo" + }, + "upload 3d model": { + }, + "upload extra resources": { + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "imagem", + "tooltip": null + }, + "1": { + "name": "mask", + "tooltip": null + }, + "2": { + "name": "caminho_malha", + "tooltip": null + }, + "3": { + "name": "normal", + "tooltip": null + }, + "4": { + "name": "info_câmera", + "tooltip": null + }, + "5": { + "name": "vídeo_gravado", + "tooltip": null + } + } + }, + "LoadAudio": { + "display_name": "Carregar Áudio", + "inputs": { + "audio": { + "name": "áudio" + }, + "audioUI": { + "name": "áudioUI" + }, + "upload": { + "name": "escolher arquivo para enviar" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LoadImage": { + "display_name": "Carregar Imagem", + "inputs": { + "image": { + "name": "imagem" + }, + "upload": { + "name": "escolher arquivo para enviar" + } + } + }, + "LoadImageDataSetFromFolder": { + "display_name": "Carregar Conjunto de Imagens da Pasta", + "inputs": { + "folder": { + "name": "pasta", + "tooltip": "A pasta de onde carregar as imagens." + } + }, + "outputs": { + "0": { + "name": "imagens", + "tooltip": "Lista de imagens carregadas" + } + } + }, + "LoadImageMask": { + "display_name": "Carregar Imagem (como Mask)", + "inputs": { + "channel": { + "name": "canal" + }, + "image": { + "name": "imagem" + }, + "upload": { + "name": "escolher arquivo para enviar" + } + } + }, + "LoadImageOutput": { + "description": "Carregue uma imagem da pasta de saída. Quando o botão de atualizar for clicado, o nó irá atualizar a lista de imagens e selecionar automaticamente a primeira imagem, facilitando a iteração.", + "display_name": "Carregar Imagem (dos Resultados)", + "inputs": { + "Auto-refresh after generation": { + }, + "image": { + "name": "imagem" + }, + "refresh": { + }, + "upload": { + "name": "escolher arquivo para enviar" + } + } + }, + "LoadImageTextDataSetFromFolder": { + "display_name": "Carregar Conjunto de Dados de Imagem e Texto da Pasta", + "inputs": { + "folder": { + "name": "pasta", + "tooltip": "A pasta de onde as imagens serão carregadas." + } + }, + "outputs": { + "0": { + "name": "imagens", + "tooltip": "Lista de imagens carregadas" + }, + "1": { + "name": "textos", + "tooltip": "Lista de legendas de texto" + } + } + }, + "LoadLatent": { + "display_name": "Carregar Latent", + "inputs": { + "latent": { + "name": "latent" + } + } + }, + "LoadTrainingDataset": { + "display_name": "Carregar Conjunto de Dados de Treinamento", + "inputs": { + "folder_name": { + "name": "nome_da_pasta", + "tooltip": "Nome da pasta contendo o conjunto de dados salvo (dentro do diretório de saída)." + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "Lista de dicionários de latent" + }, + "1": { + "name": "condicionamentos", + "tooltip": "Lista de listas de condicionamento" + } + } + }, + "LoadVideo": { + "display_name": "Carregar Vídeo", + "inputs": { + "file": { + "name": "arquivo" + }, + "upload": { + "name": "escolher arquivo para enviar" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LoraLoader": { + "description": "LoRAs são usados para modificar modelos de difusão e CLIP, alterando a forma como os latents são denoizados, como na aplicação de estilos. Vários nós LoRA podem ser conectados juntos.", + "display_name": "Carregar LoRA", + "inputs": { + "clip": { + "name": "clip", + "tooltip": "O modelo CLIP ao qual o LoRA será aplicado." + }, + "lora_name": { + "name": "nome_do_lora", + "tooltip": "O nome do LoRA." + }, + "model": { + "name": "modelo", + "tooltip": "O modelo de difusão ao qual o LoRA será aplicado." + }, + "strength_clip": { + "name": "força_clip", + "tooltip": "Quão fortemente modificar o modelo CLIP. Este valor pode ser negativo." + }, + "strength_model": { + "name": "força_modelo", + "tooltip": "Quão fortemente modificar o modelo de difusão. Este valor pode ser negativo." + } + }, + "outputs": { + "0": { + "tooltip": "O modelo de difusão modificado." + }, + "1": { + "tooltip": "O modelo CLIP modificado." + } + } + }, + "LoraLoaderModelOnly": { + "description": "LoRAs são usados para modificar modelos de difusão e CLIP, alterando a forma como os latents são denoizados, como na aplicação de estilos. Vários nós LoRA podem ser conectados juntos.", + "display_name": "LoraLoaderModelOnly", + "inputs": { + "lora_name": { + "name": "nome_do_lora" + }, + "model": { + "name": "modelo" + }, + "strength_model": { + "name": "força_modelo" + } + }, + "outputs": { + "0": { + "tooltip": "O modelo de difusão modificado." + } + } + }, + "LoraModelLoader": { + "display_name": "Carregar Modelo LoRA", + "inputs": { + "lora": { + "name": "lora", + "tooltip": "O modelo LoRA a ser aplicado ao modelo de difusão." + }, + "model": { + "name": "modelo", + "tooltip": "O modelo de difusão ao qual o LoRA será aplicado." + }, + "strength_model": { + "name": "força_modelo", + "tooltip": "Quão fortemente modificar o modelo de difusão. Este valor pode ser negativo." + } + }, + "outputs": { + "0": { + "name": "modelo", + "tooltip": "O modelo de difusão modificado." + } + } + }, + "LoraSave": { + "display_name": "Extrair e Salvar Lora", + "inputs": { + "bias_diff": { + "name": "bias_diff" + }, + "filename_prefix": { + "name": "filename_prefix" + }, + "lora_type": { + "name": "lora_type" + }, + "model_diff": { + "name": "model_diff", + "tooltip": "A saída do ModelSubtract a ser convertida em uma lora." + }, + "rank": { + "name": "rank" + }, + "text_encoder_diff": { + "name": "text_encoder_diff", + "tooltip": "A saída do CLIPSubtract a ser convertida em uma lora." + } + } + }, + "LossGraphNode": { + "display_name": "Plotar Gráfico de Loss", + "inputs": { + "filename_prefix": { + "name": "filename_prefix", + "tooltip": "Prefixo para a imagem do gráfico de loss salvo." + }, + "loss": { + "name": "loss", + "tooltip": "Mapa de loss do nó de treinamento." + } + } + }, + "LotusConditioning": { + "display_name": "LotusConditioning", + "outputs": { + "0": { + "name": "conditioning", + "tooltip": null + } + } + }, + "LtxvApiImageToVideo": { + "description": "Vídeos de qualidade profissional com duração e resolução personalizáveis a partir da imagem inicial.", + "display_name": "LTXV Imagem para Vídeo", + "inputs": { + "duration": { + "name": "duration" + }, + "fps": { + "name": "fps" + }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "Quando verdadeiro, o vídeo gerado incluirá áudio gerado por IA correspondente à cena." + }, + "image": { + "name": "image", + "tooltip": "Primeiro quadro a ser usado para o vídeo." + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt" + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LtxvApiTextToVideo": { + "description": "Vídeos de qualidade profissional com duração e resolução personalizáveis.", + "display_name": "LTXV Texto para Vídeo", + "inputs": { + "duration": { + "name": "duração" + }, + "fps": { + "name": "fps" + }, + "generate_audio": { + "name": "gerar_áudio", + "tooltip": "Quando verdadeiro, o vídeo gerado incluirá áudio gerado por IA correspondente à cena." + }, + "model": { + "name": "modelo" + }, + "prompt": { + "name": "prompt" + }, + "resolution": { + "name": "resolução" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LumaConceptsNode": { + "description": "Conceitos de Câmera para uso com os nós Luma Texto para Vídeo e Luma Imagem para Vídeo.", + "display_name": "Luma Concepts", + "inputs": { + "concept1": { + "name": "concept1" + }, + "concept2": { + "name": "concept2" + }, + "concept3": { + "name": "concept3" + }, + "concept4": { + "name": "concept4" + }, + "luma_concepts": { + "name": "luma_concepts", + "tooltip": "Conceitos de Câmera opcionais para adicionar aos escolhidos aqui." + } + }, + "outputs": { + "0": { + "name": "luma_concepts", + "tooltip": null + } + } + }, + "LumaImageModifyNode": { + "description": "Modifica imagens de forma síncrona com base no prompt e na proporção.", + "display_name": "Luma Imagem para Imagem", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "image": { + "name": "image" + }, + "image_weight": { + "name": "image_weight", + "tooltip": "Peso da imagem; quanto mais próximo de 1.0, menos a imagem será modificada." + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para a geração da imagem" + }, + "seed": { + "name": "seed", + "tooltip": "Seed para determinar se o nó deve ser executado novamente; os resultados reais são não determinísticos independentemente do seed." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LumaImageNode": { + "description": "Gera imagens de forma síncrona com base no prompt e na proporção.", + "display_name": "Luma Texto para Imagem", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "character_image": { + "name": "character_image", + "tooltip": "Imagens de referência de personagem; pode ser um lote de várias, até 4 imagens podem ser consideradas." + }, + "control_after_generate": { + "name": "control after generate" + }, + "image_luma_ref": { + "name": "image_luma_ref", + "tooltip": "Conexão do nó de Referência Luma para influenciar a geração com imagens de entrada; até 4 imagens podem ser consideradas." + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para a geração da imagem" + }, + "seed": { + "name": "seed", + "tooltip": "Seed para determinar se o nó deve ser executado novamente; os resultados reais são não determinísticos independentemente do seed." + }, + "style_image": { + "name": "style_image", + "tooltip": "Imagem de referência de estilo; apenas 1 imagem será usada." + }, + "style_image_weight": { + "name": "style_image_weight", + "tooltip": "Peso da imagem de estilo. Ignorado se nenhuma style_image for fornecida." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LumaImageToVideoNode": { + "description": "Gera vídeos de forma síncrona com base no prompt, imagens de entrada e output_size.", + "display_name": "Luma Imagem para Vídeo", + "inputs": { + "control_after_generate": { + "name": "controlar após gerar" + }, + "duration": { + "name": "duração" + }, + "first_image": { + "name": "primeira_imagem", + "tooltip": "Primeiro quadro do vídeo gerado." + }, + "last_image": { + "name": "última_imagem", + "tooltip": "Último quadro do vídeo gerado." + }, + "loop": { + "name": "loop" + }, + "luma_concepts": { + "name": "luma_concepts", + "tooltip": "Conceitos de Câmera opcionais para ditar o movimento da câmera via o nó Luma Concepts." + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para a geração do vídeo" + }, + "resolution": { + "name": "resolução" + }, + "seed": { + "name": "seed", + "tooltip": "Seed para determinar se o nó deve ser executado novamente; os resultados reais são não determinísticos independentemente do seed." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LumaReferenceNode": { + "description": "Armazena uma imagem e peso para uso com o nó Luma Gerar Imagem.", + "display_name": "Luma Referência", + "inputs": { + "image": { + "name": "imagem", + "tooltip": "Imagem para usar como referência." + }, + "luma_ref": { + "name": "luma_ref" + }, + "weight": { + "name": "peso", + "tooltip": "Peso da referência da imagem." + } + }, + "outputs": { + "0": { + "name": "luma_ref", + "tooltip": null + } + } + }, + "LumaVideoNode": { + "description": "Gera vídeos de forma síncrona com base no prompt e output_size.", + "display_name": "Luma Texto para Vídeo", + "inputs": { + "aspect_ratio": { + "name": "proporção" + }, + "control_after_generate": { + "name": "controlar após gerar" + }, + "duration": { + "name": "duração" + }, + "loop": { + "name": "loop" + }, + "luma_concepts": { + "name": "luma_concepts", + "tooltip": "Conceitos de Câmera opcionais para ditar o movimento da câmera via o nó Luma Concepts." + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para a geração do vídeo" + }, + "resolution": { + "name": "resolução" + }, + "seed": { + "name": "seed", + "tooltip": "Seed para determinar se o nó deve ser executado novamente; os resultados reais são não determinísticos independentemente do seed." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Mahiro": { + "description": "Modifica a orientação para escalar mais na 'direção' do prompt positivo do que na diferença entre o prompt negativo.", + "display_name": "Mahiro CFG", + "inputs": { + "model": { + "name": "model" + } + }, + "outputs": { + "0": { + "name": "patched_model", + "tooltip": null + } + } + }, + "MakeTrainingDataset": { + "display_name": "Criar Conjunto de Dados de Treinamento", + "inputs": { + "clip": { + "name": "clip", + "tooltip": "Modelo CLIP para codificar texto em condicionamento." + }, + "images": { + "name": "imagens", + "tooltip": "Lista de imagens para codificar." + }, + "texts": { + "name": "textos", + "tooltip": "Lista de legendas de texto. Pode ter comprimento n (correspondendo às imagens), 1 (repetido para todas) ou ser omitido (usa string vazia)." + }, + "vae": { + "name": "vae", + "tooltip": "Modelo VAE para codificar imagens em latents." + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "Lista de dicionários latents" + }, + "1": { + "name": "condicionamento", + "tooltip": "Lista de listas de condicionamento" + } + } + }, + "ManualSigmas": { + "display_name": "Sigmas Manuais", + "inputs": { + "sigmas": { + "name": "sigmas" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MaskComposite": { + "display_name": "Composição de Mask", + "inputs": { + "destination": { + "name": "destino" + }, + "operation": { + "name": "operação" + }, + "source": { + "name": "fonte" + }, + "x": { + "name": "x" + }, + "y": { + "name": "y" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MaskPreview": { + "description": "Salva as imagens de entrada no diretório de saída do ComfyUI.", + "display_name": "Pré-visualizar Mask", + "inputs": { + "mask": { + "name": "mask" + } + } + }, + "MaskToImage": { + "display_name": "Converter Mask em Imagem", + "inputs": { + "mask": { + "name": "mask" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MergeImageLists": { + "display_name": "Mesclar Listas de Imagens", + "inputs": { + "images": { + "name": "imagens", + "tooltip": "Lista de imagens para processar." + } + }, + "outputs": { + "0": { + "name": "imagens", + "tooltip": "Imagens processadas" + } + } + }, + "MergeTextLists": { + "display_name": "Mesclar Listas de Textos", + "inputs": { + "texts": { + "name": "textos", + "tooltip": "Lista de textos para processar." + } + }, + "outputs": { + "0": { + "name": "textos", + "tooltip": "Textos processados" + } + } + }, + "MeshyAnimateModelNode": { + "description": "Aplicar uma ação de animação específica a um personagem previamente rigado.", + "display_name": "Meshy: Animar Modelo", + "inputs": { + "action_id": { + "name": "action_id", + "tooltip": "Visite https://docs.meshy.ai/en/api/animation-library para uma lista de valores disponíveis." + }, + "rig_task_id": { + "name": "rig_task_id" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + } + } + }, + "MeshyImageToModelNode": { + "display_name": "Meshy: Imagem para Modelo", + "inputs": { + "control_after_generate": { + "name": "controle_após_gerar" + }, + "image": { + "name": "imagem" + }, + "model": { + "name": "modelo" + }, + "pose_mode": { + "name": "modo_de_pose", + "tooltip": "Especifique o modo de pose para o modelo gerado." + }, + "seed": { + "name": "semente", + "tooltip": "A semente controla se o nó deve ser executado novamente; os resultados são não determinísticos independentemente da semente." + }, + "should_remesh": { + "name": "refazer_malha", + "tooltip": "Quando definido como falso, retorna uma malha triangular não processada." + }, + "should_remesh_target_polycount": { + "name": "contagem_alvo_de_polígonos" + }, + "should_remesh_topology": { + "name": "topologia" + }, + "should_texture": { + "name": "gerar_textura", + "tooltip": "Determina se as texturas serão geradas. Definir como falso pula a fase de textura e retorna uma malha sem texturas." + }, + "should_texture_enable_pbr": { + "name": "habilitar_pbr" + }, + "should_texture_texture_prompt": { + "name": "prompt_de_textura" + }, + "symmetry_mode": { + "name": "modo_de_simetria" + } + }, + "outputs": { + "0": { + "name": "arquivo_do_modelo", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyMultiImageToModelNode": { + "display_name": "Meshy: Múltiplas Imagens para Modelo", + "inputs": { + "control_after_generate": { + "name": "controle_após_gerar" + }, + "images": { + "name": "imagens" + }, + "model": { + "name": "modelo" + }, + "pose_mode": { + "name": "modo_de_pose", + "tooltip": "Especifique o modo de pose para o modelo gerado." + }, + "seed": { + "name": "semente", + "tooltip": "A semente controla se o nó deve ser executado novamente; os resultados são não determinísticos independentemente da semente." + }, + "should_remesh": { + "name": "refazer_malha", + "tooltip": "Quando definido como falso, retorna uma malha triangular não processada." + }, + "should_remesh_target_polycount": { + "name": "contagem_alvo_de_polígonos" + }, + "should_remesh_topology": { + "name": "topologia" + }, + "should_texture": { + "name": "gerar_textura", + "tooltip": "Determina se as texturas serão geradas. Definir como falso pula a fase de textura e retorna uma malha sem texturas." + }, + "should_texture_enable_pbr": { + "name": "habilitar_pbr" + }, + "should_texture_texture_prompt": { + "name": "prompt_de_textura" + }, + "symmetry_mode": { + "name": "modo_de_simetria" + } + }, + "outputs": { + "0": { + "name": "arquivo_do_modelo", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRefineNode": { + "description": "Refine um modelo rascunho criado anteriormente.", + "display_name": "Meshy: Refinar Modelo Rascunho", + "inputs": { + "enable_pbr": { + "name": "habilitar_pbr", + "tooltip": "Gerar mapas PBR (metálico, rugosidade, normal) além da cor base. Nota: isso deve ser definido como falso ao usar o estilo Escultura, pois o estilo Escultura gera seu próprio conjunto de mapas PBR." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "model": { + "name": "modelo" + }, + "texture_image": { + "name": "imagem_de_textura", + "tooltip": "Apenas um de 'texture_image' ou 'texture_prompt' pode ser usado ao mesmo tempo." + }, + "texture_prompt": { + "name": "prompt_de_textura", + "tooltip": "Forneça um prompt de texto para orientar o processo de texturização. Máximo de 600 caracteres. Não pode ser usado ao mesmo tempo que 'texture_image'." + } + }, + "outputs": { + "0": { + "name": "arquivo_modelo", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRigModelNode": { + "description": "Fornece um personagem com rig em formatos padrão. O auto-rigging atualmente não é adequado para malhas sem textura, ativos não humanóides ou ativos humanóides com estrutura de membros e corpo indefinida.", + "display_name": "Meshy: Rig de Modelo", + "inputs": { + "height_meters": { + "name": "altura_metros", + "tooltip": "A altura aproximada do modelo do personagem em metros. Isso auxilia na escala e precisão do rig." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "texture_image": { + "name": "imagem_de_textura", + "tooltip": "Imagem de textura de cor base UV-desdobrada do modelo." + } + }, + "outputs": { + "0": { + "name": "arquivo_modelo", + "tooltip": null + }, + "1": { + "name": "rig_task_id", + "tooltip": null + } + } + }, + "MeshyTextToModelNode": { + "display_name": "Meshy: Texto para Modelo", + "inputs": { + "control_after_generate": { + "name": "controle_após_gerar" + }, + "model": { + "name": "modelo" + }, + "pose_mode": { + "name": "modo_de_pose", + "tooltip": "Especifique o modo de pose para o modelo gerado." + }, + "prompt": { + "name": "prompt" + }, + "seed": { + "name": "semente", + "tooltip": "A semente controla se o nó deve ser executado novamente; os resultados são não determinísticos independentemente da semente." + }, + "should_remesh": { + "name": "deve_refazer_malha", + "tooltip": "Quando definido como falso, retorna uma malha triangular não processada." + }, + "should_remesh_target_polycount": { + "name": "contagem_alvo_de_polígonos" + }, + "should_remesh_topology": { + "name": "topologia" + }, + "style": { + "name": "estilo" + }, + "symmetry_mode": { + "name": "modo_de_simetria" + } + }, + "outputs": { + "0": { + "name": "arquivo_modelo", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyTextureNode": { + "display_name": "Meshy: Modelo de Textura", + "inputs": { + "enable_original_uv": { + "name": "habilitar_uv_original", + "tooltip": "Usar o UV original do modelo em vez de gerar novos UVs. Quando ativado, o Meshy preserva as texturas existentes do modelo enviado. Se o modelo não tiver UV original, a qualidade do resultado pode não ser tão boa." + }, + "image_style": { + "name": "estilo_de_imagem", + "tooltip": "Uma imagem 2D para guiar o processo de texturização. Não pode ser usada ao mesmo tempo que 'text_style_prompt'." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "model": { + "name": "modelo" + }, + "pbr": { + "name": "pbr" + }, + "text_style_prompt": { + "name": "prompt_de_estilo_textual", + "tooltip": "Descreva o estilo de textura desejado para o objeto usando texto. Máximo de 600 caracteres. Não pode ser usado ao mesmo tempo que 'image_style'." + } + }, + "outputs": { + "0": { + "name": "arquivo_do_modelo", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MinimaxHailuoVideoNode": { + "description": "Gera vídeos a partir do prompt, com quadro inicial opcional usando o novo modelo MiniMax Hailuo-02.", + "display_name": "MiniMax Hailuo Vídeo", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "duration": { + "name": "duração", + "tooltip": "A duração do vídeo de saída em segundos." + }, + "first_frame_image": { + "name": "imagem_primeiro_quadro", + "tooltip": "Imagem opcional para usar como o primeiro quadro do vídeo." + }, + "prompt_optimizer": { + "name": "otimizador_de_prompt", + "tooltip": "Otimiza o prompt para melhorar a qualidade da geração quando necessário." + }, + "prompt_text": { + "name": "texto_prompt", + "tooltip": "Prompt de texto para guiar a geração do vídeo." + }, + "resolution": { + "name": "resolução", + "tooltip": "As dimensões da exibição do vídeo. 1080p é 1920x1080, 768p é 1366x768." + }, + "seed": { + "name": "semente", + "tooltip": "A semente aleatória usada para criar o ruído." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MinimaxImageToVideoNode": { + "description": "Gera vídeos de forma síncrona com base em uma imagem, prompt e parâmetros opcionais.", + "display_name": "MiniMax Imagem para Vídeo", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "image": { + "name": "imagem", + "tooltip": "Imagem a ser usada como o primeiro quadro da geração de vídeo" + }, + "model": { + "name": "modelo", + "tooltip": "Modelo a ser usado para geração de vídeo" + }, + "prompt_text": { + "name": "prompt_texto", + "tooltip": "Prompt de texto para guiar a geração do vídeo" + }, + "seed": { + "name": "semente", + "tooltip": "A semente aleatória usada para criar o ruído." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MinimaxTextToVideoNode": { + "description": "Gera vídeos de forma síncrona com base em um prompt e parâmetros opcionais.", + "display_name": "MiniMax Texto para Vídeo", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "model": { + "name": "modelo", + "tooltip": "Modelo a ser usado para geração de vídeo" + }, + "prompt_text": { + "name": "prompt_texto", + "tooltip": "Prompt de texto para guiar a geração do vídeo" + }, + "seed": { + "name": "semente", + "tooltip": "A semente aleatória usada para criar o ruído." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ModelComputeDtype": { + "display_name": "ModelComputeDtype", + "inputs": { + "dtype": { + "name": "dtype" + }, + "model": { + "name": "modelo" + } + } + }, + "ModelMergeAdd": { + "display_name": "ModelMergeAdd", + "inputs": { + "model1": { + "name": "modelo1" + }, + "model2": { + "name": "modelo2" + } + } + }, + "ModelMergeAuraflow": { + "display_name": "ModelMergeAuraflow", + "inputs": { + "cond_seq_linear_": { + "name": "cond_seq_linear." + }, + "double_layers_0_": { + "name": "double_layers.0." + }, + "double_layers_1_": { + "name": "double_layers.1." + }, + "double_layers_2_": { + "name": "double_layers.2." + }, + "double_layers_3_": { + "name": "double_layers.3." + }, + "final_linear_": { + "name": "final_linear." + }, + "init_x_linear_": { + "name": "init_x_linear." + }, + "modF_": { + "name": "modF." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "positional_encoding": { + "name": "codificação_posicional" + }, + "register_tokens": { + "name": "registrar_tokens" + }, + "single_layers_0_": { + "name": "single_layers.0." + }, + "single_layers_10_": { + "name": "single_layers.10." + }, + "single_layers_11_": { + "name": "single_layers.11." + }, + "single_layers_12_": { + "name": "single_layers.12." + }, + "single_layers_13_": { + "name": "single_layers.13." + }, + "single_layers_14_": { + "name": "single_layers.14." + }, + "single_layers_15_": { + "name": "single_layers.15." + }, + "single_layers_16_": { + "name": "single_layers.16." + }, + "single_layers_17_": { + "name": "single_layers.17." + }, + "single_layers_18_": { + "name": "single_layers.18." + }, + "single_layers_19_": { + "name": "single_layers.19." + }, + "single_layers_1_": { + "name": "single_layers.1." + }, + "single_layers_20_": { + "name": "single_layers.20." + }, + "single_layers_21_": { + "name": "single_layers.21." + }, + "single_layers_22_": { + "name": "single_layers.22." + }, + "single_layers_23_": { + "name": "single_layers.23." + }, + "single_layers_24_": { + "name": "single_layers.24." + }, + "single_layers_25_": { + "name": "single_layers.25." + }, + "single_layers_26_": { + "name": "single_layers.26." + }, + "single_layers_27_": { + "name": "single_layers.27." + }, + "single_layers_28_": { + "name": "single_layers.28." + }, + "single_layers_29_": { + "name": "single_layers.29." + }, + "single_layers_2_": { + "name": "single_layers.2." + }, + "single_layers_30_": { + "name": "single_layers.30." + }, + "single_layers_31_": { + "name": "single_layers.31." + }, + "single_layers_3_": { + "name": "single_layers.3." + }, + "single_layers_4_": { + "name": "single_layers.4." + }, + "single_layers_5_": { + "name": "single_layers.5." + }, + "single_layers_6_": { + "name": "single_layers.6." + }, + "single_layers_7_": { + "name": "single_layers.7." + }, + "single_layers_8_": { + "name": "single_layers.8." + }, + "single_layers_9_": { + "name": "single_layers.9." + }, + "t_embedder_": { + "name": "t_embedder." + } + } + }, + "ModelMergeBlocks": { + "display_name": "ModelMergeBlocks", + "inputs": { + "input": { + "name": "entrada" + }, + "middle": { + "name": "meio" + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "out": { + "name": "saída" + } + } + }, + "ModelMergeCosmos14B": { + "display_name": "ModelMergeCosmos14B", + "inputs": { + "affline_norm_": { + "name": "affline_norm." + }, + "blocks_block0_": { + "name": "blocos.bloco0." + }, + "blocks_block10_": { + "name": "blocos.bloco10." + }, + "blocks_block11_": { + "name": "blocos.bloco11." + }, + "blocks_block12_": { + "name": "blocos.bloco12." + }, + "blocks_block13_": { + "name": "blocos.bloco13." + }, + "blocks_block14_": { + "name": "blocos.bloco14." + }, + "blocks_block15_": { + "name": "blocos.bloco15." + }, + "blocks_block16_": { + "name": "blocos.bloco16." + }, + "blocks_block17_": { + "name": "blocos.bloco17." + }, + "blocks_block18_": { + "name": "blocos.bloco18." + }, + "blocks_block19_": { + "name": "blocos.bloco19." + }, + "blocks_block1_": { + "name": "blocos.bloco1." + }, + "blocks_block20_": { + "name": "blocos.bloco20." + }, + "blocks_block21_": { + "name": "blocos.bloco21." + }, + "blocks_block22_": { + "name": "blocos.bloco22." + }, + "blocks_block23_": { + "name": "blocos.bloco23." + }, + "blocks_block24_": { + "name": "blocos.bloco24." + }, + "blocks_block25_": { + "name": "blocos.bloco25." + }, + "blocks_block26_": { + "name": "blocos.bloco26." + }, + "blocks_block27_": { + "name": "blocos.bloco27." + }, + "blocks_block28_": { + "name": "blocos.bloco28." + }, + "blocks_block29_": { + "name": "blocos.bloco29." + }, + "blocks_block2_": { + "name": "blocos.bloco2." + }, + "blocks_block30_": { + "name": "blocos.bloco30." + }, + "blocks_block31_": { + "name": "blocos.bloco31." + }, + "blocks_block32_": { + "name": "blocos.bloco32." + }, + "blocks_block33_": { + "name": "blocos.bloco33." + }, + "blocks_block34_": { + "name": "blocos.bloco34." + }, + "blocks_block35_": { + "name": "blocos.bloco35." + }, + "blocks_block3_": { + "name": "blocos.bloco3." + }, + "blocks_block4_": { + "name": "blocos.bloco4." + }, + "blocks_block5_": { + "name": "blocos.bloco5." + }, + "blocks_block6_": { + "name": "blocos.bloco6." + }, + "blocks_block7_": { + "name": "blocos.bloco7." + }, + "blocks_block8_": { + "name": "blocos.bloco8." + }, + "blocks_block9_": { + "name": "blocos.bloco9." + }, + "extra_pos_embedder_": { + "name": "extra_pos_embedder." + }, + "final_layer_": { + "name": "camada_final." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "pos_embedder_": { + "name": "pos_embedder." + }, + "t_embedder_": { + "name": "t_embedder." + }, + "x_embedder_": { + "name": "x_embedder." + } + } + }, + "ModelMergeCosmos7B": { + "display_name": "ModelMergeCosmos7B", + "inputs": { + "affline_norm_": { + "name": "affline_norm." + }, + "blocks_block0_": { + "name": "blocks.block0." + }, + "blocks_block10_": { + "name": "blocks.block10." + }, + "blocks_block11_": { + "name": "blocks.block11." + }, + "blocks_block12_": { + "name": "blocks.block12." + }, + "blocks_block13_": { + "name": "blocks.block13." + }, + "blocks_block14_": { + "name": "blocks.block14." + }, + "blocks_block15_": { + "name": "blocks.block15." + }, + "blocks_block16_": { + "name": "blocks.block16." + }, + "blocks_block17_": { + "name": "blocks.block17." + }, + "blocks_block18_": { + "name": "blocks.block18." + }, + "blocks_block19_": { + "name": "blocks.block19." + }, + "blocks_block1_": { + "name": "blocks.block1." + }, + "blocks_block20_": { + "name": "blocks.block20." + }, + "blocks_block21_": { + "name": "blocks.block21." + }, + "blocks_block22_": { + "name": "blocks.block22." + }, + "blocks_block23_": { + "name": "blocks.block23." + }, + "blocks_block24_": { + "name": "blocks.block24." + }, + "blocks_block25_": { + "name": "blocks.block25." + }, + "blocks_block26_": { + "name": "blocks.block26." + }, + "blocks_block27_": { + "name": "blocks.block27." + }, + "blocks_block2_": { + "name": "blocks.block2." + }, + "blocks_block3_": { + "name": "blocks.block3." + }, + "blocks_block4_": { + "name": "blocks.block4." + }, + "blocks_block5_": { + "name": "blocks.block5." + }, + "blocks_block6_": { + "name": "blocks.block6." + }, + "blocks_block7_": { + "name": "blocks.block7." + }, + "blocks_block8_": { + "name": "blocks.block8." + }, + "blocks_block9_": { + "name": "blocks.block9." + }, + "extra_pos_embedder_": { + "name": "extra_pos_embedder." + }, + "final_layer_": { + "name": "final_layer." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "pos_embedder_": { + "name": "pos_embedder." + }, + "t_embedder_": { + "name": "t_embedder." + }, + "x_embedder_": { + "name": "x_embedder." + } + } + }, + "ModelMergeCosmosPredict2_14B": { + "display_name": "ModelMergeCosmosPredict2_14B", + "inputs": { + "blocks_0_": { + "name": "blocks.0." + }, + "blocks_10_": { + "name": "blocks.10." + }, + "blocks_11_": { + "name": "blocks.11." + }, + "blocks_12_": { + "name": "blocks.12." + }, + "blocks_13_": { + "name": "blocks.13." + }, + "blocks_14_": { + "name": "blocks.14." + }, + "blocks_15_": { + "name": "blocks.15." + }, + "blocks_16_": { + "name": "blocks.16." + }, + "blocks_17_": { + "name": "blocks.17." + }, + "blocks_18_": { + "name": "blocks.18." + }, + "blocks_19_": { + "name": "blocks.19." + }, + "blocks_1_": { + "name": "blocks.1." + }, + "blocks_20_": { + "name": "blocks.20." + }, + "blocks_21_": { + "name": "blocks.21." + }, + "blocks_22_": { + "name": "blocks.22." + }, + "blocks_23_": { + "name": "blocks.23." + }, + "blocks_24_": { + "name": "blocks.24." + }, + "blocks_25_": { + "name": "blocks.25." + }, + "blocks_26_": { + "name": "blocks.26." + }, + "blocks_27_": { + "name": "blocks.27." + }, + "blocks_28_": { + "name": "blocks.28." + }, + "blocks_29_": { + "name": "blocks.29." + }, + "blocks_2_": { + "name": "blocks.2." + }, + "blocks_30_": { + "name": "blocks.30." + }, + "blocks_31_": { + "name": "blocks.31." + }, + "blocks_32_": { + "name": "blocks.32." + }, + "blocks_33_": { + "name": "blocks.33." + }, + "blocks_34_": { + "name": "blocks.34." + }, + "blocks_35_": { + "name": "blocks.35." + }, + "blocks_3_": { + "name": "blocks.3." + }, + "blocks_4_": { + "name": "blocks.4." + }, + "blocks_5_": { + "name": "blocks.5." + }, + "blocks_6_": { + "name": "blocks.6." + }, + "blocks_7_": { + "name": "blocks.7." + }, + "blocks_8_": { + "name": "blocks.8." + }, + "blocks_9_": { + "name": "blocks.9." + }, + "final_layer_": { + "name": "final_layer." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "pos_embedder_": { + "name": "pos_embedder." + }, + "t_embedder_": { + "name": "t_embedder." + }, + "t_embedding_norm_": { + "name": "t_embedding_norm." + }, + "x_embedder_": { + "name": "x_embedder." + } + } + }, + "ModelMergeCosmosPredict2_2B": { + "display_name": "ModelMergeCosmosPredict2_2B", + "inputs": { + "blocks_0_": { + "name": "blocos.0." + }, + "blocks_10_": { + "name": "blocos.10." + }, + "blocks_11_": { + "name": "blocos.11." + }, + "blocks_12_": { + "name": "blocos.12." + }, + "blocks_13_": { + "name": "blocos.13." + }, + "blocks_14_": { + "name": "blocos.14." + }, + "blocks_15_": { + "name": "blocos.15." + }, + "blocks_16_": { + "name": "blocos.16." + }, + "blocks_17_": { + "name": "blocos.17." + }, + "blocks_18_": { + "name": "blocos.18." + }, + "blocks_19_": { + "name": "blocos.19." + }, + "blocks_1_": { + "name": "blocos.1." + }, + "blocks_20_": { + "name": "blocos.20." + }, + "blocks_21_": { + "name": "blocos.21." + }, + "blocks_22_": { + "name": "blocos.22." + }, + "blocks_23_": { + "name": "blocos.23." + }, + "blocks_24_": { + "name": "blocos.24." + }, + "blocks_25_": { + "name": "blocos.25." + }, + "blocks_26_": { + "name": "blocos.26." + }, + "blocks_27_": { + "name": "blocos.27." + }, + "blocks_2_": { + "name": "blocos.2." + }, + "blocks_3_": { + "name": "blocos.3." + }, + "blocks_4_": { + "name": "blocos.4." + }, + "blocks_5_": { + "name": "blocos.5." + }, + "blocks_6_": { + "name": "blocos.6." + }, + "blocks_7_": { + "name": "blocos.7." + }, + "blocks_8_": { + "name": "blocos.8." + }, + "blocks_9_": { + "name": "blocos.9." + }, + "final_layer_": { + "name": "camada_final." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "pos_embedder_": { + "name": "pos_embedder." + }, + "t_embedder_": { + "name": "t_embedder." + }, + "t_embedding_norm_": { + "name": "t_embedding_norm." + }, + "x_embedder_": { + "name": "x_embedder." + } + } + }, + "ModelMergeFlux1": { + "display_name": "ModelMergeFlux1", + "inputs": { + "double_blocks_0_": { + "name": "double_blocks.0." + }, + "double_blocks_10_": { + "name": "double_blocks.10." + }, + "double_blocks_11_": { + "name": "double_blocks.11." + }, + "double_blocks_12_": { + "name": "double_blocks.12." + }, + "double_blocks_13_": { + "name": "double_blocks.13." + }, + "double_blocks_14_": { + "name": "double_blocks.14." + }, + "double_blocks_15_": { + "name": "double_blocks.15." + }, + "double_blocks_16_": { + "name": "double_blocks.16." + }, + "double_blocks_17_": { + "name": "double_blocks.17." + }, + "double_blocks_18_": { + "name": "double_blocks.18." + }, + "double_blocks_1_": { + "name": "double_blocks.1." + }, + "double_blocks_2_": { + "name": "double_blocks.2." + }, + "double_blocks_3_": { + "name": "double_blocks.3." + }, + "double_blocks_4_": { + "name": "double_blocks.4." + }, + "double_blocks_5_": { + "name": "double_blocks.5." + }, + "double_blocks_6_": { + "name": "double_blocks.6." + }, + "double_blocks_7_": { + "name": "double_blocks.7." + }, + "double_blocks_8_": { + "name": "double_blocks.8." + }, + "double_blocks_9_": { + "name": "double_blocks.9." + }, + "final_layer_": { + "name": "final_layer." + }, + "guidance_in": { + "name": "guidance_in" + }, + "img_in_": { + "name": "img_in." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "single_blocks_0_": { + "name": "single_blocks.0." + }, + "single_blocks_10_": { + "name": "single_blocks.10." + }, + "single_blocks_11_": { + "name": "single_blocks.11." + }, + "single_blocks_12_": { + "name": "single_blocks.12." + }, + "single_blocks_13_": { + "name": "single_blocks.13." + }, + "single_blocks_14_": { + "name": "single_blocks.14." + }, + "single_blocks_15_": { + "name": "single_blocks.15." + }, + "single_blocks_16_": { + "name": "single_blocks.16." + }, + "single_blocks_17_": { + "name": "single_blocks.17." + }, + "single_blocks_18_": { + "name": "single_blocks.18." + }, + "single_blocks_19_": { + "name": "single_blocks.19." + }, + "single_blocks_1_": { + "name": "single_blocks.1." + }, + "single_blocks_20_": { + "name": "single_blocks.20." + }, + "single_blocks_21_": { + "name": "single_blocks.21." + }, + "single_blocks_22_": { + "name": "single_blocks.22." + }, + "single_blocks_23_": { + "name": "single_blocks.23." + }, + "single_blocks_24_": { + "name": "single_blocks.24." + }, + "single_blocks_25_": { + "name": "single_blocks.25." + }, + "single_blocks_26_": { + "name": "single_blocks.26." + }, + "single_blocks_27_": { + "name": "single_blocks.27." + }, + "single_blocks_28_": { + "name": "single_blocks.28." + }, + "single_blocks_29_": { + "name": "single_blocks.29." + }, + "single_blocks_2_": { + "name": "single_blocks.2." + }, + "single_blocks_30_": { + "name": "single_blocks.30." + }, + "single_blocks_31_": { + "name": "single_blocks.31." + }, + "single_blocks_32_": { + "name": "single_blocks.32." + }, + "single_blocks_33_": { + "name": "single_blocks.33." + }, + "single_blocks_34_": { + "name": "single_blocks.34." + }, + "single_blocks_35_": { + "name": "single_blocks.35." + }, + "single_blocks_36_": { + "name": "single_blocks.36." + }, + "single_blocks_37_": { + "name": "single_blocks.37." + }, + "single_blocks_3_": { + "name": "single_blocks.3." + }, + "single_blocks_4_": { + "name": "single_blocks.4." + }, + "single_blocks_5_": { + "name": "single_blocks.5." + }, + "single_blocks_6_": { + "name": "single_blocks.6." + }, + "single_blocks_7_": { + "name": "single_blocks.7." + }, + "single_blocks_8_": { + "name": "single_blocks.8." + }, + "single_blocks_9_": { + "name": "single_blocks.9." + }, + "time_in_": { + "name": "time_in." + }, + "txt_in_": { + "name": "txt_in." + }, + "vector_in_": { + "name": "vector_in." + } + } + }, + "ModelMergeLTXV": { + "display_name": "ModelMergeLTXV", + "inputs": { + "adaln_single_": { + "name": "adaln_single." + }, + "caption_projection_": { + "name": "caption_projection." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "patchify_proj_": { + "name": "patchify_proj." + }, + "proj_out_": { + "name": "proj_out." + }, + "scale_shift_table": { + "name": "scale_shift_table" + }, + "transformer_blocks_0_": { + "name": "transformer_blocks.0." + }, + "transformer_blocks_10_": { + "name": "transformer_blocks.10." + }, + "transformer_blocks_11_": { + "name": "transformer_blocks.11." + }, + "transformer_blocks_12_": { + "name": "transformer_blocks.12." + }, + "transformer_blocks_13_": { + "name": "transformer_blocks.13." + }, + "transformer_blocks_14_": { + "name": "transformer_blocks.14." + }, + "transformer_blocks_15_": { + "name": "transformer_blocks.15." + }, + "transformer_blocks_16_": { + "name": "transformer_blocks.16." + }, + "transformer_blocks_17_": { + "name": "transformer_blocks.17." + }, + "transformer_blocks_18_": { + "name": "transformer_blocks.18." + }, + "transformer_blocks_19_": { + "name": "transformer_blocks.19." + }, + "transformer_blocks_1_": { + "name": "transformer_blocks.1." + }, + "transformer_blocks_20_": { + "name": "transformer_blocks.20." + }, + "transformer_blocks_21_": { + "name": "transformer_blocks.21." + }, + "transformer_blocks_22_": { + "name": "transformer_blocks.22." + }, + "transformer_blocks_23_": { + "name": "transformer_blocks.23." + }, + "transformer_blocks_24_": { + "name": "transformer_blocks.24." + }, + "transformer_blocks_25_": { + "name": "transformer_blocks.25." + }, + "transformer_blocks_26_": { + "name": "transformer_blocks.26." + }, + "transformer_blocks_27_": { + "name": "transformer_blocks.27." + }, + "transformer_blocks_2_": { + "name": "transformer_blocks.2." + }, + "transformer_blocks_3_": { + "name": "transformer_blocks.3." + }, + "transformer_blocks_4_": { + "name": "transformer_blocks.4." + }, + "transformer_blocks_5_": { + "name": "transformer_blocks.5." + }, + "transformer_blocks_6_": { + "name": "transformer_blocks.6." + }, + "transformer_blocks_7_": { + "name": "transformer_blocks.7." + }, + "transformer_blocks_8_": { + "name": "transformer_blocks.8." + }, + "transformer_blocks_9_": { + "name": "transformer_blocks.9." + } + } + }, + "ModelMergeMochiPreview": { + "display_name": "ModelMergeMochiPreview", + "inputs": { + "blocks_0_": { + "name": "blocks.0." + }, + "blocks_10_": { + "name": "blocks.10." + }, + "blocks_11_": { + "name": "blocks.11." + }, + "blocks_12_": { + "name": "blocks.12." + }, + "blocks_13_": { + "name": "blocks.13." + }, + "blocks_14_": { + "name": "blocks.14." + }, + "blocks_15_": { + "name": "blocks.15." + }, + "blocks_16_": { + "name": "blocks.16." + }, + "blocks_17_": { + "name": "blocks.17." + }, + "blocks_18_": { + "name": "blocks.18." + }, + "blocks_19_": { + "name": "blocks.19." + }, + "blocks_1_": { + "name": "blocks.1." + }, + "blocks_20_": { + "name": "blocks.20." + }, + "blocks_21_": { + "name": "blocks.21." + }, + "blocks_22_": { + "name": "blocks.22." + }, + "blocks_23_": { + "name": "blocks.23." + }, + "blocks_24_": { + "name": "blocks.24." + }, + "blocks_25_": { + "name": "blocks.25." + }, + "blocks_26_": { + "name": "blocks.26." + }, + "blocks_27_": { + "name": "blocks.27." + }, + "blocks_28_": { + "name": "blocks.28." + }, + "blocks_29_": { + "name": "blocks.29." + }, + "blocks_2_": { + "name": "blocks.2." + }, + "blocks_30_": { + "name": "blocks.30." + }, + "blocks_31_": { + "name": "blocks.31." + }, + "blocks_32_": { + "name": "blocks.32." + }, + "blocks_33_": { + "name": "blocks.33." + }, + "blocks_34_": { + "name": "blocks.34." + }, + "blocks_35_": { + "name": "blocks.35." + }, + "blocks_36_": { + "name": "blocks.36." + }, + "blocks_37_": { + "name": "blocks.37." + }, + "blocks_38_": { + "name": "blocks.38." + }, + "blocks_39_": { + "name": "blocks.39." + }, + "blocks_3_": { + "name": "blocks.3." + }, + "blocks_40_": { + "name": "blocks.40." + }, + "blocks_41_": { + "name": "blocks.41." + }, + "blocks_42_": { + "name": "blocks.42." + }, + "blocks_43_": { + "name": "blocks.43." + }, + "blocks_44_": { + "name": "blocks.44." + }, + "blocks_45_": { + "name": "blocks.45." + }, + "blocks_46_": { + "name": "blocks.46." + }, + "blocks_47_": { + "name": "blocks.47." + }, + "blocks_4_": { + "name": "blocks.4." + }, + "blocks_5_": { + "name": "blocks.5." + }, + "blocks_6_": { + "name": "blocks.6." + }, + "blocks_7_": { + "name": "blocks.7." + }, + "blocks_8_": { + "name": "blocks.8." + }, + "blocks_9_": { + "name": "blocks.9." + }, + "final_layer_": { + "name": "final_layer." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "pos_frequencies_": { + "name": "pos_frequencies." + }, + "t5_y_embedder_": { + "name": "t5_y_embedder." + }, + "t5_yproj_": { + "name": "t5_yproj." + }, + "t_embedder_": { + "name": "t_embedder." + } + } + }, + "ModelMergeQwenImage": { + "display_name": "ModelMergeQwenImage", + "inputs": { + "img_in_": { + "name": "img_in." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "pos_embeds_": { + "name": "pos_embeds." + }, + "proj_out_": { + "name": "proj_out." + }, + "time_text_embed_": { + "name": "time_text_embed." + }, + "transformer_blocks_0_": { + "name": "transformer_blocks.0." + }, + "transformer_blocks_10_": { + "name": "transformer_blocks.10." + }, + "transformer_blocks_11_": { + "name": "transformer_blocks.11." + }, + "transformer_blocks_12_": { + "name": "transformer_blocks.12." + }, + "transformer_blocks_13_": { + "name": "transformer_blocks.13." + }, + "transformer_blocks_14_": { + "name": "transformer_blocks.14." + }, + "transformer_blocks_15_": { + "name": "transformer_blocks.15." + }, + "transformer_blocks_16_": { + "name": "transformer_blocks.16." + }, + "transformer_blocks_17_": { + "name": "transformer_blocks.17." + }, + "transformer_blocks_18_": { + "name": "transformer_blocks.18." + }, + "transformer_blocks_19_": { + "name": "transformer_blocks.19." + }, + "transformer_blocks_1_": { + "name": "transformer_blocks.1." + }, + "transformer_blocks_20_": { + "name": "transformer_blocks.20." + }, + "transformer_blocks_21_": { + "name": "transformer_blocks.21." + }, + "transformer_blocks_22_": { + "name": "transformer_blocks.22." + }, + "transformer_blocks_23_": { + "name": "transformer_blocks.23." + }, + "transformer_blocks_24_": { + "name": "transformer_blocks.24." + }, + "transformer_blocks_25_": { + "name": "transformer_blocks.25." + }, + "transformer_blocks_26_": { + "name": "transformer_blocks.26." + }, + "transformer_blocks_27_": { + "name": "transformer_blocks.27." + }, + "transformer_blocks_28_": { + "name": "transformer_blocks.28." + }, + "transformer_blocks_29_": { + "name": "transformer_blocks.29." + }, + "transformer_blocks_2_": { + "name": "transformer_blocks.2." + }, + "transformer_blocks_30_": { + "name": "transformer_blocks.30." + }, + "transformer_blocks_31_": { + "name": "transformer_blocks.31." + }, + "transformer_blocks_32_": { + "name": "transformer_blocks.32." + }, + "transformer_blocks_33_": { + "name": "transformer_blocks.33." + }, + "transformer_blocks_34_": { + "name": "transformer_blocks.34." + }, + "transformer_blocks_35_": { + "name": "transformer_blocks.35." + }, + "transformer_blocks_36_": { + "name": "transformer_blocks.36." + }, + "transformer_blocks_37_": { + "name": "transformer_blocks.37." + }, + "transformer_blocks_38_": { + "name": "transformer_blocks.38." + }, + "transformer_blocks_39_": { + "name": "transformer_blocks.39." + }, + "transformer_blocks_3_": { + "name": "transformer_blocks.3." + }, + "transformer_blocks_40_": { + "name": "transformer_blocks.40." + }, + "transformer_blocks_41_": { + "name": "transformer_blocks.41." + }, + "transformer_blocks_42_": { + "name": "transformer_blocks.42." + }, + "transformer_blocks_43_": { + "name": "transformer_blocks.43." + }, + "transformer_blocks_44_": { + "name": "transformer_blocks.44." + }, + "transformer_blocks_45_": { + "name": "transformer_blocks.45." + }, + "transformer_blocks_46_": { + "name": "transformer_blocks.46." + }, + "transformer_blocks_47_": { + "name": "transformer_blocks.47." + }, + "transformer_blocks_48_": { + "name": "transformer_blocks.48." + }, + "transformer_blocks_49_": { + "name": "transformer_blocks.49." + }, + "transformer_blocks_4_": { + "name": "transformer_blocks.4." + }, + "transformer_blocks_50_": { + "name": "transformer_blocks.50." + }, + "transformer_blocks_51_": { + "name": "transformer_blocks.51." + }, + "transformer_blocks_52_": { + "name": "transformer_blocks.52." + }, + "transformer_blocks_53_": { + "name": "transformer_blocks.53." + }, + "transformer_blocks_54_": { + "name": "transformer_blocks.54." + }, + "transformer_blocks_55_": { + "name": "transformer_blocks.55." + }, + "transformer_blocks_56_": { + "name": "transformer_blocks.56." + }, + "transformer_blocks_57_": { + "name": "transformer_blocks.57." + }, + "transformer_blocks_58_": { + "name": "transformer_blocks.58." + }, + "transformer_blocks_59_": { + "name": "transformer_blocks.59." + }, + "transformer_blocks_5_": { + "name": "transformer_blocks.5." + }, + "transformer_blocks_6_": { + "name": "transformer_blocks.6." + }, + "transformer_blocks_7_": { + "name": "transformer_blocks.7." + }, + "transformer_blocks_8_": { + "name": "transformer_blocks.8." + }, + "transformer_blocks_9_": { + "name": "transformer_blocks.9." + }, + "txt_in_": { + "name": "txt_in." + }, + "txt_norm_": { + "name": "txt_norm." + } + } + }, + "ModelMergeSD1": { + "display_name": "ModelMergeSD1", + "inputs": { + "input_blocks_0_": { + "name": "input_blocks.0." + }, + "input_blocks_10_": { + "name": "input_blocks.10." + }, + "input_blocks_11_": { + "name": "input_blocks.11." + }, + "input_blocks_1_": { + "name": "input_blocks.1." + }, + "input_blocks_2_": { + "name": "input_blocks.2." + }, + "input_blocks_3_": { + "name": "input_blocks.3." + }, + "input_blocks_4_": { + "name": "input_blocks.4." + }, + "input_blocks_5_": { + "name": "input_blocks.5." + }, + "input_blocks_6_": { + "name": "input_blocks.6." + }, + "input_blocks_7_": { + "name": "input_blocks.7." + }, + "input_blocks_8_": { + "name": "input_blocks.8." + }, + "input_blocks_9_": { + "name": "input_blocks.9." + }, + "label_emb_": { + "name": "label_emb." + }, + "middle_block_0_": { + "name": "middle_block.0." + }, + "middle_block_1_": { + "name": "middle_block.1." + }, + "middle_block_2_": { + "name": "middle_block.2." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "out_": { + "name": "out." + }, + "output_blocks_0_": { + "name": "output_blocks.0." + }, + "output_blocks_10_": { + "name": "output_blocks.10." + }, + "output_blocks_11_": { + "name": "output_blocks.11." + }, + "output_blocks_1_": { + "name": "output_blocks.1." + }, + "output_blocks_2_": { + "name": "output_blocks.2." + }, + "output_blocks_3_": { + "name": "output_blocks.3." + }, + "output_blocks_4_": { + "name": "output_blocks.4." + }, + "output_blocks_5_": { + "name": "output_blocks.5." + }, + "output_blocks_6_": { + "name": "output_blocks.6." + }, + "output_blocks_7_": { + "name": "output_blocks.7." + }, + "output_blocks_8_": { + "name": "output_blocks.8." + }, + "output_blocks_9_": { + "name": "output_blocks.9." + }, + "time_embed_": { + "name": "time_embed." + } + } + }, + "ModelMergeSD2": { + "display_name": "ModelMergeSD2", + "inputs": { + "input_blocks_0_": { + "name": "input_blocks.0." + }, + "input_blocks_10_": { + "name": "input_blocks.10." + }, + "input_blocks_11_": { + "name": "input_blocks.11." + }, + "input_blocks_1_": { + "name": "input_blocks.1." + }, + "input_blocks_2_": { + "name": "input_blocks.2." + }, + "input_blocks_3_": { + "name": "input_blocks.3." + }, + "input_blocks_4_": { + "name": "input_blocks.4." + }, + "input_blocks_5_": { + "name": "input_blocks.5." + }, + "input_blocks_6_": { + "name": "input_blocks.6." + }, + "input_blocks_7_": { + "name": "input_blocks.7." + }, + "input_blocks_8_": { + "name": "input_blocks.8." + }, + "input_blocks_9_": { + "name": "input_blocks.9." + }, + "label_emb_": { + "name": "label_emb." + }, + "middle_block_0_": { + "name": "middle_block.0." + }, + "middle_block_1_": { + "name": "middle_block.1." + }, + "middle_block_2_": { + "name": "middle_block.2." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "out_": { + "name": "out." + }, + "output_blocks_0_": { + "name": "output_blocks.0." + }, + "output_blocks_10_": { + "name": "output_blocks.10." + }, + "output_blocks_11_": { + "name": "output_blocks.11." + }, + "output_blocks_1_": { + "name": "output_blocks.1." + }, + "output_blocks_2_": { + "name": "output_blocks.2." + }, + "output_blocks_3_": { + "name": "output_blocks.3." + }, + "output_blocks_4_": { + "name": "output_blocks.4." + }, + "output_blocks_5_": { + "name": "output_blocks.5." + }, + "output_blocks_6_": { + "name": "output_blocks.6." + }, + "output_blocks_7_": { + "name": "output_blocks.7." + }, + "output_blocks_8_": { + "name": "output_blocks.8." + }, + "output_blocks_9_": { + "name": "output_blocks.9." + }, + "time_embed_": { + "name": "time_embed." + } + } + }, + "ModelMergeSD35_Large": { + "display_name": "ModelMergeSD35_Large", + "inputs": { + "context_embedder_": { + "name": "context_embedder." + }, + "final_layer_": { + "name": "final_layer." + }, + "joint_blocks_0_": { + "name": "joint_blocks.0." + }, + "joint_blocks_10_": { + "name": "joint_blocks.10." + }, + "joint_blocks_11_": { + "name": "joint_blocks.11." + }, + "joint_blocks_12_": { + "name": "joint_blocks.12." + }, + "joint_blocks_13_": { + "name": "joint_blocks.13." + }, + "joint_blocks_14_": { + "name": "joint_blocks.14." + }, + "joint_blocks_15_": { + "name": "joint_blocks.15." + }, + "joint_blocks_16_": { + "name": "joint_blocks.16." + }, + "joint_blocks_17_": { + "name": "joint_blocks.17." + }, + "joint_blocks_18_": { + "name": "joint_blocks.18." + }, + "joint_blocks_19_": { + "name": "joint_blocks.19." + }, + "joint_blocks_1_": { + "name": "joint_blocks.1." + }, + "joint_blocks_20_": { + "name": "joint_blocks.20." + }, + "joint_blocks_21_": { + "name": "joint_blocks.21." + }, + "joint_blocks_22_": { + "name": "joint_blocks.22." + }, + "joint_blocks_23_": { + "name": "joint_blocks.23." + }, + "joint_blocks_24_": { + "name": "joint_blocks.24." + }, + "joint_blocks_25_": { + "name": "joint_blocks.25." + }, + "joint_blocks_26_": { + "name": "joint_blocks.26." + }, + "joint_blocks_27_": { + "name": "joint_blocks.27." + }, + "joint_blocks_28_": { + "name": "joint_blocks.28." + }, + "joint_blocks_29_": { + "name": "joint_blocks.29." + }, + "joint_blocks_2_": { + "name": "joint_blocks.2." + }, + "joint_blocks_30_": { + "name": "joint_blocks.30." + }, + "joint_blocks_31_": { + "name": "joint_blocks.31." + }, + "joint_blocks_32_": { + "name": "joint_blocks.32." + }, + "joint_blocks_33_": { + "name": "joint_blocks.33." + }, + "joint_blocks_34_": { + "name": "joint_blocks.34." + }, + "joint_blocks_35_": { + "name": "joint_blocks.35." + }, + "joint_blocks_36_": { + "name": "joint_blocks.36." + }, + "joint_blocks_37_": { + "name": "joint_blocks.37." + }, + "joint_blocks_3_": { + "name": "joint_blocks.3." + }, + "joint_blocks_4_": { + "name": "joint_blocks.4." + }, + "joint_blocks_5_": { + "name": "joint_blocks.5." + }, + "joint_blocks_6_": { + "name": "joint_blocks.6." + }, + "joint_blocks_7_": { + "name": "joint_blocks.7." + }, + "joint_blocks_8_": { + "name": "joint_blocks.8." + }, + "joint_blocks_9_": { + "name": "joint_blocks.9." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "pos_embed_": { + "name": "pos_embed." + }, + "t_embedder_": { + "name": "t_embedder." + }, + "x_embedder_": { + "name": "x_embedder." + }, + "y_embedder_": { + "name": "y_embedder." + } + } + }, + "ModelMergeSD3_2B": { + "display_name": "ModelMergeSD3_2B", + "inputs": { + "context_embedder_": { + "name": "context_embedder." + }, + "final_layer_": { + "name": "final_layer." + }, + "joint_blocks_0_": { + "name": "joint_blocks.0." + }, + "joint_blocks_10_": { + "name": "joint_blocks.10." + }, + "joint_blocks_11_": { + "name": "joint_blocks.11." + }, + "joint_blocks_12_": { + "name": "joint_blocks.12." + }, + "joint_blocks_13_": { + "name": "joint_blocks.13." + }, + "joint_blocks_14_": { + "name": "joint_blocks.14." + }, + "joint_blocks_15_": { + "name": "joint_blocks.15." + }, + "joint_blocks_16_": { + "name": "joint_blocks.16." + }, + "joint_blocks_17_": { + "name": "joint_blocks.17." + }, + "joint_blocks_18_": { + "name": "joint_blocks.18." + }, + "joint_blocks_19_": { + "name": "joint_blocks.19." + }, + "joint_blocks_1_": { + "name": "joint_blocks.1." + }, + "joint_blocks_20_": { + "name": "joint_blocks.20." + }, + "joint_blocks_21_": { + "name": "joint_blocks.21." + }, + "joint_blocks_22_": { + "name": "joint_blocks.22." + }, + "joint_blocks_23_": { + "name": "joint_blocks.23." + }, + "joint_blocks_2_": { + "name": "joint_blocks.2." + }, + "joint_blocks_3_": { + "name": "joint_blocks.3." + }, + "joint_blocks_4_": { + "name": "joint_blocks.4." + }, + "joint_blocks_5_": { + "name": "joint_blocks.5." + }, + "joint_blocks_6_": { + "name": "joint_blocks.6." + }, + "joint_blocks_7_": { + "name": "joint_blocks.7." + }, + "joint_blocks_8_": { + "name": "joint_blocks.8." + }, + "joint_blocks_9_": { + "name": "joint_blocks.9." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "pos_embed_": { + "name": "pos_embed." + }, + "t_embedder_": { + "name": "t_embedder." + }, + "x_embedder_": { + "name": "x_embedder." + }, + "y_embedder_": { + "name": "y_embedder." + } + } + }, + "ModelMergeSDXL": { + "display_name": "ModelMergeSDXL", + "inputs": { + "input_blocks_0": { + "name": "input_blocks.0" + }, + "input_blocks_1": { + "name": "input_blocks.1" + }, + "input_blocks_2": { + "name": "input_blocks.2" + }, + "input_blocks_3": { + "name": "input_blocks.3" + }, + "input_blocks_4": { + "name": "input_blocks.4" + }, + "input_blocks_5": { + "name": "input_blocks.5" + }, + "input_blocks_6": { + "name": "input_blocks.6" + }, + "input_blocks_7": { + "name": "input_blocks.7" + }, + "input_blocks_8": { + "name": "input_blocks.8" + }, + "label_emb_": { + "name": "label_emb." + }, + "middle_block_0": { + "name": "middle_block.0" + }, + "middle_block_1": { + "name": "middle_block.1" + }, + "middle_block_2": { + "name": "middle_block.2" + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "out_": { + "name": "out." + }, + "output_blocks_0": { + "name": "output_blocks.0" + }, + "output_blocks_1": { + "name": "output_blocks.1" + }, + "output_blocks_2": { + "name": "output_blocks.2" + }, + "output_blocks_3": { + "name": "output_blocks.3" + }, + "output_blocks_4": { + "name": "output_blocks.4" + }, + "output_blocks_5": { + "name": "output_blocks.5" + }, + "output_blocks_6": { + "name": "output_blocks.6" + }, + "output_blocks_7": { + "name": "output_blocks.7" + }, + "output_blocks_8": { + "name": "output_blocks.8" + }, + "time_embed_": { + "name": "time_embed." + } + } + }, + "ModelMergeSimple": { + "display_name": "ModelMergeSimple", + "inputs": { + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "ratio": { + "name": "proporção" + } + } + }, + "ModelMergeSubtract": { + "display_name": "ModelMergeSubtract", + "inputs": { + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "multiplier": { + "name": "multiplicador" + } + } + }, + "ModelMergeWAN2_1": { + "description": "O modelo de 1,3B possui 30 blocos, o modelo de 14B possui 40 blocos. O modelo de imagem para vídeo possui o img_emb extra.", + "display_name": "ModelMergeWAN2_1", + "inputs": { + "blocks_0_": { + "name": "blocks.0." + }, + "blocks_10_": { + "name": "blocks.10." + }, + "blocks_11_": { + "name": "blocks.11." + }, + "blocks_12_": { + "name": "blocks.12." + }, + "blocks_13_": { + "name": "blocks.13." + }, + "blocks_14_": { + "name": "blocks.14." + }, + "blocks_15_": { + "name": "blocks.15." + }, + "blocks_16_": { + "name": "blocks.16." + }, + "blocks_17_": { + "name": "blocks.17." + }, + "blocks_18_": { + "name": "blocks.18." + }, + "blocks_19_": { + "name": "blocks.19." + }, + "blocks_1_": { + "name": "blocks.1." + }, + "blocks_20_": { + "name": "blocks.20." + }, + "blocks_21_": { + "name": "blocks.21." + }, + "blocks_22_": { + "name": "blocks.22." + }, + "blocks_23_": { + "name": "blocks.23." + }, + "blocks_24_": { + "name": "blocks.24." + }, + "blocks_25_": { + "name": "blocks.25." + }, + "blocks_26_": { + "name": "blocks.26." + }, + "blocks_27_": { + "name": "blocks.27." + }, + "blocks_28_": { + "name": "blocks.28." + }, + "blocks_29_": { + "name": "blocks.29." + }, + "blocks_2_": { + "name": "blocks.2." + }, + "blocks_30_": { + "name": "blocks.30." + }, + "blocks_31_": { + "name": "blocks.31." + }, + "blocks_32_": { + "name": "blocks.32." + }, + "blocks_33_": { + "name": "blocks.33." + }, + "blocks_34_": { + "name": "blocks.34." + }, + "blocks_35_": { + "name": "blocks.35." + }, + "blocks_36_": { + "name": "blocks.36." + }, + "blocks_37_": { + "name": "blocks.37." + }, + "blocks_38_": { + "name": "blocks.38." + }, + "blocks_39_": { + "name": "blocks.39." + }, + "blocks_3_": { + "name": "blocks.3." + }, + "blocks_4_": { + "name": "blocks.4." + }, + "blocks_5_": { + "name": "blocks.5." + }, + "blocks_6_": { + "name": "blocks.6." + }, + "blocks_7_": { + "name": "blocks.7." + }, + "blocks_8_": { + "name": "blocks.8." + }, + "blocks_9_": { + "name": "blocks.9." + }, + "head_": { + "name": "head." + }, + "img_emb_": { + "name": "img_emb." + }, + "model1": { + "name": "model1" + }, + "model2": { + "name": "model2" + }, + "patch_embedding_": { + "name": "patch_embedding." + }, + "text_embedding_": { + "name": "text_embedding." + }, + "time_embedding_": { + "name": "time_embedding." + }, + "time_projection_": { + "name": "time_projection." + } + } + }, + "ModelPatchLoader": { + "display_name": "ModelPatchLoader", + "inputs": { + "name": { + "name": "nome" + } + } + }, + "ModelSamplingAuraFlow": { + "display_name": "ModelSamplingAuraFlow", + "inputs": { + "model": { + "name": "modelo" + }, + "shift": { + "name": "deslocamento" + } + } + }, + "ModelSamplingContinuousEDM": { + "display_name": "ModelSamplingContinuousEDM", + "inputs": { + "model": { + "name": "modelo" + }, + "sampling": { + "name": "amostragem" + }, + "sigma_max": { + "name": "sigma_máx" + }, + "sigma_min": { + "name": "sigma_mín" + } + } + }, + "ModelSamplingContinuousV": { + "display_name": "ModelSamplingContinuousV", + "inputs": { + "model": { + "name": "modelo" + }, + "sampling": { + "name": "amostragem" + }, + "sigma_max": { + "name": "sigma_máx" + }, + "sigma_min": { + "name": "sigma_mín" + } + } + }, + "ModelSamplingDiscrete": { + "display_name": "ModelSamplingDiscrete", + "inputs": { + "model": { + "name": "modelo" + }, + "sampling": { + "name": "amostragem" + }, + "zsnr": { + "name": "zsnr" + } + } + }, + "ModelSamplingFlux": { + "display_name": "ModelSamplingFlux", + "inputs": { + "base_shift": { + "name": "deslocamento_base" + }, + "height": { + "name": "altura" + }, + "max_shift": { + "name": "deslocamento_máx" + }, + "model": { + "name": "modelo" + }, + "width": { + "name": "largura" + } + } + }, + "ModelSamplingLTXV": { + "display_name": "ModelSamplingLTXV", + "inputs": { + "base_shift": { + "name": "deslocamento_base" + }, + "latent": { + "name": "latente" + }, + "max_shift": { + "name": "deslocamento_máx" + }, + "model": { + "name": "modelo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ModelSamplingSD3": { + "display_name": "ModelSamplingSD3", + "inputs": { + "model": { + "name": "modelo" + }, + "shift": { + "name": "deslocamento" + } + } + }, + "ModelSamplingStableCascade": { + "display_name": "ModelSamplingStableCascade", + "inputs": { + "model": { + "name": "modelo" + }, + "shift": { + "name": "deslocamento" + } + } + }, + "ModelSave": { + "display_name": "ModelSave", + "inputs": { + "filename_prefix": { + "name": "prefixo_do_arquivo" + }, + "model": { + "name": "modelo" + } + } + }, + "MoonvalleyImg2VideoNode": { + "description": "Nó de Imagem para Vídeo Moonvalley Marey", + "display_name": "Moonvalley Marey Imagem para Vídeo", + "inputs": { + "control_after_generate": { + "name": "controle_após_gerar" + }, + "image": { + "name": "imagem", + "tooltip": "A imagem de referência usada para gerar o vídeo" + }, + "negative_prompt": { + "name": "prompt_negativo", + "tooltip": "Texto do prompt negativo" + }, + "prompt": { + "name": "prompt" + }, + "prompt_adherence": { + "name": "aderência_ao_prompt", + "tooltip": "Escala de orientação para controle da geração" + }, + "resolution": { + "name": "resolução", + "tooltip": "Resolução do vídeo de saída" + }, + "seed": { + "name": "semente", + "tooltip": "Valor da semente aleatória" + }, + "steps": { + "name": "passos", + "tooltip": "Número de passos de remoção de ruído" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MoonvalleyTxt2VideoNode": { + "display_name": "Moonvalley Marey Texto para Vídeo", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Texto de prompt negativo" + }, + "prompt": { + "name": "prompt" + }, + "prompt_adherence": { + "name": "prompt_adherence", + "tooltip": "Escala de orientação para controle da geração" + }, + "resolution": { + "name": "resolution", + "tooltip": "Resolução do vídeo de saída" + }, + "seed": { + "name": "seed", + "tooltip": "Valor da semente aleatória" + }, + "steps": { + "name": "steps", + "tooltip": "Etapas de inferência" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MoonvalleyVideo2VideoNode": { + "display_name": "Moonvalley Marey Vídeo para Vídeo", + "inputs": { + "control_type": { + "name": "control_type" + }, + "motion_intensity": { + "name": "motion_intensity", + "tooltip": "Usado apenas se o control_type for 'Motion Transfer'" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Texto de prompt negativo" + }, + "prompt": { + "name": "prompt", + "tooltip": "Descreve o vídeo a ser gerado" + }, + "seed": { + "name": "seed", + "tooltip": "Valor da semente aleatória" + }, + "steps": { + "name": "steps", + "tooltip": "Número de etapas de inferência" + }, + "video": { + "name": "video", + "tooltip": "O vídeo de referência usado para gerar o vídeo de saída. Deve ter pelo menos 5 segundos de duração. Vídeos com mais de 5s serão automaticamente cortados. Apenas o formato MP4 é suportado." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Morphology": { + "display_name": "ImageMorphology", + "inputs": { + "image": { + "name": "image" + }, + "kernel_size": { + "name": "kernel_size" + }, + "operation": { + "name": "operation" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "NormalizeImages": { + "display_name": "Normalizar Imagens", + "inputs": { + "images": { + "name": "images", + "tooltip": "Imagem a ser processada." + }, + "mean": { + "name": "mean", + "tooltip": "Valor médio para normalização." + }, + "std": { + "name": "std", + "tooltip": "Desvio padrão para normalização." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Imagens processadas" + } + } + }, + "NormalizeVideoLatentStart": { + "description": "Normaliza os quadros iniciais de um latent de vídeo para corresponder à média e ao desvio padrão dos quadros de referência subsequentes. Ajuda a reduzir diferenças entre os quadros iniciais e o restante do vídeo.", + "display_name": "Normalizar Início do Latent de Vídeo", + "inputs": { + "latent": { + "name": "latent" + }, + "reference_frame_count": { + "name": "reference_frame_count", + "tooltip": "Número de quadros latentes após os quadros iniciais a serem usados como referência" + }, + "start_frame_count": { + "name": "start_frame_count", + "tooltip": "Número de quadros latentes a normalizar, contados a partir do início" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, + "OpenAIChatConfig": { + "description": "Permite especificar opções avançadas de configuração para os Nós de Chat OpenAI.", + "display_name": "Opções Avançadas do OpenAI ChatGPT", + "inputs": { + "instructions": { + "name": "instructions", + "tooltip": "Instruções para o modelo sobre como gerar a resposta." + }, + "max_output_tokens": { + "name": "max_output_tokens", + "tooltip": "Um limite superior para o número de tokens que podem ser gerados em uma resposta, incluindo tokens de saída visíveis." + }, + "truncation": { + "name": "truncation", + "tooltip": "A estratégia de truncamento a ser usada para a resposta do modelo. auto: Se o contexto desta resposta e das anteriores exceder o tamanho da janela de contexto do modelo, o modelo irá truncar a resposta para caber na janela de contexto, removendo itens de entrada no meio da conversa. disabled: Se uma resposta do modelo exceder o tamanho da janela de contexto, a solicitação falhará com um erro 400." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "OpenAIChatNode": { + "description": "Gera respostas em texto a partir de um modelo OpenAI.", + "display_name": "OpenAI ChatGPT", + "inputs": { + "advanced_options": { + "name": "advanced_options", + "tooltip": "Configuração opcional para o modelo. Aceita entradas do nó OpenAI Chat Advanced Options." + }, + "files": { + "name": "files", + "tooltip": "Arquivo(s) opcional(is) para usar como contexto para o modelo. Aceita entradas do nó OpenAI Chat Input Files." + }, + "images": { + "name": "images", + "tooltip": "Imagem(ns) opcional(is) para usar como contexto para o modelo. Para incluir várias imagens, você pode usar o nó Batch Images." + }, + "model": { + "name": "model", + "tooltip": "O modelo utilizado para gerar a resposta." + }, + "persist_context": { + "name": "persist_context", + "tooltip": "Este parâmetro está obsoleto e não tem efeito." + }, + "prompt": { + "name": "prompt", + "tooltip": "Entradas de texto para o modelo, usadas para gerar uma resposta." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "OpenAIDalle2": { + "description": "Gera imagens de forma síncrona via endpoint DALL·E 2 da OpenAI.", + "display_name": "OpenAI DALL·E 2", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "image": { + "name": "image", + "tooltip": "Imagem de referência opcional para edição de imagem." + }, + "mask": { + "name": "mask", + "tooltip": "Máscara opcional para inpainting (áreas brancas serão substituídas)" + }, + "n": { + "name": "n", + "tooltip": "Quantas imagens gerar" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto para o DALL·E" + }, + "seed": { + "name": "seed", + "tooltip": "ainda não implementado no backend" + }, + "size": { + "name": "size", + "tooltip": "Tamanho da imagem" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "OpenAIDalle3": { + "description": "Gera imagens de forma síncrona via endpoint DALL·E 3 da OpenAI.", + "display_name": "OpenAI DALL·E 3", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto para o DALL·E" + }, + "quality": { + "name": "qualidade", + "tooltip": "Qualidade da imagem" + }, + "seed": { + "name": "seed", + "tooltip": "ainda não implementado no backend" + }, + "size": { + "name": "tamanho", + "tooltip": "Tamanho da imagem" + }, + "style": { + "name": "estilo", + "tooltip": "Vivo faz o modelo tender a gerar imagens hiper-realistas e dramáticas. Natural faz o modelo produzir imagens mais naturais, com aparência menos hiper-realista." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "OpenAIGPTImage1": { + "description": "Gera imagens de forma síncrona via endpoint GPT Image 1 da OpenAI.", + "display_name": "OpenAI GPT Image 1", + "inputs": { + "background": { + "name": "fundo", + "tooltip": "Retornar imagem com ou sem fundo" + }, + "control_after_generate": { + "name": "controle após gerar" + }, + "image": { + "name": "imagem", + "tooltip": "Imagem de referência opcional para edição de imagem." + }, + "mask": { + "name": "mask", + "tooltip": "Máscara opcional para inpainting (áreas brancas serão substituídas)" + }, + "model": { + "name": "modelo" + }, + "n": { + "name": "n", + "tooltip": "Quantas imagens gerar" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto para GPT Image" + }, + "quality": { + "name": "qualidade", + "tooltip": "Qualidade da imagem, afeta o custo e o tempo de geração." + }, + "seed": { + "name": "seed", + "tooltip": "ainda não implementado no backend" + }, + "size": { + "name": "tamanho", + "tooltip": "Tamanho da imagem" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "OpenAIInputFiles": { + "description": "Carrega e prepara arquivos de entrada (texto, pdf, etc.) para incluir como entradas no Nó de Chat da OpenAI. Os arquivos serão lidos pelo modelo OpenAI ao gerar uma resposta. 🛈 DICA: Pode ser encadeado com outros nós de Arquivo de Entrada OpenAI.", + "display_name": "Arquivos de Entrada do OpenAI ChatGPT", + "inputs": { + "OPENAI_INPUT_FILES": { + "name": "OPENAI_INPUT_FILES", + "tooltip": "Arquivo(s) adicional(is) opcional(is) para agrupar junto com o arquivo carregado deste nó. Permite o encadeamento de arquivos de entrada para que uma única mensagem possa incluir múltiplos arquivos." + }, + "file": { + "name": "arquivo", + "tooltip": "Arquivos de entrada para incluir como contexto para o modelo. Aceita apenas arquivos de texto (.txt) e PDF (.pdf) por enquanto." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "OpenAIVideoSora2": { + "description": "Geração de vídeo e áudio da OpenAI.", + "display_name": "OpenAI Sora - Vídeo", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "duration": { + "name": "duração" + }, + "image": { + "name": "imagem" + }, + "model": { + "name": "modelo" + }, + "prompt": { + "name": "prompt", + "tooltip": "Texto guia; pode estar vazio se houver uma imagem de entrada." + }, + "seed": { + "name": "seed", + "tooltip": "Seed para determinar se o nó deve ser executado novamente; os resultados reais são não determinísticos independentemente do seed." + }, + "size": { + "name": "tamanho" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "OptimalStepsScheduler": { + "display_name": "Agendador de Passos Ótimos", + "inputs": { + "denoise": { + "name": "reduzir_ruído" + }, + "model_type": { + "name": "tipo_de_modelo" + }, + "steps": { + "name": "passos" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PairConditioningCombine": { + "display_name": "Combinar Par de Condições", + "inputs": { + "negative_A": { + "name": "negativo_A" + }, + "negative_B": { + "name": "negativo_B" + }, + "positive_A": { + "name": "positivo_A" + }, + "positive_B": { + "name": "positivo_B" + } + }, + "outputs": { + "0": { + "name": "positivo" + }, + "1": { + "name": "negativo" + } + } + }, + "PairConditioningSetDefaultCombine": { + "display_name": "Combinar Par de Condições Padrão", + "inputs": { + "hooks": { + "name": "ganchos" + }, + "negative": { + "name": "negativo" + }, + "negative_DEFAULT": { + "name": "negativo_PADRÃO" + }, + "positive": { + "name": "positivo" + }, + "positive_DEFAULT": { + "name": "positivo_PADRÃO" + } + }, + "outputs": { + "0": { + "name": "positivo" + }, + "1": { + "name": "negativo" + } + } + }, + "PairConditioningSetProperties": { + "display_name": "Definir Propriedades do Par de Condições", + "inputs": { + "hooks": { + "name": "ganchos" + }, + "mask": { + "name": "máscara" + }, + "negative_NEW": { + "name": "negativo_NOVO" + }, + "positive_NEW": { + "name": "positivo_NOVO" + }, + "set_cond_area": { + "name": "definir_área_cond" + }, + "strength": { + "name": "força" + }, + "timesteps": { + "name": "etapas" + } + }, + "outputs": { + "0": { + "name": "positivo" + }, + "1": { + "name": "negativo" + } + } + }, + "PairConditioningSetPropertiesAndCombine": { + "display_name": "Definir Propriedades e Combinar Par de Condições", + "inputs": { + "hooks": { + "name": "ganchos" + }, + "mask": { + "name": "máscara" + }, + "negative": { + "name": "negativo" + }, + "negative_NEW": { + "name": "negativo_NOVO" + }, + "positive": { + "name": "positivo" + }, + "positive_NEW": { + "name": "positivo_NOVO" + }, + "set_cond_area": { + "name": "definir_área_cond" + }, + "strength": { + "name": "força" + }, + "timesteps": { + "name": "etapas" + } + }, + "outputs": { + "0": { + "name": "positivo" + }, + "1": { + "name": "negativo" + } + } + }, + "PatchModelAddDownscale": { + "display_name": "PatchModelAddDownscale (Kohya Deep Shrink)", + "inputs": { + "block_number": { + "name": "número_do_bloco" + }, + "downscale_after_skip": { + "name": "reduzir_após_pular" + }, + "downscale_factor": { + "name": "fator_de_redução" + }, + "downscale_method": { + "name": "método_de_redução" + }, + "end_percent": { + "name": "percentual_final" + }, + "model": { + "name": "modelo" + }, + "start_percent": { + "name": "percentual_inicial" + }, + "upscale_method": { + "name": "método_de_ampliação" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PerpNeg": { + "display_name": "Perp-Neg (OBSOLETO por PerpNegGuider)", + "inputs": { + "empty_conditioning": { + "name": "empty_conditioning" + }, + "model": { + "name": "model" + }, + "neg_scale": { + "name": "neg_scale" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PerpNegGuider": { + "display_name": "PerpNegGuider", + "inputs": { + "cfg": { + "name": "cfg" + }, + "empty_conditioning": { + "name": "empty_conditioning" + }, + "model": { + "name": "model" + }, + "neg_scale": { + "name": "neg_scale" + }, + "negative": { + "name": "negative" + }, + "positive": { + "name": "positive" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PerturbedAttentionGuidance": { + "display_name": "PerturbedAttentionGuidance", + "inputs": { + "model": { + "name": "model" + }, + "scale": { + "name": "scale" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PhotoMakerEncode": { + "display_name": "PhotoMakerEncode", + "inputs": { + "clip": { + "name": "clip" + }, + "image": { + "name": "imagem" + }, + "photomaker": { + "name": "photomaker" + }, + "text": { + "name": "texto" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PhotoMakerLoader": { + "display_name": "PhotoMakerLoader", + "inputs": { + "photomaker_model_name": { + "name": "photomaker_model_name" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PixverseImageToVideoNode": { + "description": "Gera vídeos com base no prompt e no output_size.", + "display_name": "PixVerse Imagem para Vídeo", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "duration_seconds": { + "name": "duração_segundos" + }, + "image": { + "name": "imagem" + }, + "motion_mode": { + "name": "modo_de_movimento" + }, + "negative_prompt": { + "name": "prompt_negativo", + "tooltip": "Uma descrição opcional em texto de elementos indesejados em uma imagem." + }, + "pixverse_template": { + "name": "pixverse_template", + "tooltip": "Um template opcional para influenciar o estilo da geração, criado pelo nó PixVerse Template." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para a geração do vídeo" + }, + "quality": { + "name": "qualidade" + }, + "seed": { + "name": "semente", + "tooltip": "Semente para geração do vídeo." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PixverseTemplateNode": { + "display_name": "PixVerse Template", + "inputs": { + "template": { + "name": "template" + } + }, + "outputs": { + "0": { + "name": "pixverse_template", + "tooltip": null + } + } + }, + "PixverseTextToVideoNode": { + "description": "Gera vídeos com base no prompt e no output_size.", + "display_name": "PixVerse Texto para Vídeo", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "control_after_generate": { + "name": "control after generate" + }, + "duration_seconds": { + "name": "duration_seconds" + }, + "motion_mode": { + "name": "motion_mode" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Uma descrição opcional de elementos indesejados em uma imagem." + }, + "pixverse_template": { + "name": "pixverse_template", + "tooltip": "Um template opcional para influenciar o estilo da geração, criado pelo nó PixVerse Template." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para a geração do vídeo" + }, + "quality": { + "name": "quality" + }, + "seed": { + "name": "seed", + "tooltip": "Seed para geração do vídeo." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PixverseTransitionVideoNode": { + "description": "Gera vídeos com base no prompt e no output_size.", + "display_name": "PixVerse Transição de Vídeo", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "duration_seconds": { + "name": "duration_seconds" + }, + "first_frame": { + "name": "first_frame" + }, + "last_frame": { + "name": "last_frame" + }, + "motion_mode": { + "name": "motion_mode" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Uma descrição opcional de elementos indesejados em uma imagem." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para a geração do vídeo" + }, + "quality": { + "name": "quality" + }, + "seed": { + "name": "seed", + "tooltip": "Seed para geração do vídeo." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PolyexponentialScheduler": { + "display_name": "PolyexponentialScheduler", + "inputs": { + "rho": { + "name": "rho" + }, + "sigma_max": { + "name": "sigma_max" + }, + "sigma_min": { + "name": "sigma_min" + }, + "steps": { + "name": "steps" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PorterDuffImageComposite": { + "display_name": "Composição de Imagem Porter-Duff", + "inputs": { + "destination": { + "name": "destination" + }, + "destination_alpha": { + "name": "destination_alpha" + }, + "mode": { + "name": "mode" + }, + "source": { + "name": "source" + }, + "source_alpha": { + "name": "source_alpha" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "tooltip": null + } + } + }, + "Preview3D": { + "display_name": "Pré-visualização 3D & Animação", + "inputs": { + "bg_image": { + "name": "bg_image" + }, + "camera_info": { + "name": "camera_info" + }, + "image": { + "name": "image" + }, + "model_file": { + "name": "model_file" + } + } + }, + "PreviewAny": { + "display_name": "Pré-visualizar como Texto", + "inputs": { + "preview": { + }, + "previewMode": { + }, + "source": { + "name": "source" + } + } + }, + "PreviewAudio": { + "display_name": "Pré-visualizar Áudio", + "inputs": { + "audio": { + "name": "audio" + }, + "audioUI": { + "name": "audioUI" + } + } + }, + "PreviewImage": { + "description": "Salva as imagens de entrada no seu diretório de saída do ComfyUI.", + "display_name": "Imagem de Pré-visualização", + "inputs": { + "images": { + "name": "imagens" + } + } + }, + "PrimitiveBoolean": { + "display_name": "Booleano", + "inputs": { + "value": { + "name": "valor" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PrimitiveFloat": { + "display_name": "Ponto Flutuante", + "inputs": { + "value": { + "name": "valor" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PrimitiveInt": { + "display_name": "Inteiro", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "value": { + "name": "valor" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PrimitiveString": { + "display_name": "Texto", + "inputs": { + "value": { + "name": "valor" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "PrimitiveStringMultiline": { + "display_name": "Texto (Multilinha)", + "inputs": { + "value": { + "name": "valor" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "QuadrupleCLIPLoader": { + "description": "[Receitas]\n\nhidream: long clip-l, long clip-g, t5xxl, llama_8b_3.1_instruct", + "display_name": "QuadrupleCLIPLoader", + "inputs": { + "clip_name1": { + "name": "clip_name1" + }, + "clip_name2": { + "name": "clip_name2" + }, + "clip_name3": { + "name": "clip_name3" + }, + "clip_name4": { + "name": "clip_name4" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "QwenImageDiffsynthControlnet": { + "display_name": "QwenImageDiffsynthControlnet", + "inputs": { + "image": { + "name": "imagem" + }, + "mask": { + "name": "máscara" + }, + "model": { + "name": "modelo" + }, + "model_patch": { + "name": "patch do modelo" + }, + "strength": { + "name": "força" + }, + "vae": { + "name": "vae" + } + } + }, + "RandomCropImages": { + "display_name": "Corte Aleatório de Imagens", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "height": { + "name": "altura", + "tooltip": "Altura do corte." + }, + "images": { + "name": "imagens", + "tooltip": "Imagem para processar." + }, + "seed": { + "name": "semente", + "tooltip": "Semente aleatória." + }, + "width": { + "name": "largura", + "tooltip": "Largura do corte." + } + }, + "outputs": { + "0": { + "name": "imagens", + "tooltip": "Imagens processadas" + } + } + }, + "RandomNoise": { + "display_name": "Ruído Aleatório", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "noise_seed": { + "name": "semente do ruído" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RebatchImages": { + "display_name": "Reagrupar Imagens", + "inputs": { + "batch_size": { + "name": "tamanho do lote" + }, + "images": { + "name": "imagens" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RebatchLatents": { + "display_name": "Reagrupar Latents", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "latents": { + "name": "latents" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RecordAudio": { + "display_name": "Gravar Áudio", + "inputs": { + "audio": { + "name": "áudio" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RecraftColorRGB": { + "description": "Crie uma cor Recraft escolhendo valores RGB específicos.", + "display_name": "Recraft Cor RGB", + "inputs": { + "b": { + "name": "b", + "tooltip": "Valor azul da cor." + }, + "g": { + "name": "g", + "tooltip": "Valor verde da cor." + }, + "r": { + "name": "r", + "tooltip": "Valor vermelho da cor." + }, + "recraft_color": { + "name": "recraft_color" + } + }, + "outputs": { + "0": { + "name": "recraft_color", + "tooltip": null + } + } + }, + "RecraftControls": { + "description": "Crie Controles Recraft para personalizar a geração Recraft.", + "display_name": "Controles Recraft", + "inputs": { + "background_color": { + "name": "cor_de_fundo" + }, + "colors": { + "name": "cores" + } + }, + "outputs": { + "0": { + "name": "recraft_controls", + "tooltip": null + } + } + }, + "RecraftCreativeUpscaleNode": { + "description": "Aumente a imagem de forma síncrona.\nAprimora uma imagem rasterizada usando a ferramenta de ‘ampliação criativa’, aumentando a resolução com foco no refinamento de pequenos detalhes e rostos.", + "display_name": "Recraft Ampliação Criativa de Imagem", + "inputs": { + "image": { + "name": "imagem" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RecraftCrispUpscaleNode": { + "description": "Aumente a imagem de forma síncrona.\nAprimora uma imagem rasterizada usando a ferramenta de ‘ampliação nítida’, aumentando a resolução da imagem, tornando-a mais nítida e limpa.", + "display_name": "Recraft Ampliação Nítida de Imagem", + "inputs": { + "image": { + "name": "imagem" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RecraftImageInpaintingNode": { + "description": "Modifique a imagem com base no prompt e na máscara.", + "display_name": "Recraft Preenchimento de Imagem", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "image": { + "name": "imagem" + }, + "mask": { + "name": "máscara" + }, + "n": { + "name": "n", + "tooltip": "O número de imagens a serem geradas." + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Uma descrição opcional em texto de elementos indesejados em uma imagem." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para a geração da imagem." + }, + "recraft_style": { + "name": "recraft_style" + }, + "seed": { + "name": "semente", + "tooltip": "Semente para determinar se o nó deve ser executado novamente; os resultados reais são não determinísticos independentemente da semente." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RecraftImageToImageNode": { + "description": "Modifique a imagem com base no prompt e intensidade.", + "display_name": "Recraft Imagem para Imagem", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "image": { + "name": "imagem" + }, + "n": { + "name": "n", + "tooltip": "Número de imagens a serem geradas." + }, + "negative_prompt": { + "name": "prompt_negativo", + "tooltip": "Uma descrição opcional em texto de elementos indesejados na imagem." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para a geração da imagem." + }, + "recraft_controls": { + "name": "recraft_controls", + "tooltip": "Controles adicionais opcionais sobre a geração via o nó Recraft Controls." + }, + "recraft_style": { + "name": "recraft_style" + }, + "seed": { + "name": "semente", + "tooltip": "Semente para determinar se o nó deve ser executado novamente; os resultados reais são não determinísticos independentemente da semente." + }, + "strength": { + "name": "intensidade", + "tooltip": "Define a diferença em relação à imagem original, deve estar em [0, 1], onde 0 significa quase idêntico e 1 significa semelhança mínima." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RecraftRemoveBackgroundNode": { + "description": "Remove o fundo da imagem e retorna a imagem processada e a máscara.", + "display_name": "Recraft Remover Fundo", + "inputs": { + "image": { + "name": "imagem" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "tooltip": null + } + } + }, + "RecraftReplaceBackgroundNode": { + "description": "Substitua o fundo da imagem com base no prompt fornecido.", + "display_name": "Recraft Substituir Fundo", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "image": { + "name": "imagem" + }, + "n": { + "name": "n", + "tooltip": "Número de imagens a serem geradas." + }, + "negative_prompt": { + "name": "prompt_negativo", + "tooltip": "Uma descrição opcional em texto de elementos indesejados na imagem." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para a geração da imagem." + }, + "recraft_style": { + "name": "recraft_style" + }, + "seed": { + "name": "semente", + "tooltip": "Semente para determinar se o nó deve ser executado novamente; os resultados reais são não determinísticos independentemente da semente." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RecraftStyleV3DigitalIllustration": { + "description": "Selecione o estilo realistic_image e subestilo opcional.", + "display_name": "Recraft Estilo - Ilustração Digital", + "inputs": { + "substyle": { + "name": "subestilo" + } + }, + "outputs": { + "0": { + "name": "recraft_style", + "tooltip": null + } + } + }, + "RecraftStyleV3InfiniteStyleLibrary": { + "description": "Selecione o estilo com base no UUID pré-existente da Biblioteca de Estilos Infinita do Recraft.", + "display_name": "Recraft Estilo - Biblioteca de Estilos Infinita", + "inputs": { + "style_id": { + "name": "style_id", + "tooltip": "UUID do estilo da Biblioteca de Estilos Infinita." + } + }, + "outputs": { + "0": { + "name": "recraft_style", + "tooltip": null + } + } + }, + "RecraftStyleV3LogoRaster": { + "description": "Selecione o estilo realistic_image e subestilo opcional.", + "display_name": "Recraft Estilo - Logo Raster", + "inputs": { + "substyle": { + "name": "subestilo" + } + }, + "outputs": { + "0": { + "name": "recraft_style", + "tooltip": null + } + } + }, + "RecraftStyleV3RealisticImage": { + "description": "Selecione o estilo realistic_image e um subestilo opcional.", + "display_name": "Recraft Style - Imagem Realista", + "inputs": { + "substyle": { + "name": "substyle" + } + }, + "outputs": { + "0": { + "name": "recraft_style", + "tooltip": null + } + } + }, + "RecraftTextToImageNode": { + "description": "Gera imagens de forma síncrona com base no prompt e na resolução.", + "display_name": "Recraft Texto para Imagem", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "n": { + "name": "n", + "tooltip": "O número de imagens a serem geradas." + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Uma descrição opcional em texto de elementos indesejados na imagem." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para a geração da imagem." + }, + "recraft_controls": { + "name": "recraft_controls", + "tooltip": "Controles adicionais opcionais sobre a geração via o nó Recraft Controls." + }, + "recraft_style": { + "name": "recraft_style" + }, + "seed": { + "name": "seed", + "tooltip": "Seed para determinar se o nó deve ser executado novamente; os resultados reais são não determinísticos independentemente do seed." + }, + "size": { + "name": "size", + "tooltip": "O tamanho da imagem gerada." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RecraftTextToVectorNode": { + "description": "Gera SVG de forma síncrona com base no prompt e na resolução.", + "display_name": "Recraft Texto para Vetor", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "n": { + "name": "n", + "tooltip": "O número de imagens a serem geradas." + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Uma descrição opcional em texto de elementos indesejados na imagem." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt para a geração da imagem." + }, + "recraft_controls": { + "name": "recraft_controls", + "tooltip": "Controles adicionais opcionais sobre a geração via o nó Recraft Controls." + }, + "seed": { + "name": "seed", + "tooltip": "Seed para determinar se o nó deve ser executado novamente; os resultados reais são não determinísticos independentemente do seed." + }, + "size": { + "name": "size", + "tooltip": "O tamanho da imagem gerada." + }, + "substyle": { + "name": "substyle" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RecraftVectorizeImageNode": { + "description": "Gera SVG de forma síncrona a partir de uma imagem de entrada.", + "display_name": "Recraft Vetorizar Imagem", + "inputs": { + "image": { + "name": "image" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ReferenceLatent": { + "description": "Este nó define o latent de referência para um modelo de edição. Se o modelo suportar, você pode encadear vários para definir múltiplas imagens de referência.", + "display_name": "ReferenceLatent", + "inputs": { + "conditioning": { + "name": "conditioning" + }, + "latent": { + "name": "latent" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RegexExtract": { + "display_name": "Extração por Regex", + "inputs": { + "case_insensitive": { + "name": "ignorar_maiusculas_minusculas" + }, + "dotall": { + "name": "dotall" + }, + "group_index": { + "name": "indice_grupo" + }, + "mode": { + "name": "modo" + }, + "multiline": { + "name": "multilinha" + }, + "regex_pattern": { + "name": "regex_pattern" + }, + "string": { + "name": "string" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RegexMatch": { + "display_name": "Correspondência Regex", + "inputs": { + "case_insensitive": { + "name": "ignorar_maiusculas_minusculas" + }, + "dotall": { + "name": "dotall" + }, + "multiline": { + "name": "multilinha" + }, + "regex_pattern": { + "name": "regex_pattern" + }, + "string": { + "name": "string" + } + }, + "outputs": { + "0": { + "name": "correspondencias", + "tooltip": null + } + } + }, + "RegexReplace": { + "description": "Localize e substitua texto usando padrões regex.", + "display_name": "Substituir por Regex", + "inputs": { + "case_insensitive": { + "name": "ignorar_maiusculas_minusculas" + }, + "count": { + "name": "quantidade", + "tooltip": "Número máximo de substituições a serem feitas. Defina como 0 para substituir todas as ocorrências (padrão). Defina como 1 para substituir apenas a primeira correspondência, 2 para as duas primeiras, etc." + }, + "dotall": { + "name": "dotall", + "tooltip": "Quando ativado, o ponto (.) corresponderá a qualquer caractere, incluindo quebras de linha. Quando desativado, pontos não corresponderão a quebras de linha." + }, + "multiline": { + "name": "multilinha" + }, + "regex_pattern": { + "name": "regex_pattern" + }, + "replace": { + "name": "substituir" + }, + "string": { + "name": "string" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RenormCFG": { + "display_name": "RenormCFG", + "inputs": { + "cfg_trunc": { + "name": "cfg_trunc" + }, + "model": { + "name": "modelo" + }, + "renorm_cfg": { + "name": "renorm_cfg" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RepeatImageBatch": { + "display_name": "Repetir Lote de Imagem", + "inputs": { + "amount": { + "name": "quantidade" + }, + "image": { + "name": "imagem" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RepeatLatentBatch": { + "display_name": "Repetir Lote Latent", + "inputs": { + "amount": { + "name": "quantidade" + }, + "samples": { + "name": "amostras" + } + } + }, + "ReplaceText": { + "display_name": "Substituir Texto", + "inputs": { + "find": { + "name": "procurar", + "tooltip": "Texto a ser encontrado." + }, + "replace": { + "name": "substituir", + "tooltip": "Texto para substituir." + }, + "texts": { + "name": "textos", + "tooltip": "Texto a ser processado." + } + }, + "outputs": { + "0": { + "name": "textos", + "tooltip": "Textos processados" + } + } + }, + "ReplaceVideoLatentFrames": { + "display_name": "ReplaceVideoLatentFrames", + "inputs": { + "destination": { + "name": "destino", + "tooltip": "O espaço latente de destino onde os quadros serão substituídos." + }, + "index": { + "name": "índice", + "tooltip": "O índice inicial do quadro latente no destino onde os quadros do espaço latente de origem serão inseridos. Valores negativos contam a partir do final." + }, + "source": { + "name": "origem", + "tooltip": "O espaço latente de origem fornecendo quadros para inserir no espaço latente de destino. Se não for fornecido, o espaço latente de destino é retornado sem alterações." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RescaleCFG": { + "display_name": "RescaleCFG", + "inputs": { + "model": { + "name": "modelo" + }, + "multiplier": { + "name": "multiplicador" + } + } + }, + "ResizeAndPadImage": { + "display_name": "ResizeAndPadImage", + "inputs": { + "image": { + "name": "imagem" + }, + "interpolation": { + "name": "interpolação" + }, + "padding_color": { + "name": "cor_do_preenchimento" + }, + "target_height": { + "name": "altura_alvo" + }, + "target_width": { + "name": "largura_alvo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ResizeImageMaskNode": { + "description": "Redimensione uma imagem ou mask usando vários métodos de escala.", + "display_name": "Redimensionar Imagem/Máscara", + "inputs": { + "input": { + "name": "entrada" + }, + "resize_type": { + "name": "tipo_de_redimensionamento", + "tooltip": "Selecione como redimensionar: por dimensões exatas, fator de escala, correspondendo a outra imagem, etc." + }, + "resize_type_crop": { + "name": "cortar" + }, + "resize_type_height": { + "name": "altura" + }, + "resize_type_width": { + "name": "largura" + }, + "scale_method": { + "name": "método_de_escala", + "tooltip": "Algoritmo de interpolação. 'area' é melhor para reduzir, 'lanczos' para aumentar, 'nearest-exact' para pixel art." + } + }, + "outputs": { + "0": { + "name": "redimensionado", + "tooltip": null + } + } + }, + "ResizeImagesByLongerEdge": { + "display_name": "Redimensionar Imagens pela Borda Maior", + "inputs": { + "images": { + "name": "imagens", + "tooltip": "Imagem para processar." + }, + "longer_edge": { + "name": "borda_maior", + "tooltip": "Comprimento alvo para a borda maior." + } + }, + "outputs": { + "0": { + "name": "imagens", + "tooltip": "Imagens processadas" + } + } + }, + "ResizeImagesByShorterEdge": { + "display_name": "Redimensionar Imagens pela Borda Menor", + "inputs": { + "images": { + "name": "imagens", + "tooltip": "Imagem para processar." + }, + "shorter_edge": { + "name": "borda_menor", + "tooltip": "Comprimento alvo para a borda menor." + } + }, + "outputs": { + "0": { + "name": "imagens", + "tooltip": "Imagens processadas" + } + } + }, + "ResolutionBucket": { + "display_name": "Bucket de Resolução", + "inputs": { + "conditioning": { + "name": "condicionamento", + "tooltip": "Lista de listas de condicionamento (deve corresponder ao comprimento dos latentes)." + }, + "latents": { + "name": "latentes", + "tooltip": "Lista de dicionários de espaço latente para agrupar por resolução." + } + }, + "outputs": { + "0": { + "name": "latentes", + "tooltip": "Lista de dicionários de espaço latente em lotes, um por bucket de resolução." + }, + "1": { + "name": "condicionamento", + "tooltip": "Lista de listas de condição, uma por bucket de resolução." + } + } + }, + "Rodin3D_Detail": { + "description": "Gerar ativos 3D usando a API Rodin", + "display_name": "Rodin 3D Gerar - Geração Detalhada", + "inputs": { + "Images": { + "name": "Imagens" + }, + "Material_Type": { + "name": "Tipo de Material" + }, + "Polygon_count": { + "name": "Contagem de Polígonos" + }, + "Seed": { + "name": "Semente" + } + }, + "outputs": { + "0": { + "name": "Caminho do Modelo 3D", + "tooltip": null + } + } + }, + "Rodin3D_Gen2": { + "description": "Gerar ativos 3D usando a API Rodin", + "display_name": "Rodin 3D Gerar - Geração Gen-2", + "inputs": { + "Images": { + "name": "Imagens" + }, + "Material_Type": { + "name": "Tipo de Material" + }, + "Polygon_count": { + "name": "Contagem de Polígonos" + }, + "Seed": { + "name": "Semente" + }, + "TAPose": { + "name": "TAPose" + } + }, + "outputs": { + "0": { + "name": "Caminho do Modelo 3D", + "tooltip": null + } + } + }, + "Rodin3D_Regular": { + "description": "Gerar ativos 3D usando a API Rodin", + "display_name": "Rodin 3D Gerar - Geração Regular", + "inputs": { + "Images": { + "name": "Imagens" + }, + "Material_Type": { + "name": "Tipo de Material" + }, + "Polygon_count": { + "name": "Contagem de Polígonos" + }, + "Seed": { + "name": "Semente" + } + }, + "outputs": { + "0": { + "name": "Caminho do Modelo 3D", + "tooltip": null + } + } + }, + "Rodin3D_Sketch": { + "description": "Gerar ativos 3D usando a API Rodin", + "display_name": "Rodin 3D Gerar - Geração de Esboço", + "inputs": { + "Images": { + "name": "Imagens" + }, + "Seed": { + "name": "Semente" + } + }, + "outputs": { + "0": { + "name": "Caminho do Modelo 3D", + "tooltip": null + } + } + }, + "Rodin3D_Smooth": { + "description": "Gerar ativos 3D usando a API Rodin", + "display_name": "Rodin 3D Gerar - Geração Suave", + "inputs": { + "Images": { + "name": "Imagens" + }, + "Material_Type": { + "name": "Tipo de Material" + }, + "Polygon_count": { + "name": "Contagem de Polígonos" + }, + "Seed": { + "name": "Semente" + } + }, + "outputs": { + "0": { + "name": "Caminho do Modelo 3D", + "tooltip": null + } + } + }, + "RunwayFirstLastFrameNode": { + "description": "Envie os quadros-chave inicial e final, escreva um prompt e gere um vídeo. Transições mais complexas, como casos em que o quadro final é completamente diferente do inicial, podem se beneficiar da duração mais longa de 10s. Isso dará mais tempo para a geração fazer uma transição suave entre as duas entradas. Antes de começar, revise estas melhores práticas para garantir que suas seleções de entrada ajudarão no sucesso da geração: https://help.runwayml.com/hc/en-us/articles/34170748696595-Creating-with-Keyframes-on-Gen-3.", + "display_name": "Runway Quadro Inicial-Final para Vídeo", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "duration": { + "name": "duração" + }, + "end_frame": { + "name": "quadro_final", + "tooltip": "Quadro final a ser usado para o vídeo. Suportado apenas para gen3a_turbo." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto para a geração" + }, + "ratio": { + "name": "proporção" + }, + "seed": { + "name": "semente", + "tooltip": "Semente aleatória para a geração" + }, + "start_frame": { + "name": "quadro_inicial", + "tooltip": "Quadro inicial a ser usado para o vídeo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RunwayImageToVideoNodeGen3a": { + "description": "Gere um vídeo a partir de um único quadro inicial usando o modelo Gen3a Turbo. Antes de começar, revise estas melhores práticas para garantir que suas seleções de entrada ajudarão no sucesso da geração: https://help.runwayml.com/hc/en-us/articles/33927968552339-Creating-with-Act-One-on-Gen-3-Alpha-and-Turbo.", + "display_name": "Runway Imagem para Vídeo (Gen3a Turbo)", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "duration": { + "name": "duração" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto para a geração" + }, + "ratio": { + "name": "proporção" + }, + "seed": { + "name": "semente", + "tooltip": "Semente aleatória para a geração" + }, + "start_frame": { + "name": "quadro_inicial", + "tooltip": "Quadro inicial a ser usado para o vídeo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RunwayImageToVideoNodeGen4": { + "description": "Gere um vídeo a partir de um único quadro inicial usando o modelo Gen4 Turbo. Antes de começar, revise estas melhores práticas para garantir que suas seleções de entrada ajudarão no sucesso da geração: https://help.runwayml.com/hc/en-us/articles/37327109429011-Creating-with-Gen-4-Video.", + "display_name": "Runway Imagem para Vídeo (Gen4 Turbo)", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "duration": { + "name": "duração" + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto para a geração" + }, + "ratio": { + "name": "proporção" + }, + "seed": { + "name": "semente", + "tooltip": "Semente aleatória para a geração" + }, + "start_frame": { + "name": "quadro_inicial", + "tooltip": "Quadro inicial a ser usado para o vídeo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "RunwayTextToImageNode": { + "description": "Gere uma imagem a partir de um prompt de texto usando o modelo Gen 4 da Runway. Você também pode incluir uma imagem de referência para guiar a geração.", + "display_name": "Runway Texto para Imagem", + "inputs": { + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto para a geração" + }, + "ratio": { + "name": "proporção" + }, + "reference_image": { + "name": "imagem_de_referência", + "tooltip": "Imagem de referência opcional para guiar a geração" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SDTurboScheduler": { + "display_name": "SDTurboScheduler", + "inputs": { + "denoise": { + "name": "redução_de_ruído" + }, + "model": { + "name": "modelo" + }, + "steps": { + "name": "passos" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SD_4XUpscale_Conditioning": { + "display_name": "SD_4XUpscale_Conditioning", + "inputs": { + "images": { + "name": "imagens" + }, + "negative": { + "name": "negativo" + }, + "noise_augmentation": { + "name": "aumento_de_ruído" + }, + "positive": { + "name": "positivo" + }, + "scale_ratio": { + "name": "fator_de_escala" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "SV3D_Conditioning": { + "display_name": "SV3D_Conditioning", + "inputs": { + "clip_vision": { + "name": "clip_vision" + }, + "elevation": { + "name": "elevação" + }, + "height": { + "name": "altura" + }, + "init_image": { + "name": "imagem_inicial" + }, + "vae": { + "name": "vae" + }, + "video_frames": { + "name": "quadros_de_vídeo" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "SVD_img2vid_Conditioning": { + "display_name": "SVD_img2vid_Conditioning", + "inputs": { + "augmentation_level": { + "name": "nível_de_aumento" + }, + "clip_vision": { + "name": "clip_vision" + }, + "fps": { + "name": "fps" + }, + "height": { + "name": "altura" + }, + "init_image": { + "name": "imagem_inicial" + }, + "motion_bucket_id": { + "name": "id_bucket_movimento" + }, + "vae": { + "name": "vae" + }, + "video_frames": { + "name": "quadros_de_vídeo" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "positivo" + }, + "1": { + "name": "negativo" + }, + "2": { + "name": "latent" + } + } + }, + "SamplerCustom": { + "display_name": "SamplerCustom", + "inputs": { + "add_noise": { + "name": "adicionar_ruído" + }, + "cfg": { + "name": "cfg" + }, + "control_after_generate": { + "name": "controle após gerar" + }, + "latent_image": { + "name": "imagem_latente" + }, + "model": { + "name": "modelo" + }, + "negative": { + "name": "negativo" + }, + "noise_seed": { + "name": "semente_de_ruído" + }, + "positive": { + "name": "positivo" + }, + "sampler": { + "name": "amostrador" + }, + "sigmas": { + "name": "sigmas" + } + }, + "outputs": { + "0": { + "name": "saída", + "tooltip": null + }, + "1": { + "name": "saída_denoisada", + "tooltip": null + } + } + }, + "SamplerCustomAdvanced": { + "display_name": "SamplerCustomAdvanced", + "inputs": { + "guider": { + "name": "guia" + }, + "latent_image": { + "name": "imagem_latente" + }, + "noise": { + "name": "ruído" + }, + "sampler": { + "name": "amostrador" + }, + "sigmas": { + "name": "sigmas" + } + }, + "outputs": { + "0": { + "name": "saída", + "tooltip": null + }, + "1": { + "name": "saída_denoisada", + "tooltip": null + } + } + }, + "SamplerDPMAdaptative": { + "display_name": "SamplerDPMAdaptative", + "inputs": { + "accept_safety": { + "name": "aceitar_segurança" + }, + "atol": { + "name": "atol" + }, + "dcoeff": { + "name": "dcoeff" + }, + "eta": { + "name": "eta" + }, + "h_init": { + "name": "h_init" + }, + "icoeff": { + "name": "icoeff" + }, + "order": { + "name": "ordem" + }, + "pcoeff": { + "name": "pcoeff" + }, + "rtol": { + "name": "rtol" + }, + "s_noise": { + "name": "s_ruído" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerDPMPP_2M_SDE": { + "display_name": "SamplerDPMPP_2M_SDE", + "inputs": { + "eta": { + "name": "eta" + }, + "noise_device": { + "name": "dispositivo_de_ruído" + }, + "s_noise": { + "name": "s_ruído" + }, + "solver_type": { + "name": "tipo_de_solvedor" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerDPMPP_2S_Ancestral": { + "display_name": "SamplerDPMPP_2S_Ancestral", + "inputs": { + "eta": { + "name": "eta" + }, + "s_noise": { + "name": "s_ruído" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerDPMPP_3M_SDE": { + "display_name": "SamplerDPMPP_3M_SDE", + "inputs": { + "eta": { + "name": "eta" + }, + "noise_device": { + "name": "noise_device" + }, + "s_noise": { + "name": "s_noise" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerDPMPP_SDE": { + "display_name": "SamplerDPMPP_SDE", + "inputs": { + "eta": { + "name": "eta" + }, + "noise_device": { + "name": "noise_device" + }, + "r": { + "name": "r" + }, + "s_noise": { + "name": "s_noise" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerER_SDE": { + "display_name": "SamplerER_SDE", + "inputs": { + "eta": { + "name": "eta", + "tooltip": "Intensidade estocástica do SDE de tempo reverso.\nQuando eta=0, reduz para ODE determinístico. Esta configuração não se aplica ao tipo de solver ER-SDE." + }, + "max_stage": { + "name": "max_stage" + }, + "s_noise": { + "name": "s_noise" + }, + "solver_type": { + "name": "solver_type" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerEulerAncestral": { + "display_name": "SamplerEulerAncestral", + "inputs": { + "eta": { + "name": "eta" + }, + "s_noise": { + "name": "s_noise" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerEulerAncestralCFGPP": { + "display_name": "SamplerEulerAncestralCFG++", + "inputs": { + "eta": { + "name": "eta" + }, + "s_noise": { + "name": "s_noise" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerEulerCFGpp": { + "display_name": "SamplerEulerCFG++", + "inputs": { + "version": { + "name": "versão" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerLCMUpscale": { + "display_name": "SamplerLCMUpscale", + "inputs": { + "scale_ratio": { + "name": "proporção_de_escala" + }, + "scale_steps": { + "name": "etapas_de_escala" + }, + "upscale_method": { + "name": "método_de_upscale" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerLMS": { + "display_name": "SamplerLMS", + "inputs": { + "order": { + "name": "ordem" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerSASolver": { + "display_name": "SamplerSASolver", + "inputs": { + "corrector_order": { + "name": "ordem_do_corretor" + }, + "eta": { + "name": "eta" + }, + "model": { + "name": "modelo" + }, + "predictor_order": { + "name": "ordem_do_preditor" + }, + "s_noise": { + "name": "s_noise" + }, + "sde_end_percent": { + "name": "percentual_final_sde" + }, + "sde_start_percent": { + "name": "percentual_inicial_sde" + }, + "simple_order_2": { + "name": "ordem_simples_2" + }, + "use_pece": { + "name": "usar_pece" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerSEEDS2": { + "description": "Este nó de amostragem pode representar vários amostradores:\n\nseeds_2\n- configuração padrão\n\nexp_heun_2_x0\n- solver_type=phi_2, r=1.0, eta=0.0\n\nexp_heun_2_x0_sde\n- solver_type=phi_2, r=1.0, eta=1.0, s_noise=1.0", + "display_name": "SamplerSEEDS2", + "inputs": { + "eta": { + "name": "eta", + "tooltip": "Intensidade estocástica" + }, + "r": { + "name": "r", + "tooltip": "Tamanho relativo do passo para o estágio intermediário (nó c2)" + }, + "s_noise": { + "name": "s_noise", + "tooltip": "Multiplicador de ruído SDE" + }, + "solver_type": { + "name": "solver_type" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplingPercentToSigma": { + "display_name": "SamplingPercentToSigma", + "inputs": { + "model": { + "name": "model" + }, + "return_actual_sigma": { + "name": "return_actual_sigma", + "tooltip": "Retorna o valor sigma real em vez do valor usado para verificações de intervalo.\nIsso só afeta resultados em 0.0 e 1.0." + }, + "sampling_percent": { + "name": "sampling_percent" + } + }, + "outputs": { + "0": { + "name": "sigma_value", + "tooltip": null + } + } + }, + "SaveAnimatedPNG": { + "display_name": "Salvar PNG Animado", + "inputs": { + "compress_level": { + "name": "nível_de_compressão" + }, + "filename_prefix": { + "name": "prefixo_do_arquivo" + }, + "fps": { + "name": "fps" + }, + "images": { + "name": "imagens" + } + } + }, + "SaveAnimatedWEBP": { + "display_name": "Salvar WEBP Animado", + "inputs": { + "filename_prefix": { + "name": "prefixo_do_arquivo" + }, + "fps": { + "name": "fps" + }, + "images": { + "name": "imagens" + }, + "lossless": { + "name": "sem_perda" + }, + "method": { + "name": "método" + }, + "quality": { + "name": "qualidade" + } + } + }, + "SaveAudio": { + "display_name": "Salvar Áudio (FLAC)", + "inputs": { + "audio": { + "name": "áudio" + }, + "audioUI": { + "name": "audioUI" + }, + "filename_prefix": { + "name": "prefixo_do_arquivo" + } + } + }, + "SaveAudioMP3": { + "display_name": "Salvar Áudio (MP3)", + "inputs": { + "audio": { + "name": "áudio" + }, + "audioUI": { + "name": "audioUI" + }, + "filename_prefix": { + "name": "prefixo_do_arquivo" + }, + "quality": { + "name": "qualidade" + } + } + }, + "SaveAudioOpus": { + "display_name": "Salvar Áudio (Opus)", + "inputs": { + "audio": { + "name": "áudio" + }, + "audioUI": { + "name": "audioUI" + }, + "filename_prefix": { + "name": "prefixo_do_arquivo" + }, + "quality": { + "name": "qualidade" + } + } + }, + "SaveGLB": { + "display_name": "Salvar GLB", + "inputs": { + "filename_prefix": { + "name": "prefixo_do_arquivo" + }, + "image": { + "name": "imagem" + }, + "mesh": { + "name": "malha" + } + } + }, + "SaveImage": { + "description": "Salva as imagens de entrada no diretório de saída do seu ComfyUI.", + "display_name": "Salvar Imagem", + "inputs": { + "filename_prefix": { + "name": "prefixo_do_arquivo", + "tooltip": "O prefixo para o arquivo a ser salvo. Isso pode incluir informações de formatação como %date:yyyy-MM-dd% ou %Empty Latent Image.width% para incluir valores de nós." + }, + "images": { + "name": "imagens", + "tooltip": "As imagens a serem salvas." + } + } + }, + "SaveImageDataSetToFolder": { + "display_name": "Salvar Conjunto de Imagens na Pasta", + "inputs": { + "filename_prefix": { + "name": "prefixo_do_arquivo", + "tooltip": "Prefixo para os nomes dos arquivos de imagem salvos." + }, + "folder_name": { + "name": "nome_da_pasta", + "tooltip": "Nome da pasta para salvar as imagens (dentro do diretório de saída)." + }, + "images": { + "name": "imagens", + "tooltip": "Lista de imagens a serem salvas." + } + } + }, + "SaveImageTextDataSetToFolder": { + "display_name": "Salvar Conjunto de Imagens e Textos na Pasta", + "inputs": { + "filename_prefix": { + "name": "prefixo_do_arquivo", + "tooltip": "Prefixo para os nomes dos arquivos de imagem salvos." + }, + "folder_name": { + "name": "nome_da_pasta", + "tooltip": "Nome da pasta para salvar as imagens (dentro do diretório de saída)." + }, + "images": { + "name": "imagens", + "tooltip": "Lista de imagens a serem salvas." + }, + "texts": { + "name": "textos", + "tooltip": "Lista de legendas de texto a serem salvas." + } + } + }, + "SaveImageWebsocket": { + "display_name": "SalvarImagemWebsocket", + "inputs": { + "images": { + "name": "imagens" + } + } + }, + "SaveLatent": { + "display_name": "SalvarLatent", + "inputs": { + "filename_prefix": { + "name": "prefixo_do_arquivo" + }, + "samples": { + "name": "amostras" + } + } + }, + "SaveLoRA": { + "display_name": "Salvar Pesos LoRA", + "inputs": { + "lora": { + "name": "lora", + "tooltip": "O modelo LoRA a ser salvo. Não use o modelo com camadas LoRA." + }, + "prefix": { + "name": "prefixo", + "tooltip": "O prefixo a ser usado para o arquivo LoRA salvo." + }, + "steps": { + "name": "etapas", + "tooltip": "Opcional: O número de etapas para o qual o LoRA foi treinado, usado para nomear o arquivo salvo." + } + } + }, + "SaveSVGNode": { + "description": "Salva arquivos SVG no disco.", + "display_name": "Salvar Nó SVG", + "inputs": { + "filename_prefix": { + "name": "prefixo_do_arquivo", + "tooltip": "O prefixo para o arquivo a ser salvo. Isso pode incluir informações de formatação como %date:yyyy-MM-dd% ou %Empty Latent Image.width% para incluir valores de nós." + }, + "svg": { + "name": "svg" + } + } + }, + "SaveTrainingDataset": { + "display_name": "Salvar Conjunto de Dados de Treinamento", + "inputs": { + "conditioning": { + "name": "condicionamento", + "tooltip": "Lista de listas de condicionamento do MakeTrainingDataset." + }, + "folder_name": { + "name": "nome_da_pasta", + "tooltip": "Nome da pasta para salvar o conjunto de dados (dentro do diretório de saída)." + }, + "latents": { + "name": "latents", + "tooltip": "Lista de dicionários de latent do MakeTrainingDataset." + }, + "shard_size": { + "name": "tamanho_do_fragmento", + "tooltip": "Número de amostras por arquivo de fragmento." + } + } + }, + "SaveVideo": { + "description": "Salva as imagens de entrada no diretório de saída do seu ComfyUI.", + "display_name": "Salvar Vídeo", + "inputs": { + "codec": { + "name": "codec", + "tooltip": "O codec a ser usado para o vídeo." + }, + "filename_prefix": { + "name": "prefixo_do_arquivo", + "tooltip": "O prefixo para o arquivo a ser salvo. Isso pode incluir informações de formatação como %date:yyyy-MM-dd% ou %Empty Latent Image.width% para incluir valores de nós." + }, + "format": { + "name": "formato", + "tooltip": "O formato para salvar o vídeo." + }, + "video": { + "name": "vídeo", + "tooltip": "O vídeo a ser salvo." + } + } + }, + "SaveWEBM": { + "display_name": "Salvar WEBM", + "inputs": { + "codec": { + "name": "codec" + }, + "crf": { + "name": "crf", + "tooltip": "CRF mais alto significa menor qualidade e menor tamanho de arquivo, CRF mais baixo significa maior qualidade e maior tamanho de arquivo." + }, + "filename_prefix": { + "name": "prefixo_do_arquivo" + }, + "fps": { + "name": "fps" + }, + "images": { + "name": "imagens" + } + } + }, + "ScaleROPE": { + "description": "Escala e desloca o ROPE do modelo.", + "display_name": "Escalar ROPE", + "inputs": { + "model": { + "name": "modelo" + }, + "scale_t": { + "name": "escala_t" + }, + "scale_x": { + "name": "escala_x" + }, + "scale_y": { + "name": "escala_y" + }, + "shift_t": { + "name": "deslocamento_t" + }, + "shift_x": { + "name": "deslocamento_x" + }, + "shift_y": { + "name": "deslocamento_y" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SelfAttentionGuidance": { + "display_name": "Orientação por Self-Attention", + "inputs": { + "blur_sigma": { + "name": "sigma_de_desfoque" + }, + "model": { + "name": "modelo" + }, + "scale": { + "name": "escala" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SetClipHooks": { + "display_name": "Definir Hooks do CLIP", + "inputs": { + "apply_to_conds": { + "name": "aplicar_a_conds" + }, + "clip": { + "name": "clip" + }, + "hooks": { + "name": "hooks" + }, + "schedule_clip": { + "name": "agendar_clip" + } + } + }, + "SetFirstSigma": { + "display_name": "DefinirPrimeiroSigma", + "inputs": { + "sigma": { + "name": "sigma" + }, + "sigmas": { + "name": "sigmas" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SetHookKeyframes": { + "display_name": "Definir Keyframes de Hook", + "inputs": { + "hook_kf": { + "name": "hook_kf" + }, + "hooks": { + "name": "hooks" + } + } + }, + "SetLatentNoiseMask": { + "display_name": "Definir Máscara de Ruído Latent", + "inputs": { + "mask": { + "name": "mask" + }, + "samples": { + "name": "amostras" + } + } + }, + "SetUnionControlNetType": { + "display_name": "DefinirTipoUnionControlNet", + "inputs": { + "control_net": { + "name": "control_net" + }, + "type": { + "name": "tipo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ShuffleDataset": { + "display_name": "Embaralhar Conjunto de Imagens", + "inputs": { + "control_after_generate": { + "name": "controlar após gerar" + }, + "images": { + "name": "imagens", + "tooltip": "Lista de imagens para processar." + }, + "seed": { + "name": "semente", + "tooltip": "Semente aleatória." + } + }, + "outputs": { + "0": { + "name": "imagens", + "tooltip": "Imagens processadas" + } + } + }, + "ShuffleImageTextDataset": { + "display_name": "Embaralhar Conjunto Imagem-Texto", + "inputs": { + "control_after_generate": { + "name": "controlar após gerar" + }, + "images": { + "name": "imagens", + "tooltip": "Lista de imagens para embaralhar." + }, + "seed": { + "name": "semente", + "tooltip": "Semente aleatória." + }, + "texts": { + "name": "textos", + "tooltip": "Lista de textos para embaralhar." + } + }, + "outputs": { + "0": { + "name": "imagens", + "tooltip": "Imagens embaralhadas" + }, + "1": { + "name": "textos", + "tooltip": "Textos embaralhados" + } + } + }, + "SkipLayerGuidanceDiT": { + "description": "Versão genérica do nó SkipLayerGuidance que pode ser usada em qualquer modelo DiT.", + "display_name": "SkipLayerGuidanceDiT", + "inputs": { + "double_layers": { + "name": "camadas duplas" + }, + "end_percent": { + "name": "percentual final" + }, + "model": { + "name": "modelo" + }, + "rescaling_scale": { + "name": "escala de reescalonamento" + }, + "scale": { + "name": "escala" + }, + "single_layers": { + "name": "camadas simples" + }, + "start_percent": { + "name": "percentual inicial" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SkipLayerGuidanceDiTSimple": { + "description": "Versão simples do nó SkipLayerGuidanceDiT que apenas modifica a passagem uncond.", + "display_name": "SkipLayerGuidanceDiTSimple", + "inputs": { + "double_layers": { + "name": "camadas duplas" + }, + "end_percent": { + "name": "percentual final" + }, + "model": { + "name": "modelo" + }, + "single_layers": { + "name": "camadas simples" + }, + "start_percent": { + "name": "percentual inicial" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SkipLayerGuidanceSD3": { + "description": "Versão genérica do nó SkipLayerGuidance que pode ser usada em qualquer modelo DiT.", + "display_name": "SkipLayerGuidanceSD3", + "inputs": { + "end_percent": { + "name": "percentual_final" + }, + "layers": { + "name": "camadas" + }, + "model": { + "name": "modelo" + }, + "scale": { + "name": "escala" + }, + "start_percent": { + "name": "percentual_inicial" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SolidMask": { + "display_name": "SolidMask", + "inputs": { + "height": { + "name": "altura" + }, + "value": { + "name": "valor" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SplitAudioChannels": { + "description": "Separa o áudio em canais esquerdo e direito.", + "display_name": "Dividir Canais de Áudio", + "inputs": { + "audio": { + "name": "áudio" + } + }, + "outputs": { + "0": { + "name": "esquerdo", + "tooltip": null + }, + "1": { + "name": "direito", + "tooltip": null + } + } + }, + "SplitImageWithAlpha": { + "display_name": "Dividir Imagem com Alpha", + "inputs": { + "image": { + "name": "imagem" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "tooltip": null + } + } + }, + "SplitSigmas": { + "display_name": "SplitSigmas", + "inputs": { + "sigmas": { + "name": "sigmas" + }, + "step": { + "name": "passo" + } + }, + "outputs": { + "0": { + "name": "sigmas_altos", + "tooltip": null + }, + "1": { + "name": "sigmas_baixos", + "tooltip": null + } + } + }, + "SplitSigmasDenoise": { + "display_name": "SplitSigmasDenoise", + "inputs": { + "denoise": { + "name": "redução_de_ruído" + }, + "sigmas": { + "name": "sigmas" + } + }, + "outputs": { + "0": { + "name": "sigmas_altos", + "tooltip": null + }, + "1": { + "name": "sigmas_baixos", + "tooltip": null + } + } + }, + "StabilityAudioInpaint": { + "description": "Transforma parte de uma amostra de áudio existente usando instruções de texto.", + "display_name": "Stability AI Audio Inpaint", + "inputs": { + "audio": { + "name": "áudio", + "tooltip": "O áudio deve ter entre 6 e 190 segundos de duração." + }, + "control_after_generate": { + "name": "controle_após_gerar" + }, + "duration": { + "name": "duração", + "tooltip": "Controla a duração em segundos do áudio gerado." + }, + "mask_end": { + "name": "fim_da_mascara" + }, + "mask_start": { + "name": "início_da_mascara" + }, + "model": { + "name": "modelo" + }, + "prompt": { + "name": "prompt" + }, + "seed": { + "name": "semente", + "tooltip": "A semente aleatória usada para a geração." + }, + "steps": { + "name": "passos", + "tooltip": "Controla o número de etapas de amostragem." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StabilityAudioToAudio": { + "description": "Transforma amostras de áudio existentes em novas composições de alta qualidade usando instruções de texto.", + "display_name": "Stability AI Áudio para Áudio", + "inputs": { + "audio": { + "name": "áudio", + "tooltip": "O áudio deve ter entre 6 e 190 segundos de duração." + }, + "control_after_generate": { + "name": "control after generate" + }, + "duration": { + "name": "duração", + "tooltip": "Controla a duração em segundos do áudio gerado." + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt" + }, + "seed": { + "name": "semente", + "tooltip": "A semente aleatória usada para a geração." + }, + "steps": { + "name": "etapas", + "tooltip": "Controla o número de etapas de amostragem." + }, + "strength": { + "name": "força", + "tooltip": "Parâmetro que controla o quanto o parâmetro de áudio influencia o áudio gerado." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StabilityStableImageSD_3_5Node": { + "description": "Gera imagens de forma síncrona com base no prompt e na resolução.", + "display_name": "Stability AI Stable Diffusion 3.5 Imagem", + "inputs": { + "aspect_ratio": { + "name": "proporção", + "tooltip": "Proporção da imagem gerada." + }, + "cfg_scale": { + "name": "cfg_scale", + "tooltip": "Quão estritamente o processo de difusão segue o texto do prompt (valores mais altos mantêm sua imagem mais próxima do prompt)" + }, + "control_after_generate": { + "name": "control after generate" + }, + "image": { + "name": "imagem" + }, + "image_denoise": { + "name": "redução de ruído da imagem", + "tooltip": "Redução de ruído da imagem de entrada; 0.0 gera uma imagem idêntica à entrada, 1.0 é como se nenhuma imagem tivesse sido fornecida." + }, + "model": { + "name": "model" + }, + "negative_prompt": { + "name": "prompt negativo", + "tooltip": "Palavras-chave do que você não deseja ver na imagem de saída. Este é um recurso avançado." + }, + "prompt": { + "name": "prompt", + "tooltip": "O que você deseja ver na imagem de saída. Um prompt forte e descritivo que define claramente elementos, cores e temas levará a melhores resultados." + }, + "seed": { + "name": "semente", + "tooltip": "A semente aleatória usada para criar o ruído." + }, + "style_preset": { + "name": "estilo predefinido", + "tooltip": "Estilo desejado opcional da imagem gerada." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StabilityStableImageUltraNode": { + "description": "Gera imagens de forma síncrona com base no prompt e na resolução.", + "display_name": "Stability AI Stable Image Ultra", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "Proporção da imagem gerada." + }, + "control_after_generate": { + "name": "control after generate" + }, + "image": { + "name": "image" + }, + "image_denoise": { + "name": "image_denoise", + "tooltip": "Denoise da imagem de entrada; 0.0 gera uma imagem idêntica à entrada, 1.0 é como se nenhuma imagem tivesse sido fornecida." + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Um texto descrevendo o que você NÃO deseja ver na imagem de saída. Este é um recurso avançado." + }, + "prompt": { + "name": "prompt", + "tooltip": "O que você deseja ver na imagem de saída. Um prompt forte e descritivo que define claramente elementos, cores e assuntos levará a melhores resultados. Para controlar o peso de uma determinada palavra, use o formato `(palavra:peso)`, onde `palavra` é a palavra que você deseja controlar o peso e `peso` é um valor entre 0 e 1. Por exemplo: `O céu estava de um (azul:0.3) e (verde:0.8) intenso` indicaria um céu azul e verde, mas mais verde do que azul." + }, + "seed": { + "name": "seed", + "tooltip": "A semente aleatória usada para criar o ruído." + }, + "style_preset": { + "name": "style_preset", + "tooltip": "Estilo opcional desejado para a imagem gerada." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StabilityTextToAudio": { + "description": "Gera músicas e efeitos sonoros de alta qualidade a partir de descrições em texto.", + "display_name": "Stability AI Text To Audio", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "duration": { + "name": "duration", + "tooltip": "Controla a duração em segundos do áudio gerado." + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt" + }, + "seed": { + "name": "seed", + "tooltip": "A semente aleatória usada para a geração." + }, + "steps": { + "name": "steps", + "tooltip": "Controla o número de etapas de amostragem." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StabilityUpscaleConservativeNode": { + "description": "Aumenta a resolução da imagem para 4K com alterações mínimas.", + "display_name": "Stability AI Upscale Conservative", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "creativity": { + "name": "creativity", + "tooltip": "Controla a probabilidade de criar detalhes adicionais não fortemente condicionados pela imagem inicial." + }, + "image": { + "name": "image" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Palavras-chave do que você NÃO deseja ver na imagem de saída. Este é um recurso avançado." + }, + "prompt": { + "name": "prompt", + "tooltip": "O que você deseja ver na imagem de saída. Um prompt forte e descritivo que define claramente elementos, cores e assuntos levará a melhores resultados." + }, + "seed": { + "name": "seed", + "tooltip": "A semente aleatória usada para criar o ruído." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StabilityUpscaleCreativeNode": { + "description": "Aumente a resolução da imagem para 4K com alterações mínimas.", + "display_name": "Stability AI Upscale Criativo", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "creativity": { + "name": "criatividade", + "tooltip": "Controla a probabilidade de criar detalhes adicionais não fortemente condicionados pela imagem inicial." + }, + "image": { + "name": "imagem" + }, + "negative_prompt": { + "name": "prompt_negativo", + "tooltip": "Palavras-chave do que você NÃO deseja ver na imagem de saída. Este é um recurso avançado." + }, + "prompt": { + "name": "prompt", + "tooltip": "O que você deseja ver na imagem de saída. Um prompt forte e descritivo, que define claramente elementos, cores e assuntos, levará a melhores resultados." + }, + "seed": { + "name": "semente", + "tooltip": "A semente aleatória usada para criar o ruído." + }, + "style_preset": { + "name": "estilo_predefinido", + "tooltip": "Estilo desejado opcional para a imagem gerada." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StabilityUpscaleFastNode": { + "description": "Aumenta rapidamente a imagem para 4x o tamanho original via chamada à API da Stability; indicado para imagens de baixa qualidade ou comprimidas.", + "display_name": "Stability AI Upscale Rápido", + "inputs": { + "image": { + "name": "imagem" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StableCascade_EmptyLatentImage": { + "display_name": "StableCascade_EmptyLatentImage", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "compression": { + "name": "compressão" + }, + "height": { + "name": "altura" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "stage_c", + "tooltip": null + }, + "1": { + "name": "stage_b", + "tooltip": null + } + } + }, + "StableCascade_StageB_Conditioning": { + "display_name": "StableCascade_StageB_Conditioning", + "inputs": { + "conditioning": { + "name": "condicionamento" + }, + "stage_c": { + "name": "stage_c" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StableCascade_StageC_VAEEncode": { + "display_name": "StableCascade_StageC_VAEEncode", + "inputs": { + "compression": { + "name": "compressão" + }, + "image": { + "name": "imagem" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "stage_c", + "tooltip": null + }, + "1": { + "name": "stage_b", + "tooltip": null + } + } + }, + "StableCascade_SuperResolutionControlnet": { + "display_name": "StableCascade_SuperResolutionControlnet", + "inputs": { + "image": { + "name": "imagem" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "controlnet_input", + "tooltip": null + }, + "1": { + "name": "stage_c", + "tooltip": null + }, + "2": { + "name": "stage_b", + "tooltip": null + } + } + }, + "StableZero123_Conditioning": { + "display_name": "StableZero123_Conditioning", + "inputs": { + "azimuth": { + "name": "azimute" + }, + "batch_size": { + "name": "tamanho_do_lote" + }, + "clip_vision": { + "name": "clip_vision" + }, + "elevation": { + "name": "elevação" + }, + "height": { + "name": "altura" + }, + "init_image": { + "name": "imagem_inicial" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latente", + "tooltip": null + } + } + }, + "StableZero123_Conditioning_Batched": { + "display_name": "StableZero123_Conditioning_Batched", + "inputs": { + "azimuth": { + "name": "azimute" + }, + "azimuth_batch_increment": { + "name": "incremento_de_lote_de_azimute" + }, + "batch_size": { + "name": "tamanho_do_lote" + }, + "clip_vision": { + "name": "clip_vision" + }, + "elevation": { + "name": "elevação" + }, + "elevation_batch_increment": { + "name": "incremento_de_lote_de_elevacao" + }, + "height": { + "name": "altura" + }, + "init_image": { + "name": "imagem_inicial" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latente", + "tooltip": null + } + } + }, + "StringCompare": { + "display_name": "Comparar", + "inputs": { + "case_sensitive": { + "name": "diferenciar_maiusculas_minusculas" + }, + "mode": { + "name": "modo" + }, + "string_a": { + "name": "string_a" + }, + "string_b": { + "name": "string_b" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StringConcatenate": { + "display_name": "Concatenar", + "inputs": { + "delimiter": { + "name": "delimitador" + }, + "string_a": { + "name": "string_a" + }, + "string_b": { + "name": "string_b" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StringContains": { + "display_name": "Contém", + "inputs": { + "case_sensitive": { + "name": "diferenciar_maiusculas_minusculas" + }, + "string": { + "name": "string" + }, + "substring": { + "name": "substring" + } + }, + "outputs": { + "0": { + "name": "contém", + "tooltip": null + } + } + }, + "StringLength": { + "display_name": "Comprimento", + "inputs": { + "string": { + "name": "string" + } + }, + "outputs": { + "0": { + "name": "comprimento", + "tooltip": null + } + } + }, + "StringReplace": { + "display_name": "Substituir", + "inputs": { + "find": { + "name": "encontrar" + }, + "replace": { + "name": "substituir" + }, + "string": { + "name": "string" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StringSubstring": { + "display_name": "Subsequência", + "inputs": { + "end": { + "name": "fim" + }, + "start": { + "name": "início" + }, + "string": { + "name": "string" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StringTrim": { + "display_name": "Remover Espaços", + "inputs": { + "mode": { + "name": "modo" + }, + "string": { + "name": "string" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "StripWhitespace": { + "display_name": "Remover Espaços em Branco", + "inputs": { + "texts": { + "name": "textos", + "tooltip": "Texto para processar." + } + }, + "outputs": { + "0": { + "name": "textos", + "tooltip": "Textos processados" + } + } + }, + "StyleModelApply": { + "display_name": "Aplicar Modelo de Estilo", + "inputs": { + "clip_vision_output": { + "name": "clip_vision_output" + }, + "conditioning": { + "name": "condicionamento" + }, + "strength": { + "name": "força" + }, + "strength_type": { + "name": "tipo_de_força" + }, + "style_model": { + "name": "style_model" + } + } + }, + "StyleModelLoader": { + "display_name": "Carregar Modelo de Estilo", + "inputs": { + "style_model_name": { + "name": "style_model_name" + } + } + }, + "T5TokenizerOptions": { + "display_name": "Opções do T5Tokenizer", + "inputs": { + "clip": { + "name": "clip" + }, + "min_length": { + "name": "comprimento_mínimo" + }, + "min_padding": { + "name": "preenchimento_mínimo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TCFG": { + "description": "TCFG – Tangential Damping CFG (2503.18137)\n\nRefina o uncond (negativo) para alinhar com o cond (positivo) e melhorar a qualidade.", + "display_name": "Tangential Damping CFG", + "inputs": { + "model": { + "name": "model" + } + }, + "outputs": { + "0": { + "name": "patched_model", + "tooltip": null + } + } + }, + "TemporalScoreRescaling": { + "description": "[Função Pós-CFG]\nTSR - Redimensionamento Temporal de Pontuação (2510.01184)\n\nRedimensiona a pontuação ou o ruído do modelo para direcionar a diversidade da amostragem.\n", + "display_name": "TSR - Redimensionamento Temporal de Pontuação", + "inputs": { + "model": { + "name": "model" + }, + "tsr_k": { + "name": "tsr_k", + "tooltip": "Controla a intensidade do redimensionamento.\nValores menores de k produzem resultados mais detalhados; valores maiores de k produzem resultados mais suaves na geração de imagens. Definir k = 1 desativa o redimensionamento." + }, + "tsr_sigma": { + "name": "tsr_sigma", + "tooltip": "Controla quão cedo o redimensionamento entra em efeito.\nValores maiores fazem efeito mais cedo." + } + }, + "outputs": { + "0": { + "name": "patched_model", + "tooltip": null + } + } + }, + "TencentImageToModelNode": { + "display_name": "Hunyuan3D: Imagem(ns) para Modelo (Pro)", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "face_count": { + "name": "número_de_faces" + }, + "generate_type": { + "name": "tipo_de_geração" + }, + "generate_type_pbr": { + "name": "pbr" + }, + "image": { + "name": "imagem" + }, + "image_back": { + "name": "imagem_traseira" + }, + "image_left": { + "name": "imagem_esquerda" + }, + "image_right": { + "name": "imagem_direita" + }, + "model": { + "name": "modelo", + "tooltip": "A opção LowPoly não está disponível para o modelo `3.1`." + }, + "seed": { + "name": "semente", + "tooltip": "A semente controla se o nó deve ser executado novamente; os resultados são não determinísticos independentemente da semente." + } + }, + "outputs": { + "0": { + "name": "arquivo_modelo", + "tooltip": null + } + } + }, + "TencentTextToModelNode": { + "display_name": "Hunyuan3D: Texto para Modelo (Pro)", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "face_count": { + "name": "número_de_faces" + }, + "generate_type": { + "name": "tipo_de_geração" + }, + "generate_type_pbr": { + "name": "pbr" + }, + "model": { + "name": "modelo", + "tooltip": "A opção LowPoly não está disponível para o modelo `3.1`." + }, + "prompt": { + "name": "prompt", + "tooltip": "Suporta até 1024 caracteres." + }, + "seed": { + "name": "semente", + "tooltip": "A semente controla se o nó deve ser executado novamente; os resultados são não determinísticos independentemente da semente." + } + }, + "outputs": { + "0": { + "name": "arquivo_modelo", + "tooltip": null + } + } + }, + "TextEncodeAceStepAudio": { + "display_name": "TextEncodeAceStepAudio", + "inputs": { + "clip": { + "name": "clip" + }, + "lyrics": { + "name": "lyrics" + }, + "lyrics_strength": { + "name": "lyrics_strength" + }, + "tags": { + "name": "tags" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TextEncodeHunyuanVideo_ImageToVideo": { + "display_name": "TextEncodeHunyuanVideo_ImageToVideo", + "inputs": { + "clip": { + "name": "clip" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "image_interleave": { + "name": "image_interleave", + "tooltip": "Quanto a imagem influencia em relação ao prompt de texto. Um valor mais alto significa mais influência do prompt de texto." + }, + "prompt": { + "name": "prompt" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TextEncodeQwenImageEdit": { + "display_name": "TextEncodeQwenImageEdit", + "inputs": { + "clip": { + "name": "clip" + }, + "image": { + "name": "image" + }, + "prompt": { + "name": "prompt" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TextEncodeQwenImageEditPlus": { + "display_name": "TextEncodeQwenImageEditPlus", + "inputs": { + "clip": { + "name": "clip" + }, + "image1": { + "name": "image1" + }, + "image2": { + "name": "image2" + }, + "image3": { + "name": "image3" + }, + "prompt": { + "name": "prompt" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TextEncodeZImageOmni": { + "display_name": "TextEncodeZImageOmni", + "inputs": { + "auto_resize_images": { + "name": "redimensionar_imagens_automaticamente" + }, + "clip": { + "name": "clip" + }, + "image1": { + "name": "imagem1" + }, + "image2": { + "name": "imagem2" + }, + "image3": { + "name": "imagem3" + }, + "image_encoder": { + "name": "codificador_de_imagem" + }, + "prompt": { + "name": "prompt" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TextToLowercase": { + "display_name": "Texto para minúsculas", + "inputs": { + "texts": { + "name": "texts", + "tooltip": "Texto para processar." + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "Textos processados" + } + } + }, + "TextToUppercase": { + "display_name": "Texto para Maiúsculas", + "inputs": { + "texts": { + "name": "textos", + "tooltip": "Texto a ser processado." + } + }, + "outputs": { + "0": { + "name": "textos", + "tooltip": "Textos processados" + } + } + }, + "ThresholdMask": { + "display_name": "ThresholdMask", + "inputs": { + "mask": { + "name": "mask" + }, + "value": { + "name": "valor" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TomePatchModel": { + "display_name": "TomePatchModel", + "inputs": { + "model": { + "name": "modelo" + }, + "ratio": { + "name": "proporção" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TopazImageEnhance": { + "description": "Aprimoramento e upscaling de imagem padrão da indústria.", + "display_name": "Topaz Image Enhance", + "inputs": { + "color_preservation": { + "name": "preservação_de_cores", + "tooltip": "Preservar as cores originais." + }, + "creativity": { + "name": "criatividade" + }, + "crop_to_fill": { + "name": "cortar_para_preencher", + "tooltip": "Por padrão, a imagem é exibida com faixas quando a proporção de aspecto de saída é diferente. Ative para cortar a imagem e preencher as dimensões de saída." + }, + "face_enhancement": { + "name": "aprimoramento_de_rostos", + "tooltip": "Aprimore rostos (se presentes) durante o processamento." + }, + "face_enhancement_creativity": { + "name": "criatividade_no_aprimoramento_de_rostos", + "tooltip": "Defina o nível de criatividade para o aprimoramento de rostos." + }, + "face_enhancement_strength": { + "name": "força_do_aprimoramento_de_rostos", + "tooltip": "Controla o quão nítidos os rostos aprimorados ficam em relação ao fundo." + }, + "face_preservation": { + "name": "preservação_de_rostos", + "tooltip": "Preservar a identidade facial dos sujeitos." + }, + "image": { + "name": "imagem" + }, + "model": { + "name": "modelo" + }, + "output_height": { + "name": "altura_de_saida", + "tooltip": "Valor zero significa manter a mesma altura da original ou da largura de saída." + }, + "output_width": { + "name": "largura_de_saida", + "tooltip": "Valor zero significa calcular automaticamente (normalmente será o tamanho original ou a altura de saída, se especificada)." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt de texto opcional para orientação criativa no upscaling." + }, + "subject_detection": { + "name": "detecção_de_sujeito" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TopazVideoEnhance": { + "description": "Dê nova vida aos seus vídeos com tecnologia avançada de upscaling e recuperação.", + "display_name": "Topaz Video Enhance", + "inputs": { + "dynamic_compression_level": { + "name": "dynamic_compression_level", + "tooltip": "Nível CQP." + }, + "interpolation_duplicate": { + "name": "interpolation_duplicate", + "tooltip": "Analisa o vídeo de entrada para quadros duplicados e os remove." + }, + "interpolation_duplicate_threshold": { + "name": "interpolation_duplicate_threshold", + "tooltip": "Sensibilidade de detecção para quadros duplicados." + }, + "interpolation_enabled": { + "name": "interpolation_enabled" + }, + "interpolation_frame_rate": { + "name": "interpolation_frame_rate", + "tooltip": "Taxa de quadros de saída." + }, + "interpolation_model": { + "name": "interpolation_model" + }, + "interpolation_slowmo": { + "name": "interpolation_slowmo", + "tooltip": "Fator de câmera lenta aplicado ao vídeo de entrada. Por exemplo, 2 deixa o resultado duas vezes mais lento e dobra a duração." + }, + "upscaler_creativity": { + "name": "upscaler_creativity", + "tooltip": "Nível de criatividade (aplicável apenas ao Starlight (Astra) Creative)." + }, + "upscaler_enabled": { + "name": "upscaler_enabled" + }, + "upscaler_model": { + "name": "upscaler_model" + }, + "upscaler_resolution": { + "name": "upscaler_resolution" + }, + "video": { + "name": "vídeo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TorchCompileModel": { + "display_name": "TorchCompileModel", + "inputs": { + "backend": { + "name": "backend" + }, + "model": { + "name": "modelo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TrainLoraNode": { + "display_name": "Treinar LoRA", + "inputs": { + "algorithm": { + "name": "algoritmo", + "tooltip": "O algoritmo a ser usado para o treinamento." + }, + "batch_size": { + "name": "tamanho_do_lote", + "tooltip": "O tamanho do lote a ser usado para o treinamento." + }, + "bucket_mode": { + "name": "modo_bucket", + "tooltip": "Ativar modo de bucket de resolução. Quando ativado, espera latents pré-bucketados do nó ResolutionBucket." + }, + "control_after_generate": { + "name": "controlar após gerar" + }, + "existing_lora": { + "name": "lora_existente", + "tooltip": "A LoRA existente para anexar. Defina como Nenhum para nova LoRA." + }, + "grad_accumulation_steps": { + "name": "passos_de_acumulo_de_gradiente", + "tooltip": "O número de passos de acumulação de gradiente a serem usados para o treinamento." + }, + "gradient_checkpointing": { + "name": "checkpointing_de_gradiente", + "tooltip": "Usar checkpointing de gradiente para o treinamento." + }, + "latents": { + "name": "latents", + "tooltip": "Os latents a serem usados para o treinamento, servem como conjunto de dados/entrada do modelo." + }, + "learning_rate": { + "name": "taxa_de_aprendizado", + "tooltip": "A taxa de aprendizado a ser usada para o treinamento." + }, + "lora_dtype": { + "name": "lora_dtype", + "tooltip": "O dtype a ser usado para a lora." + }, + "loss_function": { + "name": "função_de_perda", + "tooltip": "A função de perda a ser usada para o treinamento." + }, + "model": { + "name": "modelo", + "tooltip": "O modelo no qual treinar a LoRA." + }, + "optimizer": { + "name": "otimizador", + "tooltip": "O otimizador a ser usado para o treinamento." + }, + "positive": { + "name": "positivo", + "tooltip": "O condicionamento positivo a ser usado para o treinamento." + }, + "rank": { + "name": "rank", + "tooltip": "O rank das camadas da LoRA." + }, + "seed": { + "name": "semente", + "tooltip": "A semente a ser usada para o treinamento (usada no gerador para inicialização dos pesos da LoRA e amostragem de ruído)" + }, + "steps": { + "name": "passos", + "tooltip": "O número de passos para treinar a LoRA." + }, + "training_dtype": { + "name": "dtype_de_treinamento", + "tooltip": "O dtype a ser usado para o treinamento." + } + }, + "outputs": { + "0": { + "name": "modelo", + "tooltip": "Modelo com LoRA aplicada" + }, + "1": { + "name": "lora", + "tooltip": "Pesos da LoRA" + }, + "2": { + "name": "mapa_de_perda", + "tooltip": "Histórico de perda" + }, + "3": { + "name": "passos", + "tooltip": "Total de passos de treinamento" + } + } + }, + "TrimAudioDuration": { + "description": "Corta o tensor de áudio no intervalo de tempo escolhido.", + "display_name": "Cortar Duração do Áudio", + "inputs": { + "audio": { + "name": "áudio" + }, + "duration": { + "name": "duração", + "tooltip": "Duração em segundos" + }, + "start_index": { + "name": "índice_inicial", + "tooltip": "Tempo inicial em segundos, pode ser negativo para contar a partir do final (suporta frações de segundo)." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TrimVideoLatent": { + "display_name": "Cortar Latent de Vídeo", + "inputs": { + "samples": { + "name": "amostras" + }, + "trim_amount": { + "name": "quantidade_de_corte" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TripleCLIPLoader": { + "description": "[Receitas]\n\nsd3: clip-l, clip-g, t5", + "display_name": "TripleCLIPLoader", + "inputs": { + "clip_name1": { + "name": "clip_name1" + }, + "clip_name2": { + "name": "clip_name2" + }, + "clip_name3": { + "name": "clip_name3" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TripoConversionNode": { + "display_name": "Tripo: Converter modelo", + "inputs": { + "animate_in_place": { + "name": "animar_no_local" + }, + "bake": { + "name": "bake" + }, + "export_orientation": { + "name": "exportar_orientação" + }, + "export_vertex_colors": { + "name": "exportar_cores_dos_vértices" + }, + "face_limit": { + "name": "limite_de_faces" + }, + "fbx_preset": { + "name": "preset_fbx" + }, + "flatten_bottom": { + "name": "achatar_base" + }, + "flatten_bottom_threshold": { + "name": "limite_achatamento_base" + }, + "force_symmetry": { + "name": "forçar_simetria" + }, + "format": { + "name": "formato" + }, + "original_model_task_id": { + "name": "original_model_task_id" + }, + "pack_uv": { + "name": "empacotar_uv" + }, + "part_names": { + "name": "nomes_das_partes" + }, + "pivot_to_center_bottom": { + "name": "pivô_para_centro_base" + }, + "quad": { + "name": "quad" + }, + "scale_factor": { + "name": "fator_de_escala" + }, + "texture_format": { + "name": "formato_da_textura" + }, + "texture_size": { + "name": "tamanho_da_textura" + }, + "with_animation": { + "name": "com_animação" + } + } + }, + "TripoImageToModelNode": { + "display_name": "Tripo: Imagem para Modelo", + "inputs": { + "face_limit": { + "name": "limite_de_faces" + }, + "geometry_quality": { + "name": "qualidade_da_geometria" + }, + "image": { + "name": "imagem" + }, + "model_seed": { + "name": "semente_do_modelo" + }, + "model_version": { + "name": "versão_do_modelo", + "tooltip": "A versão do modelo a ser usada para geração" + }, + "orientation": { + "name": "orientação" + }, + "pbr": { + "name": "pbr" + }, + "quad": { + "name": "quad" + }, + "style": { + "name": "estilo" + }, + "texture": { + "name": "textura" + }, + "texture_alignment": { + "name": "alinhamento_da_textura" + }, + "texture_quality": { + "name": "qualidade_da_textura" + }, + "texture_seed": { + "name": "semente_da_textura" + } + }, + "outputs": { + "0": { + "name": "arquivo_do_modelo", + "tooltip": null + }, + "1": { + "name": "task_id do modelo", + "tooltip": null + } + } + }, + "TripoMultiviewToModelNode": { + "display_name": "Tripo: Multiview para Modelo", + "inputs": { + "face_limit": { + "name": "limite_de_faces" + }, + "geometry_quality": { + "name": "qualidade_da_geometria" + }, + "image": { + "name": "imagem" + }, + "image_back": { + "name": "imagem_traseira" + }, + "image_left": { + "name": "imagem_esquerda" + }, + "image_right": { + "name": "imagem_direita" + }, + "model_seed": { + "name": "semente_do_modelo" + }, + "model_version": { + "name": "versão_do_modelo", + "tooltip": "A versão do modelo a ser usada para geração" + }, + "orientation": { + "name": "orientação" + }, + "pbr": { + "name": "pbr" + }, + "quad": { + "name": "quad" + }, + "texture": { + "name": "textura" + }, + "texture_alignment": { + "name": "alinhamento_da_textura" + }, + "texture_quality": { + "name": "qualidade_da_textura" + }, + "texture_seed": { + "name": "semente_da_textura" + } + }, + "outputs": { + "0": { + "name": "arquivo_do_modelo", + "tooltip": null + }, + "1": { + "name": "id_da_tarefa_do_modelo", + "tooltip": null + } + } + }, + "TripoRefineNode": { + "description": "Refine um modelo rascunho criado apenas por modelos Tripo v1.4.", + "display_name": "Tripo: Refinar Modelo Rascunho", + "inputs": { + "model_task_id": { + "name": "id_da_tarefa_do_modelo", + "tooltip": "Deve ser um modelo Tripo v1.4" + } + }, + "outputs": { + "0": { + "name": "arquivo_do_modelo", + "tooltip": null + }, + "1": { + "name": "id_da_tarefa_do_modelo", + "tooltip": null + } + } + }, + "TripoRetargetNode": { + "display_name": "Tripo: Retarget para modelo com rig", + "inputs": { + "animation": { + "name": "animação" + }, + "original_model_task_id": { + "name": "id_da_tarefa_do_modelo_original" + } + }, + "outputs": { + "0": { + "name": "arquivo_do_modelo", + "tooltip": null + }, + "1": { + "name": "id_da_tarefa_de_retarget", + "tooltip": null + } + } + }, + "TripoRigNode": { + "display_name": "Tripo: Rig no modelo", + "inputs": { + "original_model_task_id": { + "name": "id_da_tarefa_do_modelo_original" + } + }, + "outputs": { + "0": { + "name": "arquivo_do_modelo", + "tooltip": null + }, + "1": { + "name": "id_da_tarefa_de_rig", + "tooltip": null + } + } + }, + "TripoTextToModelNode": { + "display_name": "Tripo: Texto para Modelo", + "inputs": { + "face_limit": { + "name": "limite_de_faces" + }, + "geometry_quality": { + "name": "qualidade_da_geometria" + }, + "image_seed": { + "name": "semente_da_imagem" + }, + "model_seed": { + "name": "semente_do_modelo" + }, + "model_version": { + "name": "versão_do_modelo" + }, + "negative_prompt": { + "name": "prompt_negativo" + }, + "pbr": { + "name": "pbr" + }, + "prompt": { + "name": "prompt" + }, + "quad": { + "name": "quad" + }, + "style": { + "name": "estilo" + }, + "texture": { + "name": "textura" + }, + "texture_quality": { + "name": "qualidade_da_textura" + }, + "texture_seed": { + "name": "semente_da_textura" + } + }, + "outputs": { + "0": { + "name": "arquivo_do_modelo", + "tooltip": null + }, + "1": { + "name": "id_da_tarefa_do_modelo", + "tooltip": null + } + } + }, + "TripoTextureNode": { + "display_name": "Tripo: Modelo de Textura", + "inputs": { + "model_task_id": { + "name": "model_task_id" + }, + "pbr": { + "name": "pbr" + }, + "texture": { + "name": "textura" + }, + "texture_alignment": { + "name": "alinhamento_textura" + }, + "texture_quality": { + "name": "qualidade_textura" + }, + "texture_seed": { + "name": "semente_textura" + } + }, + "outputs": { + "0": { + "name": "arquivo_modelo", + "tooltip": null + }, + "1": { + "name": "model task_id", + "tooltip": null + } + } + }, + "TruncateText": { + "display_name": "Truncar Texto", + "inputs": { + "max_length": { + "name": "comprimento_máximo", + "tooltip": "Comprimento máximo do texto." + }, + "texts": { + "name": "textos", + "tooltip": "Texto para processar." + } + }, + "outputs": { + "0": { + "name": "textos", + "tooltip": "Textos processados" + } + } + }, + "UNETLoader": { + "display_name": "Carregar Modelo de Difusão", + "inputs": { + "unet_name": { + "name": "unet_name" + }, + "weight_dtype": { + "name": "tipo_peso" + } + } + }, + "UNetCrossAttentionMultiply": { + "display_name": "UNetCrossAttentionMultiply", + "inputs": { + "k": { + "name": "k" + }, + "model": { + "name": "modelo" + }, + "out": { + "name": "saída" + }, + "q": { + "name": "q" + }, + "v": { + "name": "v" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "UNetSelfAttentionMultiply": { + "display_name": "UNetSelfAttentionMultiply", + "inputs": { + "k": { + "name": "k" + }, + "model": { + "name": "modelo" + }, + "out": { + "name": "saída" + }, + "q": { + "name": "q" + }, + "v": { + "name": "v" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "UNetTemporalAttentionMultiply": { + "display_name": "UNetTemporalAttentionMultiply", + "inputs": { + "cross_structural": { + "name": "cruzado_estrutural" + }, + "cross_temporal": { + "name": "cruzado_temporal" + }, + "model": { + "name": "modelo" + }, + "self_structural": { + "name": "auto_estrutural" + }, + "self_temporal": { + "name": "auto_temporal" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "USOStyleReference": { + "display_name": "USOStyleReference", + "inputs": { + "clip_vision_output": { + "name": "clip_vision_output" + }, + "model": { + "name": "model" + }, + "model_patch": { + "name": "model_patch" + } + } + }, + "UpscaleModelLoader": { + "display_name": "Carregar Modelo de Upscale", + "inputs": { + "model_name": { + "name": "nome_modelo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "VAEDecode": { + "description": "Decodifica imagens latentes de volta para imagens no espaço de pixels.", + "display_name": "VAE Decodificar", + "inputs": { + "samples": { + "name": "amostras", + "tooltip": "O latente a ser decodificado." + }, + "vae": { + "name": "vae", + "tooltip": "O modelo VAE usado para decodificar o latente." + } + }, + "outputs": { + "0": { + "tooltip": "A imagem decodificada." + } + } + }, + "VAEDecodeAudio": { + "display_name": "VAE Decodificar Áudio", + "inputs": { + "samples": { + "name": "amostras" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "VAEDecodeHunyuan3D": { + "display_name": "VAEDecodeHunyuan3D", + "inputs": { + "num_chunks": { + "name": "num_chunks" + }, + "octree_resolution": { + "name": "octree_resolution" + }, + "samples": { + "name": "amostras" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "VAEDecodeTiled": { + "display_name": "VAE Decodificar (Em Blocos)", + "inputs": { + "overlap": { + "name": "sobreposição" + }, + "samples": { + "name": "amostras" + }, + "temporal_overlap": { + "name": "sobreposição_temporal", + "tooltip": "Usado apenas para VAEs de vídeo: Quantidade de quadros para sobrepor." + }, + "temporal_size": { + "name": "tamanho_temporal", + "tooltip": "Usado apenas para VAEs de vídeo: Quantidade de quadros para decodificar por vez." + }, + "tile_size": { + "name": "tamanho_do_bloco" + }, + "vae": { + "name": "vae" + } + } + }, + "VAEEncode": { + "display_name": "VAE Codificar", + "inputs": { + "pixels": { + "name": "pixels" + }, + "vae": { + "name": "vae" + } + } + }, + "VAEEncodeAudio": { + "display_name": "VAE Codificar Áudio", + "inputs": { + "audio": { + "name": "áudio" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "VAEEncodeForInpaint": { + "display_name": "VAE Codificar (para Inpainting)", + "inputs": { + "grow_mask_by": { + "name": "aumentar_máscara_em" + }, + "mask": { + "name": "máscara" + }, + "pixels": { + "name": "pixels" + }, + "vae": { + "name": "vae" + } + } + }, + "VAEEncodeTiled": { + "display_name": "VAE Codificar (Em Blocos)", + "inputs": { + "overlap": { + "name": "sobreposição" + }, + "pixels": { + "name": "pixels" + }, + "temporal_overlap": { + "name": "sobreposição_temporal", + "tooltip": "Usado apenas para VAEs de vídeo: Quantidade de quadros para sobrepor." + }, + "temporal_size": { + "name": "tamanho_temporal", + "tooltip": "Usado apenas para VAEs de vídeo: Quantidade de quadros para codificar por vez." + }, + "tile_size": { + "name": "tamanho_do_bloco" + }, + "vae": { + "name": "vae" + } + } + }, + "VAELoader": { + "display_name": "Carregar VAE", + "inputs": { + "vae_name": { + "name": "vae_name" + } + } + }, + "VAESave": { + "display_name": "Salvar VAE", + "inputs": { + "filename_prefix": { + "name": "filename_prefix" + }, + "vae": { + "name": "vae" + } + } + }, + "VPScheduler": { + "display_name": "VPScheduler", + "inputs": { + "beta_d": { + "name": "beta_d" + }, + "beta_min": { + "name": "beta_min" + }, + "eps_s": { + "name": "eps_s" + }, + "steps": { + "name": "etapas" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Veo3FirstLastFrameNode": { + "description": "Gerar vídeo usando prompt e quadros inicial e final.", + "display_name": "Google Veo 3 Quadro Inicial-Final para Vídeo", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "Proporção do vídeo de saída" + }, + "control_after_generate": { + "name": "control after generate" + }, + "duration": { + "name": "duration", + "tooltip": "Duração do vídeo de saída em segundos" + }, + "first_frame": { + "name": "first_frame", + "tooltip": "Quadro inicial" + }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "Gerar áudio para o vídeo." + }, + "last_frame": { + "name": "last_frame", + "tooltip": "Quadro final" + }, + "model": { + "name": "model" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Prompt negativo para guiar o que evitar no vídeo" + }, + "prompt": { + "name": "prompt", + "tooltip": "Descrição em texto do vídeo" + }, + "resolution": { + "name": "resolution" + }, + "seed": { + "name": "seed", + "tooltip": "Semente para geração do vídeo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Veo3VideoGenerationNode": { + "description": "Gera vídeos a partir de prompts de texto usando a API Google Veo 3", + "display_name": "Google Veo 3 Geração de Vídeo", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "Proporção do vídeo de saída" + }, + "control_after_generate": { + "name": "control after generate" + }, + "duration_seconds": { + "name": "duration_seconds", + "tooltip": "Duração do vídeo de saída em segundos (Veo 3 suporta apenas 8 segundos)" + }, + "enhance_prompt": { + "name": "enhance_prompt", + "tooltip": "Este parâmetro está obsoleto e será ignorado." + }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "Gerar áudio para o vídeo. Suportado por todos os modelos Veo 3." + }, + "image": { + "name": "image", + "tooltip": "Imagem de referência opcional para guiar a geração do vídeo" + }, + "model": { + "name": "model", + "tooltip": "Modelo Veo 3 a ser usado para geração de vídeo" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Prompt negativo para guiar o que evitar no vídeo" + }, + "person_generation": { + "name": "person_generation", + "tooltip": "Permitir ou não a geração de pessoas no vídeo" + }, + "prompt": { + "name": "prompt", + "tooltip": "Descrição em texto do vídeo" + }, + "seed": { + "name": "seed", + "tooltip": "Semente para geração do vídeo (0 para aleatório)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "VeoVideoGenerationNode": { + "description": "Gera vídeos a partir de prompts de texto usando a API Veo 2 do Google", + "display_name": "Geração de Vídeo Google Veo 2", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "Proporção do vídeo de saída" + }, + "control_after_generate": { + "name": "control after generate" + }, + "duration_seconds": { + "name": "duration_seconds", + "tooltip": "Duração do vídeo de saída em segundos" + }, + "enhance_prompt": { + "name": "enhance_prompt", + "tooltip": "Se deve aprimorar o prompt com assistência de IA" + }, + "image": { + "name": "image", + "tooltip": "Imagem de referência opcional para guiar a geração do vídeo" + }, + "model": { + "name": "model", + "tooltip": "Modelo Veo 2 a ser usado para geração de vídeo" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Prompt negativo para orientar o que evitar no vídeo" + }, + "person_generation": { + "name": "person_generation", + "tooltip": "Se permite gerar pessoas no vídeo" + }, + "prompt": { + "name": "prompt", + "tooltip": "Descrição em texto do vídeo" + }, + "seed": { + "name": "seed", + "tooltip": "Semente para geração de vídeo (0 para aleatório)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "VideoLinearCFGGuidance": { + "display_name": "VideoLinearCFGGuidance", + "inputs": { + "min_cfg": { + "name": "min_cfg" + }, + "model": { + "name": "model" + } + } + }, + "VideoTriangleCFGGuidance": { + "display_name": "VideoTriangleCFGGuidance", + "inputs": { + "min_cfg": { + "name": "min_cfg" + }, + "model": { + "name": "model" + } + } + }, + "Vidu2ImageToVideoNode": { + "description": "Gere um vídeo a partir de uma imagem e um prompt opcional.", + "display_name": "Geração de Vídeo a partir de Imagem Vidu2", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "duration": { + "name": "duração" + }, + "image": { + "name": "imagem", + "tooltip": "Uma imagem a ser usada como quadro inicial do vídeo gerado." + }, + "model": { + "name": "modelo" + }, + "movement_amplitude": { + "name": "amplitude_de_movimento", + "tooltip": "A amplitude de movimento dos objetos no quadro." + }, + "prompt": { + "name": "prompt", + "tooltip": "Um prompt de texto opcional para geração de vídeo (máx. 2000 caracteres)." + }, + "resolution": { + "name": "resolução" + }, + "seed": { + "name": "semente" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2ReferenceVideoNode": { + "description": "Gere um vídeo a partir de múltiplas imagens de referência e um prompt.", + "display_name": "Geração de Vídeo por Referência Vidu2", + "inputs": { + "aspect_ratio": { + "name": "proporção" + }, + "audio": { + "name": "áudio", + "tooltip": "Quando ativado, o vídeo conterá fala gerada e música de fundo com base no prompt." + }, + "control_after_generate": { + "name": "controle após gerar" + }, + "duration": { + "name": "duração" + }, + "model": { + "name": "modelo" + }, + "movement_amplitude": { + "name": "amplitude_de_movimento", + "tooltip": "A amplitude de movimento dos objetos no quadro." + }, + "prompt": { + "name": "prompt", + "tooltip": "Quando ativado, o vídeo incluirá fala gerada e música de fundo com base no prompt." + }, + "resolution": { + "name": "resolução" + }, + "seed": { + "name": "semente" + }, + "subjects": { + "name": "sujeitos", + "tooltip": "Para cada sujeito, forneça até 3 imagens de referência (7 imagens no total entre todos os sujeitos). Referencie-os nos prompts via @subject{subject_id}." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2StartEndToVideoNode": { + "description": "Gere um vídeo a partir de um quadro inicial, um quadro final e um prompt.", + "display_name": "Geração de Vídeo Vidu2 de Quadro Inicial/Final", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "duration": { + "name": "duração" + }, + "end_frame": { + "name": "quadro_final" + }, + "first_frame": { + "name": "quadro_inicial" + }, + "model": { + "name": "modelo" + }, + "movement_amplitude": { + "name": "amplitude_de_movimento", + "tooltip": "A amplitude de movimento dos objetos no quadro." + }, + "prompt": { + "name": "prompt", + "tooltip": "Descrição do prompt (máx. 2000 caracteres)." + }, + "resolution": { + "name": "resolução" + }, + "seed": { + "name": "semente" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2TextToVideoNode": { + "description": "Gerar vídeo a partir de um prompt de texto", + "display_name": "Geração de Vídeo Vidu2 a partir de Texto", + "inputs": { + "aspect_ratio": { + "name": "proporção" + }, + "background_music": { + "name": "música_de_fundo", + "tooltip": "Se deve adicionar música de fundo ao vídeo gerado." + }, + "control_after_generate": { + "name": "controle após gerar" + }, + "duration": { + "name": "duração" + }, + "model": { + "name": "modelo" + }, + "prompt": { + "name": "prompt", + "tooltip": "Uma descrição textual para geração de vídeo, com comprimento máximo de 2000 caracteres." + }, + "resolution": { + "name": "resolução" + }, + "seed": { + "name": "semente" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ViduImageToVideoNode": { + "description": "Gera vídeo a partir de uma imagem e prompt opcional", + "display_name": "Geração de Vídeo a partir de Imagem Vidu", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "duration": { + "name": "duration", + "tooltip": "Duração do vídeo de saída em segundos" + }, + "image": { + "name": "image", + "tooltip": "Uma imagem a ser usada como quadro inicial do vídeo gerado" + }, + "model": { + "name": "model", + "tooltip": "Nome do modelo" + }, + "movement_amplitude": { + "name": "movement_amplitude", + "tooltip": "A amplitude de movimento dos objetos no quadro" + }, + "prompt": { + "name": "prompt", + "tooltip": "Descrição textual para geração de vídeo" + }, + "resolution": { + "name": "resolution", + "tooltip": "Os valores suportados podem variar conforme o modelo e a duração" + }, + "seed": { + "name": "seed", + "tooltip": "Semente para geração de vídeo (0 para aleatório)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ViduReferenceVideoNode": { + "description": "Gerar vídeo a partir de múltiplas imagens e prompt", + "display_name": "Geração de Vídeo por Referência Vidu", + "inputs": { + "aspect_ratio": { + "name": "proporção", + "tooltip": "A proporção do vídeo de saída" + }, + "control_after_generate": { + "name": "controle após gerar" + }, + "duration": { + "name": "duração", + "tooltip": "Duração do vídeo de saída em segundos" + }, + "images": { + "name": "imagens", + "tooltip": "Imagens para usar como referência para gerar um vídeo com sujeitos consistentes (máx. 7 imagens)." + }, + "model": { + "name": "modelo", + "tooltip": "Nome do modelo" + }, + "movement_amplitude": { + "name": "amplitude de movimento", + "tooltip": "A amplitude de movimento dos objetos no quadro" + }, + "prompt": { + "name": "prompt", + "tooltip": "Uma descrição textual para geração de vídeo" + }, + "resolution": { + "name": "resolução", + "tooltip": "Os valores suportados podem variar conforme o modelo e a duração" + }, + "seed": { + "name": "semente", + "tooltip": "Semente para geração de vídeo (0 para aleatório)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ViduStartEndToVideoNode": { + "description": "Gerar um vídeo a partir dos quadros inicial e final e um prompt", + "display_name": "Geração de Vídeo Vidu de Início e Fim", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "duration": { + "name": "duração", + "tooltip": "Duração do vídeo de saída em segundos" + }, + "end_frame": { + "name": "quadro final", + "tooltip": "Quadro final" + }, + "first_frame": { + "name": "quadro inicial", + "tooltip": "Quadro inicial" + }, + "model": { + "name": "modelo", + "tooltip": "Nome do modelo" + }, + "movement_amplitude": { + "name": "amplitude de movimento", + "tooltip": "A amplitude de movimento dos objetos no quadro" + }, + "prompt": { + "name": "prompt", + "tooltip": "Uma descrição textual para geração de vídeo" + }, + "resolution": { + "name": "resolução", + "tooltip": "Os valores suportados podem variar conforme o modelo e a duração" + }, + "seed": { + "name": "semente", + "tooltip": "Semente para geração de vídeo (0 para aleatório)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ViduTextToVideoNode": { + "description": "Gerar vídeo a partir de um prompt de texto", + "display_name": "Geração de Vídeo Vidu por Texto", + "inputs": { + "aspect_ratio": { + "name": "proporção", + "tooltip": "A proporção do vídeo de saída" + }, + "control_after_generate": { + "name": "controle após gerar" + }, + "duration": { + "name": "duração", + "tooltip": "Duração do vídeo de saída em segundos" + }, + "model": { + "name": "modelo", + "tooltip": "Nome do modelo" + }, + "movement_amplitude": { + "name": "amplitude de movimento", + "tooltip": "A amplitude de movimento dos objetos no quadro" + }, + "prompt": { + "name": "prompt", + "tooltip": "Uma descrição textual para geração de vídeo" + }, + "resolution": { + "name": "resolução", + "tooltip": "Os valores suportados podem variar conforme o modelo e a duração" + }, + "seed": { + "name": "semente", + "tooltip": "Semente para geração de vídeo (0 para aleatório)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "VoxelToMesh": { + "display_name": "VoxelParaMalha", + "inputs": { + "algorithm": { + "name": "algoritmo" + }, + "threshold": { + "name": "limiar" + }, + "voxel": { + "name": "voxel" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "VoxelToMeshBasic": { + "display_name": "VoxelToMeshBasic", + "inputs": { + "threshold": { + "name": "limiar" + }, + "voxel": { + "name": "voxel" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Wan22FunControlToVideo": { + "display_name": "Wan22FunControlToVideo", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "control_video": { + "name": "vídeo_de_controle" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "duração" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "ref_image": { + "name": "imagem_de_referência" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latente", + "tooltip": null + } + } + }, + "Wan22ImageToVideoLatent": { + "display_name": "Wan22ImageToVideoLatent", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "duração" + }, + "start_image": { + "name": "imagem_inicial" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanAnimateToVideo": { + "display_name": "WanAnimateToVideo", + "inputs": { + "background_video": { + "name": "vídeo_de_fundo" + }, + "batch_size": { + "name": "tamanho_do_lote" + }, + "character_mask": { + "name": "máscara_de_personagem" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "continue_motion": { + "name": "continuar_movimento" + }, + "continue_motion_max_frames": { + "name": "continuar_movimento_máx_quadros" + }, + "face_video": { + "name": "vídeo_de_rosto" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "duração" + }, + "negative": { + "name": "negativo" + }, + "pose_video": { + "name": "vídeo_de_pose" + }, + "positive": { + "name": "positivo" + }, + "reference_image": { + "name": "imagem_de_referência" + }, + "vae": { + "name": "vae" + }, + "video_frame_offset": { + "name": "deslocamento_quadro_vídeo", + "tooltip": "A quantidade de quadros a avançar em todos os vídeos de entrada. Usado para gerar vídeos mais longos em partes. Conecte à saída video_frame_offset do nó anterior para estender um vídeo." + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latente", + "tooltip": null + }, + "3": { + "name": "latente_recortado", + "tooltip": null + }, + "4": { + "name": "imagem_recortada", + "tooltip": null + }, + "5": { + "name": "deslocamento_quadro_vídeo", + "tooltip": null + } + } + }, + "WanCameraEmbedding": { + "display_name": "WanCameraEmbedding", + "inputs": { + "camera_pose": { + "name": "pose_da_câmera" + }, + "cx": { + "name": "cx" + }, + "cy": { + "name": "cy" + }, + "fx": { + "name": "fx" + }, + "fy": { + "name": "fy" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "duração" + }, + "speed": { + "name": "velocidade" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "camera_embedding", + "tooltip": null + }, + "1": { + "name": "largura", + "tooltip": null + }, + "2": { + "name": "altura", + "tooltip": null + }, + "3": { + "name": "duração", + "tooltip": null + } + } + }, + "WanCameraImageToVideo": { + "display_name": "WanCameraImageToVideo", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "camera_conditions": { + "name": "condições_da_câmera" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "comprimento" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "start_image": { + "name": "imagem_inicial" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latente", + "tooltip": null + } + } + }, + "WanContextWindowsManual": { + "description": "Defina manualmente as janelas de contexto para modelos do tipo WAN (dim=2).", + "display_name": "WAN Context Windows (Manual)", + "inputs": { + "closed_loop": { + "name": "ciclo_fechado", + "tooltip": "Se deve fechar o ciclo da janela de contexto; aplicável apenas para agendamentos em loop." + }, + "context_length": { + "name": "comprimento_do_contexto", + "tooltip": "O comprimento da janela de contexto." + }, + "context_overlap": { + "name": "sobreposição_do_contexto", + "tooltip": "A sobreposição da janela de contexto." + }, + "context_schedule": { + "name": "agendamento_do_contexto", + "tooltip": "O passo da janela de contexto." + }, + "context_stride": { + "name": "passo_do_contexto", + "tooltip": "O passo da janela de contexto; aplicável apenas para agendamentos uniformes." + }, + "freenoise": { + "name": "freenoise", + "tooltip": "Se deve aplicar embaralhamento de ruído FreeNoise, melhora a mesclagem das janelas." + }, + "fuse_method": { + "name": "método_de_fusão", + "tooltip": "O método a ser usado para fundir as janelas de contexto." + }, + "model": { + "name": "modelo", + "tooltip": "O modelo ao qual aplicar as janelas de contexto durante a amostragem." + } + }, + "outputs": { + "0": { + "tooltip": "O modelo com janelas de contexto aplicadas durante a amostragem." + } + } + }, + "WanFirstLastFrameToVideo": { + "display_name": "WanFirstLastFrameToVideo", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "clip_vision_end_image": { + "name": "clip_vision_end_image" + }, + "clip_vision_start_image": { + "name": "clip_vision_start_image" + }, + "end_image": { + "name": "imagem_final" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "comprimento" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "start_image": { + "name": "imagem_inicial" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latente", + "tooltip": null + } + } + }, + "WanFunControlToVideo": { + "display_name": "WanFunControlToVideo", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "control_video": { + "name": "vídeo_de_controle" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "duração" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "start_image": { + "name": "imagem_inicial" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latente", + "tooltip": null + } + } + }, + "WanFunInpaintToVideo": { + "display_name": "WanFunInpaintToVideo", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "end_image": { + "name": "imagem_final" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "duração" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "start_image": { + "name": "imagem_inicial" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latente", + "tooltip": null + } + } + }, + "WanHuMoImageToVideo": { + "display_name": "WanHuMoImageToVideo", + "inputs": { + "audio_encoder_output": { + "name": "saída_do_codificador_de_áudio" + }, + "batch_size": { + "name": "tamanho_do_lote" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "duração" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "ref_image": { + "name": "imagem_de_referência" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latente", + "tooltip": null + } + } + }, + "WanImageToImageApi": { + "description": "Gera uma imagem a partir de uma ou duas imagens de entrada e um prompt de texto. A imagem de saída atualmente é fixa em 1,6 MP, e sua proporção corresponde à(s) imagem(ns) de entrada.", + "display_name": "Wan Imagem para Imagem", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "image": { + "name": "imagem", + "tooltip": "Edição de imagem única ou fusão de múltiplas imagens. Máximo de 2 imagens." + }, + "model": { + "name": "modelo", + "tooltip": "Modelo a ser utilizado." + }, + "negative_prompt": { + "name": "prompt negativo", + "tooltip": "Prompt negativo descrevendo o que evitar." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt descrevendo os elementos e características visuais. Suporta inglês e chinês." + }, + "seed": { + "name": "semente", + "tooltip": "Semente a ser utilizada para a geração." + }, + "watermark": { + "name": "marca d'água", + "tooltip": "Se deve adicionar uma marca d'água gerada por IA ao resultado." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanImageToVideo": { + "display_name": "WanImagemParaVídeo", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "duração" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "start_image": { + "name": "imagem_inicial" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latente", + "tooltip": null + } + } + }, + "WanImageToVideoApi": { + "description": "Gera um vídeo a partir do primeiro quadro e de um prompt de texto.", + "display_name": "Wan Imagem para Vídeo", + "inputs": { + "audio": { + "name": "áudio", + "tooltip": "O áudio deve conter uma voz clara e alta, sem ruídos ou música de fundo." + }, + "control_after_generate": { + "name": "controle após gerar" + }, + "duration": { + "name": "duração", + "tooltip": "Duração 15 disponível apenas para o modelo WAN2.6." + }, + "generate_audio": { + "name": "gerar_áudio", + "tooltip": "Se nenhum áudio for fornecido, gerar áudio automaticamente." + }, + "image": { + "name": "imagem" + }, + "model": { + "name": "modelo", + "tooltip": "Modelo a ser utilizado." + }, + "negative_prompt": { + "name": "prompt negativo", + "tooltip": "Prompt negativo descrevendo o que evitar." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt descrevendo os elementos e características visuais. Suporta inglês e chinês." + }, + "prompt_extend": { + "name": "estender_prompt", + "tooltip": "Se deve aprimorar o prompt com assistência de IA." + }, + "resolution": { + "name": "resolução" + }, + "seed": { + "name": "semente", + "tooltip": "Semente a ser utilizada para a geração." + }, + "shot_type": { + "name": "tipo_de_tomada", + "tooltip": "Especifica o tipo de tomada para o vídeo gerado, ou seja, se o vídeo é uma única tomada contínua ou múltiplas tomadas com cortes. Este parâmetro só tem efeito quando prompt_extend é True." + }, + "watermark": { + "name": "marca d'água", + "tooltip": "Se deve adicionar uma marca d'água gerada por IA ao resultado." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanInfiniteTalkToVideo": { + "display_name": "WanInfiniteTalkToVideo", + "inputs": { + "audio_encoder_output_1": { + "name": "saída do codificador de áudio 1" + }, + "audio_scale": { + "name": "escala de áudio" + }, + "clip_vision_output": { + "name": "saída do clip vision" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "duração" + }, + "mode": { + "name": "modo" + }, + "model": { + "name": "modelo" + }, + "model_patch": { + "name": "patch do modelo" + }, + "motion_frame_count": { + "name": "quantidade de quadros de movimento", + "tooltip": "Número de quadros anteriores a serem usados como contexto de movimento." + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "previous_frames": { + "name": "quadros anteriores" + }, + "start_image": { + "name": "imagem inicial" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "modelo", + "tooltip": null + }, + "1": { + "name": "positivo", + "tooltip": null + }, + "2": { + "name": "negativo", + "tooltip": null + }, + "3": { + "name": "latente", + "tooltip": null + }, + "4": { + "name": "imagem recortada", + "tooltip": null + } + } + }, + "WanMoveConcatTrack": { + "display_name": "WanMoveConcatTrack", + "inputs": { + "tracks_1": { + "name": "tracks_1" + }, + "tracks_2": { + "name": "tracks_2" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanMoveTrackToVideo": { + "display_name": "WanMoveTrackToVideo", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "comprimento" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "start_image": { + "name": "imagem_inicial" + }, + "strength": { + "name": "força", + "tooltip": "Força do condicionamento da trilha." + }, + "tracks": { + "name": "trilhas" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "WanMoveTracksFromCoords": { + "display_name": "WanMoveTracksFromCoords", + "inputs": { + "track_coords": { + "name": "track_coords" + }, + "track_mask": { + "name": "track_mask" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "track_length", + "tooltip": null + } + } + }, + "WanMoveVisualizeTracks": { + "display_name": "WanMoveVisualizeTracks", + "inputs": { + "circle_size": { + "name": "tamanho_do_círculo" + }, + "images": { + "name": "imagens" + }, + "line_resolution": { + "name": "resolução_da_linha" + }, + "line_width": { + "name": "largura_da_linha" + }, + "opacity": { + "name": "opacidade" + }, + "tracks": { + "name": "trilhas" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanPhantomSubjectToVideo": { + "display_name": "WanPhantomSubjectToVideo", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "height": { + "name": "altura" + }, + "images": { + "name": "imagens" + }, + "length": { + "name": "comprimento" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "texto_negativo", + "tooltip": null + }, + "2": { + "name": "texto_img_negativo", + "tooltip": null + }, + "3": { + "name": "latent", + "tooltip": null + } + } + }, + "WanReferenceVideoApi": { + "description": "Use o personagem e a voz dos vídeos de entrada, combinados com um prompt, para gerar um novo vídeo que mantém a consistência do personagem.", + "display_name": "Wan Referência para Vídeo", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "duration": { + "name": "duração" + }, + "model": { + "name": "modelo" + }, + "negative_prompt": { + "name": "prompt negativo", + "tooltip": "Prompt negativo descrevendo o que deve ser evitado." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt descrevendo os elementos e características visuais. Suporta inglês e chinês. Use identificadores como `character1` e `character2` para se referir aos personagens de referência." + }, + "reference_videos": { + "name": "vídeos de referência" + }, + "seed": { + "name": "semente" + }, + "shot_type": { + "name": "tipo de tomada", + "tooltip": "Especifica o tipo de tomada para o vídeo gerado, ou seja, se o vídeo é uma única tomada contínua ou múltiplas tomadas com cortes." + }, + "size": { + "name": "tamanho" + }, + "watermark": { + "name": "marca d'água", + "tooltip": "Se deve adicionar uma marca d'água gerada por IA ao resultado." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanSoundImageToVideo": { + "display_name": "WanSoundImageToVideo", + "inputs": { + "audio_encoder_output": { + "name": "saída do codificador de áudio" + }, + "batch_size": { + "name": "tamanho do lote" + }, + "control_video": { + "name": "vídeo de controle" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "comprimento" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "ref_image": { + "name": "imagem de referência" + }, + "ref_motion": { + "name": "referência de movimento" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latente", + "tooltip": null + } + } + }, + "WanSoundImageToVideoExtend": { + "display_name": "WanSoundImageToVideoExtend", + "inputs": { + "audio_encoder_output": { + "name": "saída do codificador de áudio" + }, + "control_video": { + "name": "vídeo de controle" + }, + "length": { + "name": "comprimento" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "ref_image": { + "name": "imagem de referência" + }, + "vae": { + "name": "vae" + }, + "video_latent": { + "name": "latente de vídeo" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latente", + "tooltip": null + } + } + }, + "WanTextToImageApi": { + "description": "Gera uma imagem com base em um prompt de texto.", + "display_name": "Wan Texto para Imagem", + "inputs": { + "control_after_generate": { + "name": "controle após gerar" + }, + "height": { + "name": "altura" + }, + "model": { + "name": "modelo", + "tooltip": "Modelo a ser utilizado." + }, + "negative_prompt": { + "name": "prompt_negativo", + "tooltip": "Prompt negativo descrevendo o que evitar." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt descrevendo os elementos e características visuais. Suporta inglês e chinês." + }, + "prompt_extend": { + "name": "estender_prompt", + "tooltip": "Se deve aprimorar o prompt com assistência de IA." + }, + "seed": { + "name": "semente", + "tooltip": "Semente a ser utilizada para a geração." + }, + "watermark": { + "name": "marca_d'água", + "tooltip": "Se deve adicionar uma marca d'água gerada por IA ao resultado." + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanTextToVideoApi": { + "description": "Gera um vídeo com base em um prompt de texto.", + "display_name": "Wan Texto para Vídeo", + "inputs": { + "audio": { + "name": "áudio", + "tooltip": "O áudio deve conter uma voz clara e alta, sem ruídos ou música de fundo." + }, + "control_after_generate": { + "name": "controle após gerar" + }, + "duration": { + "name": "duração", + "tooltip": "Uma duração de 15 segundos está disponível apenas para o modelo Wan 2.6." + }, + "generate_audio": { + "name": "gerar_áudio", + "tooltip": "Se nenhum áudio for fornecido, gerar áudio automaticamente." + }, + "model": { + "name": "modelo", + "tooltip": "Modelo a ser utilizado." + }, + "negative_prompt": { + "name": "prompt_negativo", + "tooltip": "Prompt negativo descrevendo o que evitar." + }, + "prompt": { + "name": "prompt", + "tooltip": "Prompt descrevendo os elementos e características visuais. Suporta inglês e chinês." + }, + "prompt_extend": { + "name": "estender_prompt", + "tooltip": "Se deve aprimorar o prompt com assistência de IA." + }, + "seed": { + "name": "semente", + "tooltip": "Semente a ser utilizada para a geração." + }, + "shot_type": { + "name": "tipo_de_tomada", + "tooltip": "Especifica o tipo de tomada para o vídeo gerado, ou seja, se o vídeo é uma única tomada contínua ou múltiplas tomadas com cortes. Este parâmetro só tem efeito quando estender_prompt está ativado." + }, + "size": { + "name": "tamanho" + }, + "watermark": { + "name": "marca_d'água", + "tooltip": "Se deve adicionar uma marca d'água gerada por IA ao resultado." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanTrackToVideo": { + "display_name": "WanTrackToVideo", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "duração" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "start_image": { + "name": "imagem_inicial" + }, + "temperature": { + "name": "temperatura" + }, + "topk": { + "name": "topk" + }, + "tracks": { + "name": "faixas" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latente", + "tooltip": null + } + } + }, + "WanVaceToVideo": { + "display_name": "WanVaceToVideo", + "inputs": { + "batch_size": { + "name": "tamanho_do_lote" + }, + "control_masks": { + "name": "máscaras_de_controle" + }, + "control_video": { + "name": "control_video" + }, + "height": { + "name": "altura" + }, + "length": { + "name": "duração" + }, + "negative": { + "name": "negativo" + }, + "positive": { + "name": "positivo" + }, + "reference_image": { + "name": "imagem_de_referência" + }, + "strength": { + "name": "força" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "largura" + } + }, + "outputs": { + "0": { + "name": "positivo", + "tooltip": null + }, + "1": { + "name": "negativo", + "tooltip": null + }, + "2": { + "name": "latente", + "tooltip": null + }, + "3": { + "name": "latente_recortado", + "tooltip": null + } + } + }, + "WavespeedFlashVSRNode": { + "description": "Aprimorador de vídeo rápido e de alta qualidade que aumenta a resolução e restaura a nitidez de vídeos de baixa resolução ou borrados.", + "display_name": "FlashVSR Video Upscale", + "inputs": { + "target_resolution": { + "name": "resolução_alvo" + }, + "video": { + "name": "vídeo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WavespeedImageUpscaleNode": { + "description": "Aumente a resolução e a qualidade da imagem, ampliando fotos para 4K ou 8K para resultados nítidos e detalhados.", + "display_name": "WaveSpeed Image Upscale", + "inputs": { + "image": { + "name": "imagem" + }, + "model": { + "name": "modelo" + }, + "target_resolution": { + "name": "resolução_alvo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WebcamCapture": { + "display_name": "Captura da Webcam", + "inputs": { + "capture_on_queue": { + "name": "capturar_na_fila" + }, + "height": { + "name": "altura" + }, + "image": { + "name": "imagem" + }, + "waiting for camera___": { + }, + "width": { + "name": "largura" + } + } + }, + "ZImageFunControlnet": { + "display_name": "ZImageFunControlnet", + "inputs": { + "image": { + "name": "imagem" + }, + "inpaint_image": { + "name": "imagem_para_retouch" + }, + "mask": { + "name": "máscara" + }, + "model": { + "name": "modelo" + }, + "model_patch": { + "name": "patch_do_modelo" + }, + "strength": { + "name": "força" + }, + "vae": { + "name": "vae" + } + } + }, + "unCLIPCheckpointLoader": { + "display_name": "unCLIPCheckpointLoader", + "inputs": { + "ckpt_name": { + "name": "ckpt_name" + } + } + }, + "unCLIPConditioning": { + "display_name": "unCLIPConditioning", + "inputs": { + "clip_vision_output": { + "name": "clip_vision_output" + }, + "conditioning": { + "name": "condicionamento" + }, + "noise_augmentation": { + "name": "aumento_ruído" + }, + "strength": { + "name": "força" + } + } + }, + "wanBlockSwap": { + "description": "NOP", + "display_name": "wanBlockSwap", + "inputs": { + "model": { + "name": "modelo" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + } +} diff --git a/src/locales/pt-BR/settings.json b/src/locales/pt-BR/settings.json new file mode 100644 index 000000000..c2a60c117 --- /dev/null +++ b/src/locales/pt-BR/settings.json @@ -0,0 +1,479 @@ +{ + "Comfy-Desktop_AutoUpdate": { + "name": "Verificar atualizações automaticamente" + }, + "Comfy-Desktop_SendStatistics": { + "name": "Enviar métricas de uso anônimas" + }, + "Comfy-Desktop_UV_PypiInstallMirror": { + "name": "Espelho de instalação do Pypi", + "tooltip": "Espelho padrão para instalação via pip" + }, + "Comfy-Desktop_UV_PythonInstallMirror": { + "name": "Espelho de instalação do Python", + "tooltip": "Instalações gerenciadas do Python são baixadas do projeto Astral python-build-standalone. Esta variável pode ser definida para uma URL de espelho para usar uma fonte diferente para as instalações do Python. A URL fornecida substituirá https://github.com/astral-sh/python-build-standalone/releases/download em, por exemplo, https://github.com/astral-sh/python-build-standalone/releases/download/20240713/cpython-3.12.4%2B20240713-aarch64-apple-darwin-install_only.tar.gz. As distribuições podem ser lidas de um diretório local usando o esquema de URL file://." + }, + "Comfy-Desktop_UV_TorchInstallMirror": { + "name": "Espelho de instalação do Torch", + "tooltip": "Espelho pip para instalação do pytorch" + }, + "Comfy-Desktop_WindowStyle": { + "name": "Estilo da janela", + "options": { + "custom": "personalizado", + "default": "padrão" + }, + "tooltip": "Personalizado: Substitui a barra de título do sistema pelo menu superior do ComfyUI" + }, + "Comfy_Canvas_BackgroundImage": { + "name": "Imagem de fundo do canvas", + "tooltip": "URL da imagem para o fundo do canvas. Você pode clicar com o botão direito em uma imagem no painel de saídas e selecionar \"Definir como fundo\" para usá-la, ou enviar sua própria imagem usando o botão de upload." + }, + "Comfy_Canvas_LeftMouseClickBehavior": { + "name": "Comportamento do clique esquerdo do mouse", + "options": { + "Panning": "Deslocar", + "Select": "Selecionar" + } + }, + "Comfy_Canvas_MouseWheelScroll": { + "name": "Rolar com a roda do mouse", + "options": { + "Panning": "Deslocar", + "Zoom in/out": "Zoom in/out" + } + }, + "Comfy_Canvas_NavigationMode": { + "name": "Modo de navegação", + "options": { + "Custom": "Personalizado", + "Drag Navigation": "Navegação por arrasto", + "Standard (New)": "Padrão (Novo)" + } + }, + "Comfy_Canvas_SelectionToolbox": { + "name": "Mostrar caixa de ferramentas de seleção" + }, + "Comfy_ConfirmClear": { + "name": "Exigir confirmação ao limpar o fluxo de trabalho" + }, + "Comfy_DOMClippingEnabled": { + "name": "Ativar recorte de elementos DOM (ativar pode reduzir o desempenho)" + }, + "Comfy_DevMode": { + "name": "Ativar opções de modo desenvolvedor (salvar API, etc.)" + }, + "Comfy_DisableFloatRounding": { + "name": "Desativar arredondamento padrão de widget float.", + "tooltip": "(requer recarregar a página) Não é possível desativar o arredondamento quando ele é definido pelo nó no backend." + }, + "Comfy_DisableSliders": { + "name": "Desativar sliders dos widgets dos nós" + }, + "Comfy_EditAttention_Delta": { + "name": "Precisão de Ctrl+cima/baixo" + }, + "Comfy_EnableTooltips": { + "name": "Ativar dicas de ferramenta" + }, + "Comfy_EnableWorkflowViewRestore": { + "name": "Salvar e restaurar posição e nível de zoom do canvas nos fluxos de trabalho" + }, + "Comfy_Execution_PreviewMethod": { + "name": "Método de visualização ao vivo", + "options": { + "auto": "auto", + "default": "padrão", + "latent2rgb": "latent2rgb", + "none": "nenhum", + "taesd": "taesd" + }, + "tooltip": "Método de visualização ao vivo durante a geração de imagem. \"padrão\" usa a configuração do servidor CLI." + }, + "Comfy_FloatRoundingPrecision": { + "name": "Casas decimais de arredondamento do widget float [0 = auto].", + "tooltip": "(requer recarregar a página)" + }, + "Comfy_Graph_CanvasInfo": { + "name": "Mostrar informações do canvas no canto inferior esquerdo (fps, etc.)" + }, + "Comfy_Graph_CanvasMenu": { + "name": "Mostrar menu do canvas do grafo" + }, + "Comfy_Graph_CtrlShiftZoom": { + "name": "Ativar atalho de zoom rápido (Ctrl + Shift + Arrastar)" + }, + "Comfy_Graph_LinkMarkers": { + "name": "Marcadores de meio do link", + "options": { + "Arrow": "Seta", + "Circle": "Círculo", + "None": "Nenhum" + } + }, + "Comfy_Graph_LiveSelection": { + "name": "Seleção ao vivo", + "tooltip": "Quando ativado, os nós são selecionados/deselecionados em tempo real enquanto você arrasta o retângulo de seleção, semelhante a outras ferramentas de design." + }, + "Comfy_Graph_ZoomSpeed": { + "name": "Velocidade de zoom da tela" + }, + "Comfy_GroupSelectedNodes_Padding": { + "name": "Espaçamento dos nós selecionados no grupo" + }, + "Comfy_Group_DoubleClickTitleToEdit": { + "name": "Clique duplo no título do grupo para editar" + }, + "Comfy_LinkRelease_Action": { + "name": "Ação ao soltar o link (Sem modificador)", + "options": { + "context menu": "menu de contexto", + "no action": "nenhuma ação", + "search box": "caixa de pesquisa" + } + }, + "Comfy_LinkRelease_ActionShift": { + "name": "Ação ao soltar o link (Shift)", + "options": { + "context menu": "menu de contexto", + "no action": "nenhuma ação", + "search box": "caixa de pesquisa" + } + }, + "Comfy_LinkRenderMode": { + "name": "Modo de renderização do link", + "options": { + "Hidden": "Oculto", + "Linear": "Linear", + "Spline": "Spline", + "Straight": "Reto" + } + }, + "Comfy_Load3D_3DViewerEnable": { + "name": "Ativar visualizador 3D (Beta)", + "tooltip": "Ativa o visualizador 3D (Beta) para os nós selecionados. Este recurso permite visualizar e interagir com modelos 3D diretamente no visualizador 3D em tamanho completo." + }, + "Comfy_Load3D_BackgroundColor": { + "name": "Cor de fundo inicial", + "tooltip": "Controla a cor de fundo padrão da cena 3D. Esta configuração determina a aparência do fundo ao criar um novo widget 3D, mas pode ser ajustada individualmente para cada widget após a criação." + }, + "Comfy_Load3D_CameraType": { + "name": "Tipo de câmera inicial", + "options": { + "orthographic": "ortográfica", + "perspective": "perspectiva" + }, + "tooltip": "Controla se a câmera é perspectiva ou ortográfica por padrão ao criar um novo widget 3D. Este padrão ainda pode ser alternado individualmente para cada widget após a criação." + }, + "Comfy_Load3D_LightAdjustmentIncrement": { + "name": "Incremento de ajuste de luz", + "tooltip": "Controla o tamanho do incremento ao ajustar a intensidade da luz em cenas 3D. Um valor menor permite um controle mais preciso dos ajustes de iluminação, enquanto um valor maior resulta em mudanças mais perceptíveis por ajuste." + }, + "Comfy_Load3D_LightIntensity": { + "name": "Intensidade de luz inicial", + "tooltip": "Define o nível de brilho padrão da iluminação na cena 3D. Este valor determina o quão intensamente as luzes iluminam os objetos ao criar um novo widget 3D, mas pode ser ajustado individualmente para cada widget após a criação." + }, + "Comfy_Load3D_LightIntensityMaximum": { + "name": "Intensidade máxima da luz", + "tooltip": "Define o valor máximo permitido para a intensidade da luz em cenas 3D. Isso determina o limite superior de brilho que pode ser definido ao ajustar a iluminação em qualquer widget 3D." + }, + "Comfy_Load3D_LightIntensityMinimum": { + "name": "Intensidade mínima da luz", + "tooltip": "Define o valor mínimo permitido para a intensidade da luz em cenas 3D. Isso determina o limite inferior de brilho que pode ser definido ao ajustar a iluminação em qualquer widget 3D." + }, + "Comfy_Load3D_PLYEngine": { + "name": "Engine PLY", + "options": { + "fastply": "fastply", + "sparkjs": "sparkjs", + "threejs": "threejs" + }, + "tooltip": "Selecione a engine para carregar arquivos PLY. \"threejs\" usa o PLYLoader nativo do Three.js (melhor para arquivos PLY de malha). \"fastply\" usa um carregador otimizado para arquivos PLY de nuvem de pontos ASCII. \"sparkjs\" usa Spark.js para arquivos PLY de Gaussian Splatting 3D." + }, + "Comfy_Load3D_ShowGrid": { + "name": "Visibilidade inicial da grade", + "tooltip": "Controla se a grade está visível por padrão ao criar um novo widget 3D. Este padrão ainda pode ser alternado individualmente para cada widget após a criação." + }, + "Comfy_Locale": { + "name": "Idioma" + }, + "Comfy_MaskEditor_BrushAdjustmentSpeed": { + "name": "Multiplicador de velocidade de ajuste do pincel", + "tooltip": "Controla a rapidez com que o tamanho e a dureza do pincel mudam ao ajustar. Valores mais altos significam mudanças mais rápidas." + }, + "Comfy_MaskEditor_UseDominantAxis": { + "name": "Travar ajuste do pincel ao eixo dominante", + "tooltip": "Quando ativado, os ajustes do pincel afetarão apenas o tamanho OU a dureza, dependendo de qual direção você mover mais" + }, + "Comfy_ModelLibrary_AutoLoadAll": { + "name": "Carregar automaticamente todas as pastas de modelos", + "tooltip": "Se verdadeiro, todas as pastas serão carregadas assim que você abrir a biblioteca de modelos (isso pode causar atrasos durante o carregamento). Se falso, as pastas de modelos no nível raiz só serão carregadas quando você clicar nelas." + }, + "Comfy_ModelLibrary_NameFormat": { + "name": "Qual nome exibir na visualização em árvore da biblioteca de modelos", + "options": { + "filename": "filename", + "title": "title" + }, + "tooltip": "Selecione \"filename\" para exibir uma visualização simplificada do nome do arquivo bruto (sem diretório ou extensão \".safetensors\") na lista de modelos. Selecione \"title\" para exibir o título configurável dos metadados do modelo." + }, + "Comfy_NodeBadge_NodeIdBadgeMode": { + "name": "Modo de exibição do ID do nó", + "options": { + "None": "Nenhum", + "Show all": "Mostrar todos" + } + }, + "Comfy_NodeBadge_NodeLifeCycleBadgeMode": { + "name": "Modo de exibição do ciclo de vida do nó", + "options": { + "None": "Nenhum", + "Show all": "Mostrar todos" + } + }, + "Comfy_NodeBadge_NodeSourceBadgeMode": { + "name": "Modo de exibição da origem do nó", + "options": { + "Hide built-in": "Ocultar integrados", + "None": "Nenhum", + "Show all": "Mostrar todos" + } + }, + "Comfy_NodeBadge_ShowApiPricing": { + "name": "Mostrar selo de preço do nó de API" + }, + "Comfy_NodeSearchBoxImpl": { + "name": "Implementação da caixa de busca de nós", + "options": { + "default": "padrão", + "litegraph (legacy)": "litegraph (legado)" + } + }, + "Comfy_NodeSearchBoxImpl_NodePreview": { + "name": "Pré-visualização do nó", + "tooltip": "Aplica-se apenas à implementação padrão" + }, + "Comfy_NodeSearchBoxImpl_ShowCategory": { + "name": "Mostrar categoria do nó nos resultados da busca", + "tooltip": "Aplica-se apenas à implementação padrão" + }, + "Comfy_NodeSearchBoxImpl_ShowIdName": { + "name": "Mostrar nome de ID do nó nos resultados da busca", + "tooltip": "Aplica-se apenas à implementação padrão" + }, + "Comfy_NodeSearchBoxImpl_ShowNodeFrequency": { + "name": "Mostrar frequência do nó nos resultados da busca", + "tooltip": "Aplica-se apenas à implementação padrão" + }, + "Comfy_NodeSuggestions_number": { + "name": "Número de sugestões de nós", + "tooltip": "Apenas para a caixa de busca/menu de contexto do litegraph" + }, + "Comfy_Node_AllowImageSizeDraw": { + "name": "Mostrar largura × altura abaixo da pré-visualização da imagem" + }, + "Comfy_Node_AlwaysShowAdvancedWidgets": { + "name": "Sempre mostrar widgets avançados em todos os nodes", + "tooltip": "Quando ativado, os widgets avançados ficam sempre visíveis em todos os nodes, sem a necessidade de expandi-los individualmente." + }, + "Comfy_Node_AutoSnapLinkToSlot": { + "name": "Ajustar automaticamente o link ao slot do nó", + "tooltip": "Ao arrastar um link sobre um nó, o link se ajusta automaticamente a um slot de entrada viável no nó" + }, + "Comfy_Node_BypassAllLinksOnDelete": { + "name": "Manter todos os links ao excluir nós", + "tooltip": "Ao excluir um nó, tenta reconectar todos os seus links de entrada e saída (ignorando o nó excluído)" + }, + "Comfy_Node_DoubleClickTitleToEdit": { + "name": "Clique duplo no título do nó para editar" + }, + "Comfy_Node_MiddleClickRerouteNode": { + "name": "Clique do meio cria um novo nó de redirecionamento" + }, + "Comfy_Node_Opacity": { + "name": "Opacidade do nó" + }, + "Comfy_Node_ShowDeprecated": { + "name": "Mostrar nós obsoletos na busca", + "tooltip": "Nós obsoletos são ocultados por padrão na interface, mas permanecem funcionais em fluxos de trabalho existentes que os utilizam." + }, + "Comfy_Node_ShowExperimental": { + "name": "Mostrar nós experimentais na busca", + "tooltip": "Nós experimentais são marcados como tal na interface e podem sofrer alterações significativas ou remoção em versões futuras. Use com cautela em fluxos de trabalho de produção" + }, + "Comfy_Node_SnapHighlightsNode": { + "name": "Destacar nó ao ajustar link", + "tooltip": "Ao arrastar um link sobre um nó com slot de entrada viável, destaca o nó" + }, + "Comfy_Notification_ShowVersionUpdates": { + "name": "Mostrar atualizações de versão", + "tooltip": "Mostrar atualizações para novos modelos e grandes novos recursos." + }, + "Comfy_Pointer_ClickBufferTime": { + "name": "Atraso de tolerância de clique do ponteiro", + "tooltip": "Após pressionar um botão do ponteiro, este é o tempo máximo (em milissegundos) em que o movimento do ponteiro pode ser ignorado.\n\nAjuda a evitar que objetos sejam movidos acidentalmente se o ponteiro for movido ao clicar." + }, + "Comfy_Pointer_ClickDrift": { + "name": "Tolerância de movimento do clique do ponteiro (distância máxima)", + "tooltip": "Se o ponteiro se mover mais do que esta distância enquanto um botão estiver pressionado, será considerado arrastar (em vez de clicar).\n\nAjuda a evitar que objetos sejam movidos acidentalmente se o ponteiro for movido ao clicar." + }, + "Comfy_Pointer_DoubleClickTime": { + "name": "Intervalo de duplo clique (máximo)", + "tooltip": "O tempo máximo em milissegundos entre os dois cliques de um duplo clique. Aumentar este valor pode ajudar se os duplos cliques às vezes não forem registrados." + }, + "Comfy_PreviewFormat": { + "name": "Formato da imagem de pré-visualização", + "tooltip": "Ao exibir uma pré-visualização no widget de imagem, converta para uma imagem leve, por exemplo, webp, jpeg, webp;50, etc." + }, + "Comfy_PromptFilename": { + "name": "Solicitar nome do arquivo ao salvar fluxo de trabalho" + }, + "Comfy_QueueButton_BatchCountLimit": { + "name": "Limite de quantidade por lote", + "tooltip": "O número máximo de tarefas adicionadas à fila em um único clique" + }, + "Comfy_Queue_MaxHistoryItems": { + "name": "Tamanho do histórico da fila", + "tooltip": "O número máximo de tarefas exibidas no histórico da fila." + }, + "Comfy_Queue_QPOV2": { + "name": "Usar a fila de tarefas unificada no painel lateral de Assets", + "tooltip": "Substitui o painel flutuante de fila de tarefas por uma fila de tarefas equivalente incorporada ao painel lateral de Assets. Você pode desativar isso para voltar ao layout do painel flutuante." + }, + "Comfy_Sidebar_Location": { + "name": "Localização da barra lateral", + "options": { + "left": "esquerda", + "right": "direita" + } + }, + "Comfy_Sidebar_Size": { + "name": "Tamanho da barra lateral", + "options": { + "normal": "normal", + "small": "pequena" + } + }, + "Comfy_Sidebar_Style": { + "name": "Estilo da barra lateral", + "options": { + "connected": "conectada", + "floating": "flutuante" + } + }, + "Comfy_Sidebar_UnifiedWidth": { + "name": "Largura unificada da barra lateral" + }, + "Comfy_SnapToGrid_GridSize": { + "name": "Tamanho do grid de alinhamento", + "tooltip": "Ao arrastar e redimensionar nós segurando shift, eles serão alinhados à grade. Este valor controla o tamanho dessa grade." + }, + "Comfy_TextareaWidget_FontSize": { + "name": "Tamanho da fonte do widget de área de texto" + }, + "Comfy_TextareaWidget_Spellcheck": { + "name": "Verificação ortográfica do widget de área de texto" + }, + "Comfy_TreeExplorer_ItemPadding": { + "name": "Espaçamento dos itens do explorador em árvore" + }, + "Comfy_UI_TabBarLayout": { + "name": "Layout da Barra de Abas", + "options": { + "Default": "Padrão", + "Integrated": "Integrado" + }, + "tooltip": "Controla o layout da barra de abas. \"Integrado\" move os controles de Ajuda e Usuário para a área da barra de abas." + }, + "Comfy_UseNewMenu": { + "name": "Usar novo menu", + "options": { + "Disabled": "Desativado", + "Top": "Superior" + }, + "tooltip": "Ativar a barra de menu superior redesenhada." + }, + "Comfy_Validation_Workflows": { + "name": "Validar fluxos de trabalho" + }, + "Comfy_VueNodes_AutoScaleLayout": { + "name": "Auto-escalar layout (Nodes 2.0)", + "tooltip": "Escala automaticamente as posições dos nós ao alternar para a renderização Nodes 2.0 para evitar sobreposição" + }, + "Comfy_VueNodes_Enabled": { + "name": "Design Moderno de Nós (Nodes 2.0)", + "tooltip": "Moderno: renderização baseada em DOM com interatividade aprimorada, recursos nativos do navegador e design visual atualizado. Clássico: renderização tradicional em canvas." + }, + "Comfy_WidgetControlMode": { + "name": "Modo de controle do widget", + "options": { + "after": "depois", + "before": "antes" + }, + "tooltip": "Controla quando os valores do widget são atualizados (aleatorizar/incrementar/decrementar), antes ou depois do fluxo ser adicionado à fila." + }, + "Comfy_Window_UnloadConfirmation": { + "name": "Mostrar confirmação ao fechar a janela" + }, + "Comfy_Workflow_AutoSave": { + "name": "Salvar automaticamente", + "options": { + "after delay": "após atraso", + "off": "desligado" + } + }, + "Comfy_Workflow_AutoSaveDelay": { + "name": "Atraso de Salvamento Automático (ms)", + "tooltip": "Aplica-se apenas se o Salvamento Automático estiver definido como \"após atraso\"." + }, + "Comfy_Workflow_ConfirmDelete": { + "name": "Mostrar confirmação ao excluir fluxos de trabalho" + }, + "Comfy_Workflow_Persist": { + "name": "Persistir estado do fluxo de trabalho e restaurar ao recarregar a página" + }, + "Comfy_Workflow_ShowMissingModelsWarning": { + "name": "Mostrar aviso de modelos ausentes" + }, + "Comfy_Workflow_ShowMissingNodesWarning": { + "name": "Mostrar aviso de nós ausentes" + }, + "Comfy_Workflow_SortNodeIdOnSave": { + "name": "Ordenar IDs dos nós ao salvar fluxo de trabalho" + }, + "Comfy_Workflow_WarnBlueprintOverwrite": { + "name": "Exigir confirmação para sobrescrever um blueprint de subgrafo existente" + }, + "Comfy_Workflow_WorkflowTabsPosition": { + "name": "Posição dos fluxos de trabalho abertos", + "options": { + "Sidebar": "Barra lateral", + "Topbar": "Barra superior" + } + }, + "LiteGraph_Canvas_MaximumFps": { + "name": "FPS Máximo", + "tooltip": "O número máximo de quadros por segundo que o canvas pode renderizar. Limita o uso da GPU ao custo de suavidade. Se 0, a taxa de atualização da tela é usada. Padrão: 0" + }, + "LiteGraph_Canvas_MinFontSizeForLOD": { + "name": "Zoom do Nível de Detalhe do Nó - limite de tamanho da fonte", + "tooltip": "Controla quando os nós mudam para renderização LOD de baixa qualidade. Usa o tamanho da fonte em pixels para determinar quando alternar. Defina como 0 para desativar. Valores de 1-24 definem o limite mínimo de tamanho da fonte para LOD - valores mais altos (24px) = alterna para renderização simplificada dos nós mais cedo ao afastar o zoom, valores mais baixos (1px) = mantém a qualidade total do nó por mais tempo." + }, + "LiteGraph_ContextMenu_Scaling": { + "name": "Escalar menus de combinação de nós (listas) ao dar zoom" + }, + "LiteGraph_Node_DefaultPadding": { + "name": "Sempre reduzir novos nós", + "tooltip": "Redimensiona os nós para o menor tamanho possível ao serem criados. Quando desativado, um nó recém-adicionado será ligeiramente alargado para mostrar os valores dos widgets." + }, + "LiteGraph_Node_TooltipDelay": { + "name": "Atraso da Dica de Ferramenta" + }, + "LiteGraph_Reroute_SplineOffset": { + "name": "Deslocamento da curva de redirecionamento", + "tooltip": "O deslocamento do ponto de controle bezier a partir do ponto central de redirecionamento" + }, + "pysssss_SnapToGrid": { + "name": "Sempre alinhar à grade" + } +} diff --git a/src/locales/ru/commands.json b/src/locales/ru/commands.json index 433af33fb..33959377d 100644 --- a/src/locales/ru/commands.json +++ b/src/locales/ru/commands.json @@ -1,4 +1,40 @@ { + "Comfy-Desktop_CheckForUpdates": { + "label": "Проверить обновления" + }, + "Comfy-Desktop_Folders_OpenCustomNodesFolder": { + "label": "Открыть папку пользовательских узлов" + }, + "Comfy-Desktop_Folders_OpenInputsFolder": { + "label": "Открыть папку входных данных" + }, + "Comfy-Desktop_Folders_OpenLogsFolder": { + "label": "Открыть папку логов" + }, + "Comfy-Desktop_Folders_OpenModelConfig": { + "label": "Открыть extra_model_paths.yaml" + }, + "Comfy-Desktop_Folders_OpenModelsFolder": { + "label": "Открыть папку моделей" + }, + "Comfy-Desktop_Folders_OpenOutputsFolder": { + "label": "Открыть папку выходных данных" + }, + "Comfy-Desktop_OpenDevTools": { + "label": "Открыть инструменты разработчика" + }, + "Comfy-Desktop_OpenUserGuide": { + "label": "Руководство пользователя для Desktop" + }, + "Comfy-Desktop_Quit": { + "label": "Выйти" + }, + "Comfy-Desktop_Reinstall": { + "label": "Переустановить" + }, + "Comfy-Desktop_Restart": { + "label": "Перезапустить" + }, "Comfy_3DViewer_Open3DViewer": { "label": "Открыть 3D-просмотрщик (бета) для выбранного узла" }, @@ -155,18 +191,30 @@ "Comfy_Manager_ShowUpdateAvailablePacks": { "label": "Проверить наличие обновлений" }, - "Comfy_Manager_ToggleManagerProgressDialog": { - "label": "Переключить диалоговое окно прогресса" - }, "Comfy_MaskEditor_BrushSize_Decrease": { "label": "Уменьшить размер кисти в MaskEditor" }, "Comfy_MaskEditor_BrushSize_Increase": { "label": "Увеличить размер кисти в MaskEditor" }, + "Comfy_MaskEditor_ColorPicker": { + "label": "Открыть палитру цветов в MaskEditor" + }, + "Comfy_MaskEditor_Mirror_Horizontal": { + "label": "Отразить по горизонтали в MaskEditor" + }, + "Comfy_MaskEditor_Mirror_Vertical": { + "label": "Отразить по вертикали в MaskEditor" + }, "Comfy_MaskEditor_OpenMaskEditor": { "label": "Открыть редактор масок для выбранной ноды" }, + "Comfy_MaskEditor_Rotate_Left": { + "label": "Повернуть влево в MaskEditor" + }, + "Comfy_MaskEditor_Rotate_Right": { + "label": "Повернуть вправо в MaskEditor" + }, "Comfy_Memory_UnloadModels": { "label": "Выгрузить модели" }, @@ -197,12 +245,18 @@ "Comfy_QueueSelectedOutputNodes": { "label": "Добавить выбранные выходные узлы в очередь" }, + "Comfy_Queue_ToggleOverlay": { + "label": "Показать/скрыть историю заданий" + }, "Comfy_Redo": { "label": "Повторить" }, "Comfy_RefreshNodeDefinitions": { "label": "Обновить определения нод" }, + "Comfy_RenameWorkflow": { + "label": "Переименовать рабочий процесс" + }, "Comfy_SaveWorkflow": { "label": "Сохранить рабочий процесс" }, @@ -221,6 +275,12 @@ "Comfy_ToggleHelpCenter": { "label": "Центр поддержки" }, + "Comfy_ToggleLinear": { + "label": "переключить линейный режим" + }, + "Comfy_ToggleQPOV2": { + "label": "Переключить панель очереди V2" + }, "Comfy_ToggleTheme": { "label": "Переключить тему (Тёмная/Светлая)" }, diff --git a/src/locales/ru/main.json b/src/locales/ru/main.json index dfb9ff6a0..dd536d63e 100644 --- a/src/locales/ru/main.json +++ b/src/locales/ru/main.json @@ -1,6 +1,8 @@ { "actionbar": { - "dockToTop": "Закрепить сверху" + "dockToTop": "Закрепить сверху", + "feedback": "Обратная связь", + "feedbackTooltip": "Обратная связь" }, "apiNodesCostBreakdown": { "costPerRun": "Стоимость за запуск", @@ -18,23 +20,141 @@ "assetCard": "{name} - ресурс типа {type}", "loadingAsset": "Загрузка ресурса" }, + "assetCollection": "Коллекция ассетов", "assets": "Ресурсы", "baseModels": "Базовые модели", "browseAssets": "Просмотр ресурсов", + "byType": "По типу", + "checkpoints": "Чекпойнты", + "civitaiLinkExample": "{example} {link}", + "civitaiLinkExampleStrong": "Пример:", + "civitaiLinkExampleUrl": "https://civitai.com/models/10706/luisap-z-image-and-qwen-pixel-art-refiner?modelVersionId=2225295", + "civitaiLinkLabel": "Ссылка на модель Civitai для {download}", + "civitaiLinkLabelDownload": "скачивания", + "civitaiLinkPlaceholder": "Вставьте ссылку сюда", + "confirmModelDetails": "Подтвердить детали модели", "connectionError": "Проверьте соединение и попробуйте снова", + "deletion": { + "body": "Эта модель будет навсегда удалена из вашей библиотеки.", + "complete": "{assetName} удалён.", + "failed": "Не удалось удалить {assetName}.", + "header": "Удалить эту модель?", + "inProgress": "Удаление {assetName}..." + }, + "download": { + "complete": "Загрузка завершена", + "failed": "Ошибка загрузки", + "inProgress": "Загрузка {assetName}..." + }, + "emptyImported": { + "canImport": "Пока нет импортированных моделей. Нажмите «Импортировать модель», чтобы добавить свою.", + "restricted": "Персональные модели доступны только на уровне Creator и выше." + }, + "errorFileTooLarge": "Файл превышает максимально допустимый размер", + "errorFormatNotAllowed": "Разрешён только формат SafeTensor", + "errorModelTypeNotSupported": "Этот тип модели не поддерживается", + "errorUnknown": "Произошла непредвиденная ошибка", + "errorUnsafePickleScan": "CivitAI обнаружил потенциально опасный код в этом файле", + "errorUnsafeVirusScan": "CivitAI обнаружил вредоносное ПО или подозрительное содержимое в этом файле", + "errorUploadFailed": "Не удалось импортировать ассет. Пожалуйста, попробуйте ещё раз.", "failedToCreateNode": "Не удалось создать узел. Попробуйте ещё раз или проверьте консоль для подробностей.", "fileFormats": "Форматы файлов", + "fileName": "Имя файла", + "fileSize": "Размер файла", + "filterBy": "Фильтровать по", + "findInLibrary": "Найдите это в разделе {type} библиотеки моделей.", + "finish": "Готово", + "genericLinkPlaceholder": "Вставьте ссылку сюда", + "importAnother": "Импортировать другой", + "imported": "Импортировано", + "jobId": "ID задачи", "loadingModels": "Загрузка {type}...", + "maxFileSize": "Максимальный размер файла: {size}", + "maxFileSizeValue": "1 ГБ", + "media": { + "audioPlaceholder": "Аудио", + "threeDModelPlaceholder": "3D-модель" + }, + "modelAssociatedWithLink": "Модель, связанная с предоставленной вами ссылкой:", + "modelInfo": { + "addBaseModel": "Добавить базовую модель...", + "addTag": "Добавить тег...", + "additionalTags": "Дополнительные теги", + "baseModelUnknown": "Базовая модель неизвестна", + "basicInfo": "Основная информация", + "compatibleBaseModels": "Совместимые базовые модели", + "description": "Описание", + "descriptionNotSet": "Описание не задано", + "descriptionPlaceholder": "Добавьте описание для этой модели...", + "displayName": "Отображаемое имя", + "editDisplayName": "Редактировать отображаемое имя", + "fileName": "Имя файла", + "modelDescription": "Описание модели", + "modelTagging": "Теги модели", + "modelType": "Тип модели", + "noAdditionalTags": "Нет дополнительных тегов", + "selectModelPrompt": "Выберите модель, чтобы увидеть её информацию", + "selectModelType": "Выберите тип модели...", + "source": "Источник", + "title": "Информация о модели", + "triggerPhrases": "Триггерные фразы", + "viewOnSource": "Посмотреть на {source}" + }, + "modelName": "Имя модели", + "modelNamePlaceholder": "Введите имя для этой модели", + "modelTypeSelectorLabel": "Какой это тип модели?", + "modelTypeSelectorPlaceholder": "Выберите тип модели", + "modelUploaded": "Модель успешно импортирована.", "noAssetsFound": "Ресурсы не найдены", "noModelsInFolder": "Нет {type} в этой папке", - "searchAssetsPlaceholder": "Поиск ресурсов...", + "noValidSourceDetected": "Не обнаружен действительный источник импорта", + "notSureLeaveAsIs": "Не уверены? Просто оставьте как есть", + "onlyCivitaiUrlsSupported": "Поддерживаются только ссылки Civitai", + "ownership": "Владение", + "ownershipAll": "Все", + "ownershipMyModels": "Мои модели", + "ownershipPublicModels": "Публичные модели", + "processingModel": "Загрузка начата", + "processingModelDescription": "Вы можете закрыть это окно. Загрузка продолжится в фоновом режиме.", + "providerCivitai": "Civitai", + "providerHuggingFace": "Hugging Face", + "rename": { + "failed": "Не удалось переименовать ассет." + }, + "selectFrameworks": "Выберите фреймворки", + "selectModelType": "Выберите тип модели", + "selectProjects": "Выберите проекты", "sortAZ": "А-Я", "sortBy": "Сортировать по", "sortPopular": "Популярные", "sortRecent": "Недавние", "sortZA": "Я-А", + "sortingType": "Тип сортировки", + "tags": "Теги", + "tagsHelp": "Разделяйте теги запятыми", + "tagsPlaceholder": "например, models, checkpoint", "tryAdjustingFilters": "Попробуйте изменить поиск или фильтры", - "unknown": "Неизвестно" + "unknown": "Неизвестно", + "unsupportedUrlSource": "Поддерживаются только ссылки от {sources}", + "upgradeFeatureDescription": "Эта функция доступна только в планах Creator или Pro.", + "upgradeToUnlockFeature": "Обновите тариф для доступа к этой функции", + "upload": "Импортировать", + "uploadFailed": "Импорт не удался", + "uploadModel": "Импортировать", + "uploadModelDescription1": "Вставьте ссылку на скачивание модели Civitai, чтобы добавить её в свою библиотеку.", + "uploadModelDescription1Generic": "Вставьте ссылку на скачивание модели, чтобы добавить её в свою библиотеку.", + "uploadModelDescription2": "В данный момент поддерживаются только ссылки с {link}", + "uploadModelDescription2Generic": "В данный момент поддерживаются только ссылки от следующих провайдеров:", + "uploadModelDescription2Link": "https://civitai.com/models", + "uploadModelDescription3": "Максимальный размер файла: {size}", + "uploadModelFailedToRetrieveMetadata": "Не удалось получить метаданные. Пожалуйста, проверьте ссылку и попробуйте снова.", + "uploadModelFromCivitai": "Импортировать модель из Civitai", + "uploadModelGeneric": "Импортировать модель", + "uploadModelHelpFooterText": "Нужна помощь с поиском ссылок? Нажмите на провайдера ниже, чтобы посмотреть обучающее видео.", + "uploadModelHelpVideo": "Видео-помощь по загрузке модели", + "uploadModelHowDoIFindThis": "Как это найти?", + "uploadSuccess": "Модель успешно импортирована!", + "uploadingModel": "Импорт модели..." }, "auth": { "apiKey": { @@ -148,12 +268,19 @@ "title": "Создать аккаунт" } }, + "boundingBox": { + "height": "Высота", + "width": "Ширина", + "x": "X", + "y": "Y" + }, "breadcrumbsMenu": { "clearWorkflow": "Очистить рабочий процесс", "deleteBlueprint": "Удалить схему", "deleteWorkflow": "Удалить рабочий процесс", "duplicate": "Дублировать", - "enterNewName": "Введите новое имя" + "enterNewName": "Введите новое имя", + "missingNodesWarning": "В рабочем процессе есть неподдерживаемые узлы (выделены красным)." }, "clipboard": { "errorMessage": "Не удалось скопировать в буфер обмена", @@ -207,6 +334,7 @@ }, "retry": "Попробовать снова", "retrying": "Повторная попытка...", + "skipToCloudApp": "Перейти к облачному приложению", "start": { "desc": "Не требует настройки. Работает на любом устройстве.", "download": "Скачать ComfyUI", @@ -340,6 +468,8 @@ "Edit Subgraph Widgets": "Редактировать виджеты подграфа", "Expand": "Развернуть", "Expand Node": "Развернуть ноду", + "Extensions": "Расширения", + "FavoriteWidget": "Добавить в избранное", "Horizontal": "Горизонтально", "Inputs": "Входы", "Left": "Влево", @@ -359,6 +489,7 @@ "Remove": "Удалить", "Remove Bypass": "Удалить обход", "Rename": "Переименовать", + "RenameWidget": "Переименовать виджет", "Resize": "Изменить размер", "Right": "Вправо", "Run Branch": "Запустить ветку", @@ -369,6 +500,7 @@ "Shapes": "Формы", "Title": "Заголовок", "Top": "Верх", + "UnfavoriteWidget": "Удалить из избранного", "Unpack Subgraph": "Распаковать подграф", "Unpin": "Открепить", "Vertical": "Вертикально", @@ -382,6 +514,7 @@ "additionalInfo": "Дополнительная информация", "apiPricing": "Цены на API", "credits": "Кредиты", + "creditsAvailable": "Доступно кредитов", "details": "Детали", "eventType": "Тип события", "faqs": "Часто задаваемые вопросы", @@ -390,15 +523,46 @@ "messageSupport": "Связаться с поддержкой", "model": "Модель", "purchaseCredits": "Купить кредиты", + "refreshes": "Обновляется {date}", "time": "Время", "topUp": { + "addMoreCredits": "Добавить больше кредитов", + "addMoreCreditsToRun": "Добавить больше кредитов для запуска", + "amountToPayLabel": "Сумма к оплате в долларах", + "buy": "Купить", + "buyCredits": "Перейти к оплате", "buyNow": "Купить сейчас", + "contactUs": "Свяжитесь с нами", + "creditsDescription": "Кредиты используются для запуска рабочих процессов или партнерских узлов.", + "creditsPerDollar": "кредитов за доллар", + "creditsToReceiveLabel": "Кредиты к получению", + "howManyCredits": "Сколько кредитов вы хотите добавить?", "insufficientMessage": "У вас недостаточно кредитов для запуска этого рабочего процесса.", "insufficientTitle": "Недостаточно кредитов", + "insufficientWorkflowMessage": "У вас недостаточно кредитов для запуска этого рабочего процесса.", + "maxAllowed": "Максимум {credits} кредитов.", "maxAmount": "(Макс. $1,000 USD)", + "maximumAmount": "Максимум ${amount}.", + "minRequired": "Минимум {credits} кредитов", + "minimumPurchase": "Минимум ${amount} ({credits} кредитов)", + "needMore": "Нужно больше?", + "purchaseError": "Покупка не удалась", + "purchaseErrorDetail": "Не удалось купить кредиты: {error}", "quickPurchase": "Быстрая покупка", "seeDetails": "Смотреть детали", - "topUp": "Пополнить" + "selectAmount": "Выберите сумму", + "templateNote": "*Сгенерировано с помощью шаблона Wan Fun Control", + "topUp": "Пополнить", + "unknownError": "Произошла неизвестная ошибка", + "usdAmount": "${amount}", + "videosEstimate": "~{count} видео", + "viewPricing": "Посмотреть детали цен", + "youGet": "Кредиты", + "youPay": "Сумма (USD)" + }, + "unified": { + "message": "Кредиты были объединены", + "tooltip": "Мы объединили платежи в Comfy. Теперь всё работает на Comfy Credits:\n- Партнерские узлы (ранее API-узлы)\n- Облачные рабочие процессы\n\nВаш существующий баланс партнерских узлов был преобразован в кредиты." }, "yourCreditBalance": "Ваш баланс кредитов" }, @@ -414,6 +578,9 @@ "CLIP_VISION": "CLIP_VISION", "CLIP_VISION_OUTPUT": "CLIP_VISION_OUTPUT", "COMBO": "КОМБО", + "COMFY_AUTOGROW_V3": "COMFY_AUTOGROW_V3", + "COMFY_DYNAMICCOMBO_V3": "COMFY_DYNAMICCOMBO_V3", + "COMFY_MATCHTYPE_V3": "COMFY_MATCHTYPE_V3", "CONDITIONING": "КОНДИЦИОНИРОВАНИЕ", "CONTROL_NET": "CONTROL_NET", "FLOAT": "ПЛАВАЮЩИЙ", @@ -424,18 +591,21 @@ "HOOKS": "ХУКИ", "HOOK_KEYFRAMES": "КЛЮЧЕВЫЕ_КАДРЫ_ХУКА", "IMAGE": "ИЗОБРАЖЕНИЕ", + "IMAGECOMPARE": "СРАВНЕНИЕ_ИЗОБРАЖЕНИЙ", "INT": "ЦЕЛОЕ", "LATENT": "ЛАТЕНТНЫЙ", "LATENT_OPERATION": "ЛАТЕНТНАЯ_ОПЕРАЦИЯ", + "LATENT_UPSCALE_MODEL": "LATENT_UPSCALE_MODEL", "LOAD3D_CAMERA": "ЗАГРУЗИТЬ3D_КАМЕРУ", "LOAD_3D": "ЗАГРУЗИТЬ_3D", - "LOAD_3D_ANIMATION": "ЗАГРУЗИТЬ_3D_АНИМАЦИЮ", "LORA_MODEL": "МОДЕЛЬ_LORA", "LOSS_MAP": "КАРТА_ПОТЕРЬ", "LUMA_CONCEPTS": "LUMA_CONCEPTS", "LUMA_REF": "LUMA_REF", "MASK": "МАСКА", "MESH": "СЕТКА", + "MESHY_RIGGED_TASK_ID": "MESHY_RIGGED_TASK_ID", + "MESHY_TASK_ID": "MESHY_TASK_ID", "MODEL": "МОДЕЛЬ", "MODEL_PATCH": "ПАТЧ_МОДЕЛИ", "MODEL_TASK_ID": "ID_ЗАДАЧИ_МОДЕЛИ", @@ -455,6 +625,7 @@ "STYLE_MODEL": "МОДЕЛЬ_СТИЛЯ", "SVG": "SVG", "TIMESTEPS_RANGE": "ДИАПАЗОН_ВРЕМЕННЫХ_ШАГОВ", + "TRACKS": "TRACKS", "UPSCALE_MODEL": "МОДЕЛЬ_АПСКЕЙЛА", "VAE": "VAE", "VIDEO": "ВИДЕО", @@ -523,14 +694,17 @@ "amount": "Количество", "apply": "Применить", "architecture": "Архитектура", + "asset": "{count} ресурсов | {count} ресурс | {count} ресурса", "audioFailedToLoad": "Не удалось загрузить аудио", "audioProgress": "Прогресс аудио", "author": "Автор", "back": "Назад", + "batchRename": "Пакетное переименование", "beta": "БЕТА", "bookmark": "Сохранить в библиотеку", "calculatingDimensions": "Расчёт размеров", "cancel": "Отмена", + "cancelled": "Отменено", "capture": "захват", "category": "Категория", "chart": "Диаграмма", @@ -540,6 +714,7 @@ "clearAll": "Очистить всё", "clearFilters": "Сбросить фильтры", "close": "Закрыть", + "closeDialog": "Закрыть диалог", "color": "Цвет", "comfy": "Comfy", "comfyOrgLogoAlt": "Логотип ComfyOrg", @@ -556,13 +731,17 @@ "control_before_generate": "управление до генерации", "copied": "Скопировано", "copy": "Копировать", + "copyAll": "Скопировать всё", "copyJobId": "Копировать ID задания", "copyToClipboard": "Скопировать в буфер обмена", "copyURL": "Скопировать URL", + "core": "Ядро", "currentUser": "Текущий пользователь", + "custom": "Пользовательское", "customBackground": "Пользовательский фон", "customize": "Настроить", "customizeFolder": "Настроить папку", + "decrement": "Уменьшить", "defaultBanner": "баннер по умолчанию", "delete": "Удалить", "deleteAudioFile": "Удалить аудиофайл", @@ -571,27 +750,35 @@ "description": "Описание", "devices": "Устройства", "disableAll": "Отключить все", + "disableSelected": "Отключить выбранное", + "disableThirdParty": "Отключить сторонние", "disabling": "Отключение", "dismiss": "Закрыть", "download": "Скачать", "downloadImage": "Скачать изображение", "downloadVideo": "Скачать видео", + "downloading": "Загрузка", "dropYourFileOr": "Перетащите ваш файл или", "duplicate": "Дублировать", "edit": "Редактировать", "editImage": "Редактировать изображение", "editOrMaskImage": "Редактировать или маскировать изображение", + "emDash": "—", "empty": "Пусто", "enableAll": "Включить все", "enableOrDisablePack": "Включить или отключить пакет", + "enableSelected": "Включить выбранное", "enabled": "Включено", "enabling": "Включение", + "enterBaseName": "Введите базовое имя", + "enterNewName": "Введите новое имя", "error": "Ошибка", "errorLoadingImage": "Ошибка загрузки изображения", "errorLoadingVideo": "Ошибка загрузки видео", "experimental": "БЕТА", "export": "Экспорт", "extensionName": "Название расширения", + "failed": "Ошибка", "failedToCopyJobId": "Не удалось скопировать ID задания", "failedToDownloadImage": "Не удалось скачать изображение", "failedToDownloadVideo": "Не удалось скачать видео", @@ -607,12 +794,15 @@ "goToNode": "Перейти к ноде", "graphNavigation": "Навигация по графу", "halfSpeed": "0.5x", + "hideLeftPanel": "Скрыть левую панель", + "hideRightPanel": "Скрыть правую панель", "icon": "Иконка", "imageFailedToLoad": "Не удалось загрузить изображение", "imagePreview": "Предварительный просмотр изображения - Используйте клавиши со стрелками для навигации между изображениями", "imageUrl": "URL изображения", "import": "Импорт", "inProgress": "В процессе", + "increment": "Увеличить", "info": "Информация о ноде", "insert": "Вставить", "install": "Установить", @@ -620,7 +810,9 @@ "installing": "Установка", "interrupted": "Прервано", "itemSelected": "Выбран {selectedCount} элемент", + "itemsCopiedToClipboard": "Элементы скопированы в буфер обмена", "itemsSelected": "Выбрано {selectedCount} элементов", + "job": "Задача", "jobIdCopied": "ID задания скопирован в буфер обмена", "keybinding": "Привязка клавиш", "keybindingAlreadyExists": "Горячая клавиша уже существует", @@ -638,14 +830,18 @@ "micPermissionDenied": "Доступ к микрофону запрещён", "migrate": "Мигрировать", "missing": "Отсутствует", + "more": "Больше", "moreOptions": "Больше опций", "moreWorkflows": "Больше рабочих процессов", "multiSelectDropdown": "Выпадающий список множественного выбора", "name": "Имя", "newFolder": "Новая папка", "next": "Далее", + "nightly": "NIGHTLY", "no": "Нет", "noAudioRecorded": "Аудио не записано", + "noItems": "Нет элементов", + "noResults": "Нет результатов", "noResultsFound": "Результатов не найдено", "noTasksFound": "Задачи не найдены", "noTasksFoundMessage": "В очереди нет задач.", @@ -656,26 +852,45 @@ "nodeSlotsError": "Ошибка слотов ноды", "nodeWidgetsError": "Ошибка виджетов ноды", "nodes": "Узлы", + "nodesCount": "{count} узлов | {count} узел | {count} узла", "nodesRunning": "запущено узлов", "none": "Нет", + "nothingToCopy": "Нечего копировать", + "nothingToDelete": "Нечего удалять", + "nothingToDuplicate": "Нечего дублировать", + "nothingToRename": "Нечего переименовывать", "ok": "ОК", "openManager": "Открыть менеджер", "openNewIssue": "Открыть новую проблему", + "or": "или", "overwrite": "Перезаписать", + "playPause": "Воспроизвести/Пауза", "playRecording": "Воспроизвести запись", "playbackSpeed": "Скорость воспроизведения", "playing": "Воспроизводится", "pressKeysForNewBinding": "Нажмите клавиши для новой привязки", "preview": "ПРЕДПРОСМОТР", + "profile": "Профиль", "progressCountOf": "из", + "queued": "В очереди", "ready": "Готов", "reconnected": "Переподключено", "reconnecting": "Переподключение", "refresh": "Обновить", "refreshNode": "Обновить ноду", + "relativeTime": { + "daysAgo": "{count} дн. назад", + "hoursAgo": "{count} ч назад", + "minutesAgo": "{count} мин назад", + "monthsAgo": "{count} мес. назад", + "now": "сейчас", + "weeksAgo": "{count} нед. назад", + "yearsAgo": "{count} г. назад" + }, "releaseTitle": "Релиз {package} {version}", "reloadToApplyChanges": "Перезагрузите, чтобы применить изменения", "removeImage": "Удалить изображение", + "removeTag": "Удалить тег", "removeVideo": "Удалить видео", "rename": "Переименовать", "reportIssue": "Отправить отчёт", @@ -690,21 +905,31 @@ "resizeFromTopRight": "Изменять размер из правого верхнего угла", "restart": "Перезапустить", "resultsCount": "Найдено {count} результатов", + "running": "Выполняется", "save": "Сохранить", "saving": "Сохранение", + "scrollLeft": "Прокрутить влево", + "scrollRight": "Прокрутить вправо", "search": "Поиск", "searchExtensions": "Поиск расширений", "searchFailedMessage": "Мы не смогли найти настройки, соответствующие вашему запросу. Попробуйте изменить поисковые термины.", "searchKeybindings": "Поиск сочетаний клавиш", "searchModels": "Поиск моделей", "searchNodes": "Поиск нод", + "searchPlaceholder": "Поиск...", "searchSettings": "Поиск настроек", "searchWorkflows": "Поиск рабочих процессов", "seeTutorial": "Посмотреть руководство", + "selectItemsToCopy": "Выберите элементы для копирования", + "selectItemsToDelete": "Выберите элементы для удаления", + "selectItemsToDuplicate": "Выберите элементы для дублирования", + "selectItemsToRename": "Выберите элементы для переименования", "selectedFile": "Выбранный файл", "setAsBackground": "Установить как фон", "settings": "Настройки", + "showLeftPanel": "Показать левую панель", "showReport": "Показать отчёт", + "showRightPanel": "Показать правую панель", "singleSelectDropdown": "Выпадающий список единичного выбора", "sort": "Сортировать", "source": "Источник", @@ -712,12 +937,14 @@ "status": "Статус", "stopPlayback": "Остановить воспроизведение", "stopRecording": "Остановить запись", + "submit": "Отправить", "success": "Успех", "systemInfo": "Информация о системе", "terminal": "Терминал", "title": "Заголовок", "triggerPhrase": "Триггерная фраза", "unknownError": "Неизвестная ошибка", + "untitled": "Без названия", "update": "Обновить", "updateAvailable": "Доступно обновление", "updateFrontend": "Обновить интерфейс", @@ -725,6 +952,7 @@ "updating": "Обновление", "upload": "Загрузить", "usageHint": "Подсказка по использованию", + "use": "Использовать", "user": "Пользователь", "versionMismatchWarning": "Предупреждение о несовместимости версий", "versionMismatchWarningMessage": "{warning}: {detail} Посетите https://docs.comfy.org/installation/update_comfyui#common-update-issues для инструкций по обновлению.", @@ -732,11 +960,10 @@ "videoPreview": "Предварительный просмотр видео - Используйте клавиши со стрелками для навигации между видео", "viewImageOfTotal": "Просмотр изображения {index} из {total}", "viewVideoOfTotal": "Просмотр видео {index} из {total}", - "vitePreloadErrorMessage": "Вышла новая версия приложения. Хотите перезагрузить?\nЕсли нет, некоторые части приложения могут работать некорректно.\nВы можете отказаться и сохранить свой прогресс перед перезагрузкой.", - "vitePreloadErrorTitle": "Доступна новая версия", "volume": "Громкость", "warning": "Предупреждение", - "workflow": "Рабочий процесс" + "workflow": "Рабочий процесс", + "you": "Вы" }, "graphCanvasMenu": { "fitView": "Подгонять под выделенные", @@ -758,12 +985,17 @@ "create": "Создать ноду группы", "enterName": "Введите название" }, + "help": { + "helpCenterMenu": "Меню справочного центра", + "recentReleases": "Недавние релизы" + }, "helpCenter": { "clickToLearnMore": "Нажмите, чтобы узнать больше →", "desktopUserGuide": "Руководство пользователя для Desktop", "docs": "Документация", + "feedback": "Оставить отзыв", "github": "Github", - "helpFeedback": "Помощь и обратная связь", + "help": "Помощь и поддержка", "loadingReleases": "Загрузка релизов...", "managerExtension": "Расширение менеджера", "more": "Ещё...", @@ -772,6 +1004,12 @@ "recentReleases": "Последние релизы", "reinstall": "Переустановить", "updateAvailable": "Обновить", + "updateComfyUI": "Обновить ComfyUI", + "updateComfyUIFailed": "Не удалось обновить ComfyUI. Пожалуйста, попробуйте снова.", + "updateComfyUIStarted": "Обновление начато", + "updateComfyUIStartedDetail": "Обновление ComfyUI поставлено в очередь. Пожалуйста, подождите...", + "updateComfyUISuccess": "Обновление завершено", + "updateComfyUISuccessDetail": "ComfyUI обновлён. Перезагрузка...", "whatsNew": "Что нового?" }, "icon": { @@ -785,6 +1023,18 @@ "inbox": "Входящие", "star": "Звезда" }, + "imageCompare": { + "noImages": "Нет изображений для сравнения" + }, + "imageCrop": { + "cropPreviewAlt": "Предпросмотр обрезки", + "loading": "Загрузка...", + "noInputImage": "Входное изображение не подключено" + }, + "importFailed": { + "copyError": "Ошибка копирования", + "title": "Ошибка импорта" + }, "install": { "appDataLocationTooltip": "Директория данных приложения ComfyUI. Хранит:\n- Логи\n- Конфигурации сервера", "appPathLocationTooltip": "Директория активов приложения ComfyUI. Хранит код и активы ComfyUI", @@ -798,6 +1048,8 @@ "failedToSelectDirectory": "Не удалось выбрать директорию", "gpu": "GPU", "gpuPicker": { + "amdDescription": "Используйте вашу видеокарту AMD с ускорением ROCm™ для наилучшей производительности.", + "amdSubtitle": "AMD ROCm™", "appleMetalDescription": "Использует GPU вашего Mac для более высокой скорости и улучшенного общего опыта", "cpuDescription": "Используйте режим CPU для совместимости, когда ускорение GPU недоступно", "cpuSubtitle": "Режим CPU", @@ -824,6 +1076,8 @@ "selectGpuDescription": "Выберите тип GPU, который у вас есть" }, "helpImprove": "Пожалуйста, помогите улучшить ComfyUI", + "insideAppInstallDir": "Эта папка находится внутри пакета приложения ComfyUI Desktop и будет удалена при обновлении. Выберите каталог вне папки установки, например, Документы/ComfyUI.", + "insideUpdaterCache": "Эта папка находится в кэше обновлений ComfyUI, который очищается при каждом обновлении. Выберите другое место для ваших данных.", "installLocation": "Место установки", "installLocationDescription": "Выберите директорию для пользовательских данных ComfyUI. В выбранном месте будет установлена среда Python. Пожалуйста, убедитесь, что на выбранном диске достаточно места (~15 ГБ).", "installLocationTooltip": "Директория пользовательских данных ComfyUI. Хранит:\n- Среда Python\n- Модели\n- Пользовательские ноды\n", @@ -898,6 +1152,16 @@ "issueReport": { "helpFix": "Помочь исправить это" }, + "linearMode": { + "beta": "Бета - Оставить отзыв", + "downloadAll": "Скачать всё", + "dragAndDropImage": "Перетащите изображение", + "graphMode": "Графовый режим", + "linearMode": "Простой режим", + "rerun": "Перезапустить", + "reuseParameters": "Повторно использовать параметры", + "runCount": "Количество запусков:" + }, "load3d": { "applyingTexture": "Применение текстуры...", "backgroundColor": "Цвет фона", @@ -924,20 +1188,24 @@ "lineart": "Линейный арт", "normal": "Нормальный", "original": "Оригинал", + "pointCloud": "Облако точек", "wireframe": "Каркас" }, "model": "Модель", "openIn3DViewer": "Открыть в 3D просмотрщике", + "panoramaMode": "Панорама", "previewOutput": "Предварительный просмотр", "reloadingModel": "Перезагрузка модели...", "removeBackgroundImage": "Удалить фоновое изображение", "resizeNodeMatchOutput": "Изменить размер узла под вывод", "scene": "Сцена", "showGrid": "Показать сетку", + "showSkeleton": "Показать скелет", "startRecording": "Начать запись", "stopRecording": "Остановить запись", "switchCamera": "Переключить камеру", "switchingMaterialMode": "Переключение режима материала...", + "tiledMode": "Плиточный", "unsupportedFileType": "Неподдерживаемый тип файла (поддерживаются .gltf, .glb, .obj, .fbx, .stl)", "upDirection": "Направление Вверх", "upDirections": { @@ -958,6 +1226,11 @@ "title": "3D Просмотрщик (Бета)" } }, + "loadWorkflowWarning": { + "coreNodesFromVersion": "Базовые узлы из версии {version}:", + "outdatedVersion": "Этот рабочий процесс был создан в более новой версии ComfyUI ({version}). Некоторые узлы могут работать некорректно.", + "outdatedVersionGeneric": "Этот рабочий процесс был создан в более новой версии ComfyUI. Некоторые узлы могут работать некорректно." + }, "maintenance": { "None": "Нет", "OK": "OK", @@ -976,7 +1249,15 @@ "showManual": "Показать задачи по обслуживанию", "status": "Статус", "terminalDefaultMessage": "Когда вы запускаете команду для устранения неполадок, любой вывод будет отображаться здесь.", - "title": "Обслуживание" + "title": "Обслуживание", + "unsafeMigration": { + "action": "Используйте задачу обслуживания «Базовый путь» ниже, чтобы переместить ComfyUI в безопасное место.", + "appInstallDir": "Ваш базовый путь находится внутри пакета приложения ComfyUI Desktop. Эта папка может быть удалена или перезаписана при обновлениях. Выберите каталог вне папки установки, например, Документы/ComfyUI.", + "generic": "Ваш текущий базовый путь ComfyUI находится в месте, которое может быть удалено или изменено при обновлениях. Чтобы избежать потери данных, переместите его в безопасную папку.", + "oneDrive": "Ваш базовый путь находится на OneDrive, что может привести к проблемам синхронизации и случайной потере данных. Выберите локальную папку, не управляемую OneDrive.", + "title": "Обнаружено небезопасное место установки", + "updaterCache": "Ваш базовый путь находится в кэше обновлений ComfyUI, который очищается при каждом обновлении. Выберите другое место для хранения данных." + } }, "manager": { "allMissingNodesInstalled": "Все отсутствующие ноды успешно установлены", @@ -1077,6 +1358,8 @@ "totalNodes": "Всего Узлов", "tryAgainLater": "Пожалуйста, попробуйте позже.", "tryDifferentSearch": "Пожалуйста, попробуйте изменить запрос.", + "tryUpdate": "Попробовать обновить", + "tryUpdateTooltip": "Получить последние изменения из репозитория. В ночных версиях могут быть обновления, которые не определяются автоматически.", "uninstall": "Удалить", "uninstallSelected": "Удалить выбранное", "uninstalling": "Удаление", @@ -1087,31 +1370,110 @@ "version": "Версия" }, "maskEditor": { + "activateLayer": "Активировать слой", + "applyToWholeImage": "Применить ко всему изображению", + "baseImageLayer": "Базовый слой изображения", + "baseLayerPreview": "Предпросмотр базового слоя", + "black": "Чёрный", + "brushSettings": "Настройки кисти", + "brushShape": "Форма кисти", + "clear": "Очистить", + "clickToResetZoom": "Кликните, чтобы сбросить масштаб", + "colorSelectSettings": "Настройки выбора цвета", + "colorSelector": "Выбор цвета", + "fillOpacity": "Непрозрачность заливки", + "hardness": "Жёсткость", + "imageLayer": "Слой изображения", + "invert": "Инвертировать", + "layers": "Слои", + "livePreview": "Живой предпросмотр", + "maskBlendingOptions": "Параметры смешивания маски", + "maskLayer": "Слой маски", + "maskOpacity": "Непрозрачность маски", + "maskTolerance": "Допуск маски", + "method": "Метод", + "mirrorHorizontal": "Отразить по горизонтали", + "mirrorVertical": "Отразить по вертикали", + "negative": "Негатив", + "opacity": "Непрозрачность", + "paintBucketSettings": "Настройки заливки", + "paintLayer": "Слой рисования", + "redo": "Повторить", + "resetToDefault": "Сбросить по умолчанию", + "rotateLeft": "Повернуть влево", + "rotateRight": "Повернуть вправо", + "selectionOpacity": "Непрозрачность выделения", + "smoothingPrecision": "Точность сглаживания", + "stepSize": "Размер шага", + "stopAtMask": "Остановить на маске", + "thickness": "Толщина", + "title": "Редактор масок", + "tolerance": "Допуск", + "undo": "Отменить", + "white": "Белый" }, "mediaAsset": { + "actions": { + "copyJobId": "Скопировать ID задания", + "delete": "Удалить", + "download": "Скачать", + "exportWorkflow": "Экспортировать рабочий процесс", + "insertAsNodeInWorkflow": "Вставить как узел в рабочий процесс", + "inspect": "Просмотреть ресурс", + "more": "Больше опций", + "moreOptions": "Больше опций", + "openWorkflow": "Открыть как рабочий процесс в новой вкладке", + "seeMoreOutputs": "Показать больше результатов", + "zoom": "Увеличить" + }, "assetDeletedSuccessfully": "Ресурс успешно удален", "deleteAssetDescription": "Этот ресурс будет удален безвозвратно.", "deleteAssetTitle": "Удалить этот ресурс?", "deleteSelectedDescription": "{count} ресурс(ов) будет удален безвозвратно.", "deleteSelectedTitle": "Удалить выбранные ресурсы?", "deletingImportedFilesCloudOnly": "Удаление импортированных файлов поддерживается только в облачной версии", + "failedToCreateNode": "Не удалось создать узел", "failedToDeleteAsset": "Не удалось удалить ресурс", + "failedToExportWorkflow": "Не удалось экспортировать рабочий процесс", "jobIdToast": { "copied": "Скопировано", "error": "Ошибка", "jobIdCopied": "ID задания скопирован в буфер обмена", "jobIdCopyFailed": "Не удалось скопировать ID задания" }, + "noJobIdFound": "ID задания для этого ресурса не найден", + "noWorkflowDataFound": "В этом ресурсе не найдено данных рабочего процесса", + "nodeAddedToWorkflow": "Узел {nodeType} добавлен в рабочий процесс", + "nodeTypeNotFound": "Тип узла {nodeType} не найден", "selection": { "assetsDeletedSuccessfully": "{count} ресурс(ов) успешно удалено", "deleteSelected": "Удалить", + "deleteSelectedAll": "Удалить все", "deselectAll": "Снять выделение со всех", "downloadSelected": "Скачать", + "downloadSelectedAll": "Скачать все", "downloadStarted": "Скачивание {count} файлов...", "downloadsStarted": "Начато скачивание {count} файла(ов)", + "exportWorkflowAll": "Экспортировать все рабочие процессы", + "failedToAddNodes": "Не удалось добавить узлы в рабочий процесс", "failedToDeleteAssets": "Не удалось удалить выбранные ресурсы", - "selectedCount": "Выбрано ресурсов: {count}" - } + "insertAllAssetsAsNodes": "Вставить все ресурсы как узлы", + "multipleSelectedAssets": "Выбрано несколько ресурсов", + "noWorkflowsFound": "В выбранных ресурсах не найдено данных рабочих процессов", + "noWorkflowsToExport": "Нет данных рабочих процессов для экспорта", + "nodesAddedToWorkflow": "{count} узел(ов) добавлено в рабочий процесс", + "openWorkflowAll": "Открыть все рабочие процессы", + "partialAddNodesSuccess": "{succeeded} успешно добавлено, {failed} не удалось", + "partialDeleteSuccess": "{succeeded} успешно удалено, {failed} не удалось удалить", + "partialWorkflowsExported": "{succeeded} успешно экспортировано, {failed} не удалось", + "partialWorkflowsOpened": "{succeeded} рабочий(их) процесс(ов) открыт(ы), {failed} не удалось", + "selectedCount": "Выбрано ресурсов: {count}", + "workflowsExported": "{count} рабочий(их) процесс(ов) успешно экспортировано", + "workflowsOpened": "{count} рабочий(их) процесс(ов) открыт(ы) в новых вкладках" + }, + "unsupportedFileType": "Неподдерживаемый тип файла для узла загрузки", + "workflowExportedSuccessfully": "Рабочий процесс успешно экспортирован", + "workflowOpenedInNewTab": "Рабочий процесс открыт в новой вкладке" }, "menu": { "autoQueue": "Автоочередь", @@ -1119,11 +1481,13 @@ "batchCountTooltip": "Количество раз, когда генерация рабочего процесса должна быть помещена в очередь", "clear": "Очистить рабочий процесс", "clipspace": "Открыть Clipspace", + "customNodesManager": "Менеджер пользовательских узлов", "dark": "Тёмная", "disabled": "Отключено", "disabledTooltip": "Рабочий процесс не будет автоматически помещён в очередь", "execute": "Выполнить", "help": "Справка", + "helpAndFeedback": "Помощь и обратная связь", "hideMenu": "Скрыть меню", "instant": "Мгновенно", "instantTooltip": "Рабочий процесс будет помещён в очередь сразу же после завершения генерации", @@ -1137,6 +1501,7 @@ "resetView": "Сбросить вид холста", "run": "Запустить", "runWorkflow": "Запустить рабочий процесс (Shift для очереди в начале)", + "runWorkflowDisabled": "В рабочем процессе есть неподдерживаемые узлы (выделены красным). Удалите их, чтобы запустить рабочий процесс.", "runWorkflowFront": "Запустить рабочий процесс (Очередь в начале)", "settings": "Настройки", "showMenu": "Показать меню", @@ -1152,6 +1517,7 @@ "Canvas Performance": "Производительность холста", "Canvas Toggle Lock": "Переключение блокировки холста", "Check for Custom Node Updates": "Проверить обновления пользовательских узлов", + "Check for Updates": "Проверить обновления", "Clear Pending Tasks": "Очистить ожидающие задачи", "Clear Workflow": "Очистить рабочий процесс", "Clipspace": "Клиппространство", @@ -1168,13 +1534,14 @@ "Custom Nodes Manager": "Менеджер Пользовательских Узлов", "Decrease Brush Size in MaskEditor": "Уменьшить размер кисти в MaskEditor", "Delete Selected Items": "Удалить выбранные элементы", + "Desktop User Guide": "Руководство пользователя для Desktop", "Duplicate Current Workflow": "Дублировать текущий рабочий процесс", "Edit": "Редактировать", "Edit Subgraph Widgets": "Редактировать виджеты подграфа", "Exit Subgraph": "Выйти из подграфа", "Experimental: Browse Model Assets": "Экспериментально: Просмотр ассетов моделей", "Experimental: Enable AssetAPI": "Экспериментально: Включить AssetAPI", - "Experimental: Enable Vue Nodes": "Экспериментально: Включить Vue узлы", + "Experimental: Enable Nodes 2_0": "Экспериментально: Включить Nodes 2.0", "Export": "Экспортировать", "Export (API)": "Экспорт (API)", "File": "Файл", @@ -1186,12 +1553,15 @@ "Increase Brush Size in MaskEditor": "Увеличить размер кисти в MaskEditor", "Install Missing Custom Nodes": "Установить отсутствующие пользовательские узлы", "Interrupt": "Прервать", + "Job History": "История заданий", "Load Default Workflow": "Загрузить стандартный рабочий процесс", "Lock Canvas": "Заблокировать холст", "Manage group nodes": "Управление групповыми нодами", "Manager": "Менеджер", "Manager Menu (Legacy)": "Меню управления (устаревшее)", "Minimap": "Мини-карта", + "Mirror Horizontal in MaskEditor": "Отразить по горизонтали в MaskEditor", + "Mirror Vertical in MaskEditor": "Отразить по вертикали в MaskEditor", "Model Library": "Библиотека моделей", "Move Selected Nodes Down": "Переместить выбранные узлы вниз", "Move Selected Nodes Left": "Переместить выбранные узлы влево", @@ -1204,8 +1574,16 @@ "Node Links": "Связи узлов", "Open": "Открыть", "Open 3D Viewer (Beta) for Selected Node": "Открыть 3D просмотрщик (Бета) для выбранного узла", + "Open Color Picker in MaskEditor": "Открыть палитру цветов в MaskEditor", + "Open Custom Nodes Folder": "Открыть папку пользовательских узлов", + "Open DevTools": "Открыть инструменты разработчика", + "Open Inputs Folder": "Открыть папку входных данных", + "Open Logs Folder": "Открыть папку журналов", "Open Mask Editor for Selected Node": "Открыть редактор масок для выбранного узла", + "Open Models Folder": "Открыть папку моделей", + "Open Outputs Folder": "Открыть папку выходных данных", "Open Sign In Dialog": "Открыть окно входа", + "Open extra_model_paths_yaml": "Открыть extra_model_paths.yaml", "Pin/Unpin Selected Items": "Закрепить/открепить выбранные элементы", "Pin/Unpin Selected Nodes": "Закрепить/открепить выбранные ноды", "Previous Opened Workflow": "Предыдущий открытый рабочий процесс", @@ -1213,10 +1591,16 @@ "Queue Prompt": "Запрос в очереди", "Queue Prompt (Front)": "Запрос в очереди (спереди)", "Queue Selected Output Nodes": "Добавить выбранные выходные узлы в очередь", + "Quit": "Выйти", "Redo": "Повторить", "Refresh Node Definitions": "Обновить определения нод", + "Reinstall": "Переустановить", + "Rename": "Переименовать", "Reset View": "Сбросить вид", "Resize Selected Nodes": "Изменить размер выбранных узлов", + "Restart": "Перезапустить", + "Rotate Left in MaskEditor": "Повернуть влево в MaskEditor", + "Rotate Right in MaskEditor": "Повернуть вправо в MaskEditor", "Save": "Сохранить", "Save As": "Сохранить как", "Show Keybindings Dialog": "Показать диалог клавиш быстрого доступа", @@ -1225,12 +1609,13 @@ "Sign Out": "Выйти", "Toggle Essential Bottom Panel": "Показать/скрыть нижнюю панель основных элементов", "Toggle Logs Bottom Panel": "Показать/скрыть нижнюю панель логов", + "Toggle Queue Panel V2": "Переключить панель очереди V2", "Toggle Search Box": "Переключить поисковую панель", + "Toggle Simple Mode": "Переключить простой режим", "Toggle Terminal Bottom Panel": "Показать/скрыть нижнюю панель терминала", "Toggle Theme (Dark/Light)": "Переключение темы (Тёмная/Светлая)", "Toggle View Controls Bottom Panel": "Показать/скрыть нижнюю панель элементов управления", "Toggle promotion of hovered widget": "Переключить повышение выделенного виджета", - "Toggle the Custom Nodes Manager Progress Bar": "Переключить индикатор выполнения менеджера пользовательских узлов", "Undo": "Отменить", "Ungroup selected group nodes": "Разгруппировать выбранные групповые ноды", "Unload Models": "Выгрузить модели", @@ -1255,30 +1640,56 @@ "missingModels": "Отсутствующие модели", "missingModelsMessage": "При загрузке графа следующие модели не были найдены" }, + "missingNodes": { + "cloud": { + "description": "В этом рабочем процессе используются пользовательские узлы, которые пока не поддерживаются в облачной версии.", + "gotIt": "Понятно", + "learnMore": "Подробнее", + "priorityMessage": "Мы автоматически отметили эти узлы, чтобы ускорить их добавление.", + "replacementInstruction": "Пока что замените эти узлы (выделены красным на холсте) на поддерживаемые, если возможно, или попробуйте другой рабочий процесс.", + "title": "Эти узлы пока недоступны в Comfy Cloud" + }, + "oss": { + "description": "В этом рабочем процессе используются пользовательские узлы, которые вы ещё не установили.", + "replacementInstruction": "Установите эти узлы для запуска рабочего процесса или замените их на уже установленные аналоги. Отсутствующие узлы выделены красным на холсте.", + "title": "В этом рабочем процессе отсутствуют узлы" + } + }, + "nightly": { + "badge": { + "label": "Предварительная версия", + "tooltip": "Вы используете ночную версию ComfyUI. Пожалуйста, используйте кнопку обратной связи, чтобы поделиться своим мнением об этих функциях." + } + }, "nodeCategories": { + "": "", "3d": "3d", "3d_models": "3d_модели", "BFL": "BFL", + "Bria": "Bria", "ByteDance": "ByteDance", "Gemini": "Gemini", "Ideogram": "Ideogram", "Kling": "Kling", "LTXV": "LTXV", "Luma": "Luma", + "Meshy": "Meshy", "MiniMax": "MiniMax", "Moonvalley Marey": "Moonvalley Marey", "OpenAI": "OpenAI", - "Pika": "Pika", "PixVerse": "PixVerse", "Recraft": "Recraft", "Rodin": "Rodin", "Runway": "Runway", "Sora": "Sora", "Stability AI": "Stability AI", + "Tencent": "Tencent", + "Topaz": "Topaz", "Tripo": "Tripo", "Veo": "Veo", "Vidu": "Vidu", "Wan": "Wan", + "WaveSpeed": "WaveSpeed", "_for_testing": "_для_тестирования", "advanced": "расширенный", "animation": "анимация", @@ -1299,6 +1710,7 @@ "controlnet": "controlnet", "create": "создать", "custom_sampling": "пользовательский_семплинг", + "dataset": "dataset", "debug": "отладка", "deprecated": "устаревший", "edit_models": "редактировать_модели", @@ -1310,8 +1722,10 @@ "image": "изображение", "inpaint": "восстановление", "instructpix2pix": "instructpix2pix", + "kandinsky5": "kandinsky5", "latent": "латентный", "loaders": "загрузчики", + "logic": "логика", "lotus": "lotus", "ltxv": "ltxv", "mask": "маска", @@ -1345,7 +1759,15 @@ "upscaling": "апскейл", "utils": "утилиты", "video": "видео", - "video_models": "видеомодели" + "video_models": "видеомодели", + "zimage": "zimage" + }, + "nodeErrors": { + "content": "Ошибка содержимого узла", + "header": "Ошибка заголовка узла", + "render": "Ошибка рендеринга узла", + "slots": "Ошибка слотов узла", + "widgets": "Ошибка виджетов узла" }, "nodeHelpPage": { "documentationPage": "страницу документации", @@ -1362,6 +1784,7 @@ "notSupported": { "continue": "Продолжить", "continueTooltip": "Я уверен, что моё устройство поддерживается", + "illustrationAlt": "Иллюстрация грустной девушки", "learnMore": "Узнать больше", "message": "Поддерживаются только следующие устройства:", "reportIssue": "Сообщить о проблеме", @@ -1371,12 +1794,136 @@ }, "title": "Ваше устройство не поддерживается" }, + "progressToast": { + "allDownloadsCompleted": "Все загрузки завершены", + "downloadingModel": "Загрузка модели...", + "downloadsFailed": "{count} загрузок не удалось | {count} загрузка не удалась | {count} загрузок не удалось", + "failed": "Не удалось", + "filter": { + "all": "Все", + "completed": "Завершено", + "failed": "Не удалось" + }, + "finished": "Завершено", + "importingModels": "Импорт моделей", + "noImportsInQueue": "Нет {filter} в очереди", + "pending": "В ожидании", + "progressCount": "{completed} из {total}" + }, + "queue": { + "completedIn": "Завершено за {duration}", + "inQueue": "В очереди...", + "initializingAlmostReady": "Инициализация — почти готово", + "jobAddedToQueue": "Задача добавлена в очередь", + "jobDetails": { + "computeHoursUsed": "Использовано вычислительных часов", + "errorMessage": "Сообщение об ошибке", + "estimatedFinishIn": "Ожидаемое завершение через", + "estimatedStartIn": "Ожидаемый старт через", + "eta": { + "minutes": "~{count} минута | ~{count} минут", + "minutesRange": "~{lo}-{hi} минут", + "seconds": "~{count} секунда | ~{count} секунд", + "secondsRange": "~{lo}-{hi} секунд" + }, + "failedAfter": "Ошибка после", + "generatedOn": "Сгенерировано", + "header": "Детали задачи", + "jobId": "ID задачи", + "queuePosition": "Позиция в очереди", + "queuePositionValue": "~{count} задача перед вашей | ~{count} задач перед вашей", + "queuedAt": "В очереди с", + "report": "Сообщить", + "timeElapsed": "Прошедшее время", + "totalGenerationTime": "Общее время генерации", + "workflow": "Рабочий процесс" + }, + "jobHistory": "История задач", + "jobList": { + "sortComputeHoursUsed": "Использовано вычислительных часов (сначала больше всего)", + "sortMostRecent": "Самые новые", + "sortTotalGenerationTime": "Общее время генерации (сначала самое долгое)", + "undated": "Без даты" + }, + "jobMenu": { + "addToCurrentWorkflow": "Добавить в текущий рабочий процесс", + "cancelJob": "Отменить задачу", + "copyErrorMessage": "Скопировать сообщение об ошибке", + "copyJobId": "Скопировать ID задачи", + "delete": "Удалить", + "deleteAsset": "Удалить ресурс", + "download": "Скачать", + "exportWorkflow": "Экспортировать рабочий процесс", + "inspectAsset": "Просмотреть ресурс", + "openAsWorkflowNewTab": "Открыть как рабочий процесс в новой вкладке", + "openWorkflowNewTab": "Открыть рабочий процесс в новой вкладке", + "removeJob": "Удалить задачу", + "reportError": "Сообщить об ошибке" + }, + "toggleJobHistory": "Показать/скрыть историю задач" + }, "releaseToast": { + "description": "Ознакомьтесь с последними улучшениями и функциями в этом обновлении.", "newVersionAvailable": "Доступна новая версия!", "skip": "Пропустить", "update": "Обновить", "whatsNew": "Что нового?" }, + "rightSidePanel": { + "addFavorite": "В избранное", + "advancedInputs": "РАСШИРЕННЫЕ ВХОДНЫЕ ДАННЫЕ", + "bypass": "Обход", + "color": "Цвет узла", + "fallbackGroupTitle": "Группа", + "fallbackNodeTitle": "Узел", + "favorites": "ИЗБРАННЫЕ ВХОДЫ", + "favoritesNone": "НЕТ ИЗБРАННЫХ ВХОДОВ", + "favoritesNoneDesc": "Здесь будут отображаться ваши избранные входы", + "favoritesNoneTooltip": "Отметьте виджеты звёздочкой для быстрого доступа без выбора узлов", + "globalSettings": { + "canvas": "ХОЛСТ", + "connectionLinks": "СВЯЗИ", + "gridSpacing": "Шаг сетки", + "linkShape": "Форма связи", + "nodes": "УЗЛЫ", + "nodes2": "Узлы 2.0", + "searchPlaceholder": "Поиск быстрых настроек...", + "showAdvanced": "Показать расширенные параметры", + "showAdvancedTooltip": "Это важная настройка, которая при включении отображает все расширенные параметры для узлов", + "showConnectedLinks": "Показывать соединённые связи", + "showInfoBadges": "Показывать информационные бейджи", + "showToolbox": "Показывать панель инструментов при выборе", + "snapNodesToGrid": "Привязывать узлы к сетке", + "title": "Глобальные настройки", + "viewAllSettings": "Просмотреть все настройки" + }, + "groupSettings": "Настройки группы", + "groups": "Группы", + "hideAdvancedInputsButton": "Скрыть расширенные параметры", + "hideInput": "Скрыть вход", + "info": "Информация", + "inputs": "ВХОДЫ", + "inputsNone": "НЕТ ВХОДОВ", + "inputsNoneTooltip": "Узел не имеет входов", + "locateNode": "Найти узел на холсте", + "mute": "Отключить", + "noSelection": "Выберите узел, чтобы увидеть его свойства и информацию.", + "nodeState": "Состояние узла", + "nodes": "Узлы", + "nodesNoneDesc": "НЕТ УЗЛОВ", + "noneSearchDesc": "Нет элементов, соответствующих вашему поиску", + "normal": "Обычный", + "parameters": "Параметры", + "pinned": "Закреплено", + "properties": "Свойства", + "removeFavorite": "Убрать из избранного", + "settings": "Настройки", + "showAdvancedInputsButton": "Показать расширенные входные данные", + "showInput": "Показать вход", + "title": "Нет выбранных узлов | 1 узел выбран | {count} узлов выбрано", + "togglePanel": "Показать/скрыть панель свойств", + "workflowOverview": "Обзор рабочего процесса" + }, "selectionToolbox": { "Bypass Group Nodes": "Обойти групповые узлы", "Set Group Nodes to Always": "Всегда использовать групповые узлы", @@ -1389,6 +1936,8 @@ "serverConfig": { "modifiedConfigs": "Вы изменили следующие конфигурации сервера. Перезапустите, чтобы применить изменения.", "restart": "Перезапустить", + "restartRequiredToastDetail": "Перезапустите приложение, чтобы применить изменения конфигурации сервера.", + "restartRequiredToastSummary": "Требуется перезапуск", "revertChanges": "Отменить изменения" }, "serverConfigCategories": { @@ -1457,6 +2006,10 @@ "enable-cors-header": { "name": "Включить CORS заголовок: Используйте \"*\" для всех источников или укажите домен" }, + "enable-manager-legacy-ui": { + "name": "Использовать устаревший интерфейс Manager", + "tooltip": "Использует устаревший интерфейс ComfyUI-Manager вместо нового." + }, "fast": { "name": "Включить некоторые непроверенные и потенциально ухудшающие качество оптимизации." }, @@ -1558,6 +2111,7 @@ "CustomColorPalettes": "Пользовательские цветовые палитры", "DevMode": "Режим разработчика", "EditTokenWeight": "Редактировать вес токена", + "Execution": "Выполнение", "Extension": "Расширение", "General": "Общие", "Graph": "Граф", @@ -1572,12 +2126,14 @@ "Mask Editor": "Редактор масок", "Menu": "Меню", "ModelLibrary": "Библиотека моделей", - "NewEditor": "Новый редактор", "Node": "Нода", "Node Search Box": "Поисковая строка нод", "Node Widget": "Виджет ноды", "NodeLibrary": "Библиотека нод", + "Nodes 2_0": "Nodes 2.0", "Notification Preferences": "Настройки уведомлений", + "Other": "Другое", + "PLY": "PLY", "PlanCredits": "План и кредиты", "Pointer": "Указатель", "Queue": "Очередь", @@ -1596,7 +2152,8 @@ "Vue Nodes": "Vue Nodes", "VueNodes": "Vue узлы", "Window": "Окно", - "Workflow": "Рабочий процесс" + "Workflow": "Рабочий процесс", + "Workspace": "Рабочее пространство" }, "shape": { "CARD": "Карточка", @@ -1622,11 +2179,14 @@ "viewControls": "Управление видом" }, "sideToolbar": { + "activeJobStatus": "Активная задача: {status}", "assets": "Ассеты", "backToAssets": "Назад ко всем ассетам", "browseTemplates": "Просмотреть примеры шаблонов", "downloads": "Загрузки", + "generatedAssetsHeader": "Сгенерированные ресурсы", "helpCenter": "Центр поддержки", + "importedAssetsHeader": "Импортированные ресурсы", "labels": { "assets": "Ассеты", "console": "Консоль", @@ -1669,6 +2229,47 @@ }, "openWorkflow": "Открыть рабочий процесс в локальной файловой системе", "queue": "Очередь", + "queueProgressOverlay": { + "activeJobs": "{count} активное задание | {count} активных задания | {count} активных заданий", + "activeJobsShort": "{count} активно | {count} активно", + "activeJobsSuffix": "активных заданий", + "cancelJobTooltip": "Отменить задание", + "clearHistory": "Очистить историю очереди заданий", + "clearHistoryDialogAssetsNote": "Ассеты, созданные этими заданиями, не будут удалены и всегда доступны на панели ассетов.", + "clearHistoryDialogDescription": "Все завершённые или неудачные задания ниже будут удалены из этой панели очереди заданий.", + "clearHistoryDialogTitle": "Очистить историю очереди заданий?", + "clearQueueTooltip": "Очистить очередь", + "clearQueued": "Очистить очередь", + "colonPercent": ": {percent}", + "currentNode": "Текущий узел:", + "expandCollapsedQueue": "Развернуть очередь заданий", + "filterAllWorkflows": "Все рабочие процессы", + "filterBy": "Фильтровать по", + "filterCurrentWorkflow": "Текущий рабочий процесс", + "filterJobs": "Фильтровать задания", + "interruptAll": "Остановить все выполняющиеся задания", + "jobQueue": "Очередь заданий", + "jobsCompleted": "{count} задание завершено | {count} задания завершено | {count} заданий завершено", + "jobsFailed": "{count} задание не выполнено | {count} задания не выполнено | {count} заданий не выполнено", + "moreOptions": "Больше опций", + "noActiveJobs": "Нет активных заданий", + "preview": "Предпросмотр", + "queuedSuffix": "в очереди", + "running": "выполняется", + "showAssets": "Показать ассеты", + "showAssetsPanel": "Показать панель ассетов", + "sortBy": "Сортировать по", + "sortJobs": "Сортировать задания", + "stubClipTextEncode": "CLIP Text Encode:", + "title": "Прогресс очереди", + "total": "Всего: {percent}", + "viewAllJobs": "Просмотреть все задания", + "viewGrid": "Просмотр сеткой", + "viewJobHistory": "Просмотреть историю заданий", + "viewList": "Просмотр списком" + }, + "searchAssets": "Поиск ассетов", + "sidebar": "Боковая панель", "templates": "Шаблоны", "themeToggle": "Переключить тему", "workflowTab": { @@ -1711,24 +2312,58 @@ "subscription": { "addApiCredits": "Пополнить API-кредиты", "addCredits": "Добавить кредиты", + "addCreditsLabel": "Добавляйте кредиты в любое время", "benefits": { "benefit1": "Ежемесячные кредиты для Партнёрских узлов — пополняйте по необходимости", "benefit2": "До 30 минут выполнения на задание" }, "beta": "БЕТА", + "billedMonthly": "Оплата ежемесячно", + "billedYearly": "{total} Оплата ежегодно", + "billingComingSoon": { + "message": "Скоро появится командная оплата. Вы сможете оформить подписку на тариф для вашего рабочего пространства с оплатой за каждого участника. Следите за обновлениями.", + "title": "Скоро будет" + }, + "cancelSubscription": "Отменить подписку", + "changeTo": "Перейти на {plan}", "comfyCloud": "Comfy Cloud", + "comfyCloudLogo": "Логотип Comfy Cloud", + "contactOwnerToSubscribe": "Свяжитесь с владельцем рабочего пространства для оформления подписки", + "contactUs": "Связаться с нами", + "creditsRemainingThisMonth": "Кредитов осталось в этом месяце", + "creditsRemainingThisYear": "Кредитов осталось в этом году", + "creditsYouveAdded": "Добавленные вами кредиты", + "currentPlan": "Текущий план", + "customLoRAsLabel": "Импортируйте свои LoRA", + "description": "Выберите лучший план для себя", "expiresDate": "Истекает {date}", + "gpuLabel": "RTX 6000 Pro (96ГБ VRAM)", + "haveQuestions": "Есть вопросы или интересует корпоративное решение?", "invoiceHistory": "История счетов", "learnMore": "Узнать больше", + "managePayment": "Управление оплатой", + "managePlan": "Управление планом", "manageSubscription": "Управление подпиской", + "maxDuration": { + "creator": "30 мин", + "founder": "30 мин", + "pro": "1 ч", + "standard": "30 мин" + }, + "maxDurationLabel": "Максимальная длительность одного запуска рабочего процесса", "messageSupport": "Написать в поддержку", + "monthly": "Ежемесячно", "monthlyBonusDescription": "Ежемесячный бонус кредитов", + "monthlyCreditsInfo": "Эти кредиты обновляются ежемесячно и не переносятся", + "monthlyCreditsLabel": "Ежемесячные кредиты", "monthlyCreditsRollover": "Эти кредиты переносятся на следующий месяц", + "mostPopular": "Самый популярный", "nextBillingCycle": "следующий платёжный цикл", "partnerNodesBalance": "Баланс кредитов \"Партнёрских узлов\"", "partnerNodesCredits": "Кредиты партнёрских узлов", "partnerNodesDescription": "Для запуска коммерческих/проприетарных моделей", "perMonth": "USD / месяц", + "plansAndPricing": "Планы и цены", "prepaidCreditsInfo": "Кредиты, приобретённые отдельно и не имеющие срока действия", "prepaidDescription": "Предоплаченные кредиты", "renewsDate": "Обновляется {date}", @@ -1738,14 +2373,46 @@ "waitingForSubscription": "Завершите оформление подписки в новой вкладке. Мы автоматически определим, когда вы закончите!" }, "subscribeNow": "Подписаться сейчас", + "subscribeTo": "Подписаться на {plan}", "subscribeToComfyCloud": "Подписаться на Comfy Cloud", "subscribeToRun": "Подписаться", "subscribeToRunFull": "Подписаться для запуска", + "subscriptionRequiredMessage": "Для запуска рабочих процессов в облаке участникам требуется подписка", + "tierNameYearly": "{name} Ежегодно", + "tiers": { + "creator": { + "name": "Creator" + }, + "founder": { + "name": "Founder's Edition" + }, + "pro": { + "name": "Pro" + }, + "standard": { + "name": "Standard" + } + }, "title": "Подписка", "titleUnsubscribed": "Подпишитесь на Comfy Cloud", "totalCredits": "Всего кредитов", + "upgrade": "УЛУЧШИТЬ", + "upgradePlan": "Улучшить план", + "upgradeTo": "Улучшить до {plan}", + "usdPerMonth": "USD / мес", + "videoEstimateExplanation": "Эти оценки основаны на шаблоне Wan 2.2 Image-to-Video с настройками по умолчанию (5 секунд, 640x640, 16 кадров/с, 4 шага семплирования).", + "videoEstimateHelp": "Подробнее об этом шаблоне", + "videoEstimateLabel": "Примерное количество 5-секундных видео, созданных с помощью шаблона Wan 2.2 Image-to-Video", + "videoEstimateTryTemplate": "Попробовать этот шаблон", + "videoTemplateBasedCredits": "Видео, созданные с помощью Wan 2.2 Image to Video", + "viewEnterprise": "Посмотреть корпоративные решения", "viewMoreDetails": "Подробнее", + "viewMoreDetailsPlans": "Подробнее о планах и ценах", "viewUsageHistory": "История использования", + "workspaceNotSubscribed": "Это рабочее пространство не имеет подписки", + "yearly": "Ежегодно", + "yearlyCreditsLabel": "Годовые кредиты", + "yearlyDiscount": "СКИДКА 20%", "yourPlanIncludes": "Ваш план включает:" }, "tabMenu": { @@ -1757,8 +2424,14 @@ "duplicateTab": "Дублировать вкладку", "removeFromBookmarks": "Удалить из закладок" }, + "templateWidgets": { + "sort": { + "searchPlaceholder": "Поиск..." + } + }, "templateWorkflows": { "activeFilters": "Фильтры:", + "allTemplates": "Все шаблоны", "categories": "Категории", "category": { "3D": "3D", @@ -1785,6 +2458,7 @@ "error": { "templateNotFound": "Шаблон \"{templateName}\" не найден" }, + "licenseFilter": "Лицензия", "loading": "Загрузка шаблонов...", "loadingMore": "Загрузка дополнительных шаблонов...", "modelFilter": "Фильтр моделей", @@ -1801,12 +2475,14 @@ "default": "По умолчанию", "modelSizeLowToHigh": "Размер модели (от низкого к высокому)", "newest": "Новейшие", + "popular": "Популярные", "recommended": "Рекомендуемые", "searchPlaceholder": "Поиск...", "vramLowToHigh": "Использование VRAM (от низкого к высокому)" }, "sorting": "Сортировать по", "title": "Начните с шаблона", + "useCaseFilter": "Задачи", "useCasesSelected": "{count} вариантов использования" }, "toastMessages": { @@ -1835,9 +2511,22 @@ "failedToLoadModel": "Не удалось загрузить 3D-модель", "failedToPurchaseCredits": "Не удалось купить кредиты: {error}", "failedToQueue": "Не удалось поставить в очередь", + "failedToToggleCamera": "Не удалось переключить камеру", + "failedToToggleGrid": "Не удалось переключить сетку", + "failedToUpdateBackgroundColor": "Не удалось обновить цвет фона", + "failedToUpdateBackgroundImage": "Не удалось обновить фоновое изображение", + "failedToUpdateBackgroundRenderMode": "Не удалось обновить режим рендеринга фона на {mode}", + "failedToUpdateEdgeThreshold": "Не удалось обновить порог границ", + "failedToUpdateFOV": "Не удалось обновить угол обзора", + "failedToUpdateLightIntensity": "Не удалось обновить интенсивность освещения", + "failedToUpdateMaterialMode": "Не удалось обновить режим материала", + "failedToUpdateUpDirection": "Не удалось обновить направление вверх", + "failedToUploadBackgroundImage": "Не удалось загрузить фоновое изображение", "fileLoadError": "Не удалось найти рабочий процесс в {fileName}", + "fileTooLarge": "Файл слишком большой ({size} МБ). Максимально поддерживаемый размер — {maxSize} МБ", "fileUploadFailed": "Не удалось загрузить файл", "interrupted": "Выполнение было прервано", + "legacyMaskEditorDeprecated": "Устаревший редактор масок будет скоро удалён.", "migrateToLitegraphReroute": "Узлы перенаправления будут удалены в будущих версиях. Нажмите, чтобы перейти на litegraph-native reroute.", "modelLoadedSuccessfully": "3D-модель успешно загружена", "no3dScene": "Нет 3D сцены для применения текстуры", @@ -1864,12 +2553,14 @@ "selectUser": "Выберите пользователя" }, "userSettings": { + "accountSettings": "Настройки аккаунта", "email": "Электронная почта", "name": "Имя", "notSet": "Не задано", "provider": "Способ входа", "title": "Настройки пользователя", - "updatePassword": "Обновить пароль" + "updatePassword": "Обновить пароль", + "workspaceSettings": "Настройки рабочего пространства" }, "validation": { "descriptionRequired": "Описание обязательно", @@ -1898,22 +2589,32 @@ "updateFrontend": "Обновить интерфейс" }, "vueNodesBanner": { - "message": "Узлы получили новый внешний вид", + "desc": "– Более гибкие рабочие процессы, новые мощные виджеты, расширяемость", + "title": "Представляем Nodes 2.0", "tryItOut": "Попробовать" }, "vueNodesMigration": { "button": "Открыть настройки", "message": "Предпочитаете классический дизайн узлов?" }, + "vueNodesMigrationMainMenu": { + "message": "Переключиться обратно на Nodes 2.0 можно в главном меню." + }, "welcome": { "getStarted": "Начать", "title": "Добро пожаловать в ComfyUI" }, "whatsNewPopup": { + "later": "Позже", "learnMore": "Узнать больше", "noReleaseNotes": "Нет доступных примечаний к выпуску." }, + "widgetFileUpload": { + "browseFiles": "Выбрать файлы", + "dropPrompt": "Перетащите ваш файл или" + }, "widgets": { + "node2only": "Только Node 2.0", "selectModel": "Выбрать модель", "uploadSelect": { "placeholder": "Выбрать...", @@ -1922,6 +2623,26 @@ "placeholderModel": "Выбрать модель...", "placeholderUnknown": "Выбрать медиа...", "placeholderVideo": "Выбрать видео..." + }, + "valueControl": { + "decrement": "Уменьшить значение", + "decrementDesc": "Уменьшает значение на 1 или выбирает предыдущий вариант", + "editSettings": "Изменить настройки управления", + "fixed": "Фиксированное значение", + "fixedDesc": "Оставляет значение без изменений", + "header": { + "after": "ПОСЛЕ", + "before": "ДО", + "postfix": "запуска рабочего процесса:", + "prefix": "Автоматически обновлять значение" + }, + "increment": "Увеличить значение", + "incrementDesc": "Увеличивает значение на 1 или выбирает следующий вариант", + "linkToGlobal": "Связать с", + "linkToGlobalDesc": "Уникальное значение, связанное с настройкой глобального значения", + "linkToGlobalSeed": "Глобальное значение", + "randomize": "Случайное значение", + "randomizeDesc": "Случайным образом изменяет значение после каждого генерации" } }, "workflowService": { @@ -1929,6 +2650,146 @@ "exportWorkflow": "Экспорт рабочего процесса", "saveWorkflow": "Сохранить рабочий процесс" }, + "workspace": { + "addedToWorkspace": "Вы были добавлены в {workspaceName}", + "inviteAccepted": "Приглашение принято", + "inviteFailed": "Не удалось принять приглашение", + "unsavedChanges": { + "message": "У вас есть несохранённые изменения. Хотите их отменить и переключиться на другое рабочее пространство?", + "title": "Несохранённые изменения" + } + }, + "workspaceAuth": { + "errors": { + "accessDenied": "У вас нет доступа к этому рабочему пространству", + "invalidFirebaseToken": "Ошибка аутентификации. Пожалуйста, попробуйте войти снова.", + "notAuthenticated": "Вы должны войти в систему, чтобы получить доступ к рабочим пространствам", + "tokenExchangeFailed": "Не удалось выполнить аутентификацию с рабочим пространством: {error}", + "workspaceNotFound": "Рабочее пространство не найдено" + } + }, + "workspacePanel": { + "createWorkspaceDialog": { + "create": "Создать", + "message": "Рабочие пространства позволяют участникам использовать общий пул кредитов. После создания вы станете владельцем.", + "nameLabel": "Название рабочего пространства*", + "namePlaceholder": "Введите название рабочего пространства", + "title": "Создать новое рабочее пространство" + }, + "dashboard": { + "placeholder": "Настройки рабочего пространства" + }, + "deleteDialog": { + "message": "Все неиспользованные кредиты или несохранённые ресурсы будут потеряны. Это действие необратимо.", + "messageWithName": "Удалить «{name}»? Все неиспользованные кредиты или несохранённые ресурсы будут потеряны. Это действие необратимо.", + "title": "Удалить это рабочее пространство?" + }, + "editWorkspaceDialog": { + "nameLabel": "Название рабочего пространства", + "save": "Сохранить", + "title": "Редактировать детали рабочего пространства" + }, + "invite": "Пригласить", + "inviteLimitReached": "Достигнут лимит в 50 участников", + "inviteMember": "Пригласить участника", + "inviteMemberDialog": { + "createLink": "Создать ссылку", + "linkCopied": "Скопировано", + "linkCopyFailed": "Не удалось скопировать ссылку", + "linkStep": { + "copyLink": "Скопировать ссылку", + "done": "Готово", + "message": "Убедитесь, что его аккаунт использует этот email.", + "title": "Отправьте эту ссылку человеку" + }, + "message": "Создайте ссылку-приглашение для отправки человеку", + "placeholder": "Введите email человека", + "title": "Пригласить человека в это рабочее пространство" + }, + "leaveDialog": { + "leave": "Покинуть", + "message": "Вы не сможете присоединиться снова, если не свяжетесь с владельцем рабочего пространства.", + "title": "Покинуть это рабочее пространство?" + }, + "members": { + "actions": { + "copyLink": "Скопировать ссылку приглашения", + "removeMember": "Удалить участника", + "revokeInvite": "Отозвать приглашение" + }, + "columns": { + "expiryDate": "Дата истечения", + "inviteDate": "Дата приглашения", + "joinDate": "Дата вступления" + }, + "createNewWorkspace": "создайте новое.", + "membersCount": "{count}/50 участников", + "noInvites": "Нет ожидающих приглашений", + "noMembers": "Нет участников", + "pendingInvitesCount": "{count} ожидающее приглашение | {count} ожидающих приглашений", + "personalWorkspaceMessage": "Вы не можете приглашать других участников в ваше личное рабочее пространство. Чтобы добавить участников,", + "tabs": { + "active": "Активные", + "pendingCount": "Ожидают ({count})" + } + }, + "menu": { + "deleteWorkspace": "Удалить рабочее пространство", + "deleteWorkspaceDisabledTooltip": "Сначала отмените активную подписку рабочего пространства", + "editWorkspace": "Редактировать детали рабочего пространства", + "leaveWorkspace": "Покинуть рабочее пространство" + }, + "removeMemberDialog": { + "error": "Не удалось удалить участника", + "message": "Этот участник будет удалён из вашего рабочего пространства. Использованные им кредиты не будут возвращены.", + "remove": "Удалить участника", + "success": "Участник удалён", + "title": "Удалить этого участника?" + }, + "revokeInviteDialog": { + "message": "Этот человек больше не сможет присоединиться к вашему рабочему пространству. Его ссылка-приглашение будет аннулирована.", + "revoke": "Отозвать приглашение", + "title": "Отозвать приглашение?" + }, + "tabs": { + "dashboard": "Панель управления", + "membersCount": "Участники ({count})", + "planCredits": "Тариф и кредиты" + }, + "toast": { + "failedToCreateWorkspace": "Не удалось создать рабочее пространство", + "failedToDeleteWorkspace": "Не удалось удалить рабочее пространство", + "failedToFetchWorkspaces": "Не удалось загрузить рабочие пространства", + "failedToLeaveWorkspace": "Не удалось покинуть рабочее пространство", + "failedToUpdateWorkspace": "Не удалось обновить рабочее пространство", + "workspaceCreated": { + "message": "Оформите подписку, пригласите коллег и начните совместную работу.", + "subscribe": "Оформить подписку", + "title": "Рабочее пространство создано" + }, + "workspaceDeleted": { + "message": "Рабочее пространство было безвозвратно удалено.", + "title": "Рабочее пространство удалено" + }, + "workspaceLeft": { + "message": "Вы вышли из рабочего пространства.", + "title": "Вы покинули рабочее пространство" + }, + "workspaceUpdated": { + "message": "Детали рабочего пространства сохранены.", + "title": "Рабочее пространство обновлено" + } + } + }, + "workspaceSwitcher": { + "createWorkspace": "Создать новое рабочее пространство", + "maxWorkspacesReached": "Вы можете владеть только 10 рабочими пространствами. Удалите одно, чтобы создать новое.", + "personal": "Личное", + "roleMember": "Участник", + "roleOwner": "Владелец", + "subscribe": "Оформить подписку", + "switchWorkspace": "Сменить рабочее пространство" + }, "zoomControls": { "hideMinimap": "Скрыть миникарту", "label": "Управление масштабом", diff --git a/src/locales/ru/nodeDefs.json b/src/locales/ru/nodeDefs.json index 2485e8f20..e999a8762 100644 --- a/src/locales/ru/nodeDefs.json +++ b/src/locales/ru/nodeDefs.json @@ -39,6 +39,87 @@ "sigmas": { "name": "сигмы" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AddTextPrefix": { + "display_name": "Добавить префикс к тексту", + "inputs": { + "prefix": { + "name": "префикс", + "tooltip": "Префикс для добавления." + }, + "texts": { + "name": "тексты", + "tooltip": "Текст для обработки." + } + }, + "outputs": { + "0": { + "name": "тексты", + "tooltip": "Обработанные тексты" + } + } + }, + "AddTextSuffix": { + "display_name": "Добавить суффикс к тексту", + "inputs": { + "suffix": { + "name": "суффикс", + "tooltip": "Суффикс для добавления." + }, + "texts": { + "name": "тексты", + "tooltip": "Текст для обработки." + } + }, + "outputs": { + "0": { + "name": "тексты", + "tooltip": "Обработанные тексты" + } + } + }, + "AdjustBrightness": { + "display_name": "Регулировка яркости", + "inputs": { + "factor": { + "name": "коэффициент", + "tooltip": "Коэффициент яркости. 1.0 = без изменений, <1.0 = темнее, >1.0 = ярче." + }, + "images": { + "name": "изображения", + "tooltip": "Изображение для обработки." + } + }, + "outputs": { + "0": { + "name": "изображения", + "tooltip": "Обработанные изображения" + } + } + }, + "AdjustContrast": { + "display_name": "Регулировка контрастности", + "inputs": { + "factor": { + "name": "коэффициент", + "tooltip": "Коэффициент контрастности. 1.0 = без изменений, <1.0 = меньше контраст, >1.0 = больше контраст." + }, + "images": { + "name": "изображения", + "tooltip": "Изображение для обработки." + } + }, + "outputs": { + "0": { + "name": "изображения", + "tooltip": "Обработанные изображения" + } } }, "AlignYourStepsScheduler": { @@ -70,6 +151,11 @@ "name": "громкость", "tooltip": "Корректировка громкости в децибелах (дБ). 0 = без изменений, +6 = удвоение, -6 = половина и т.д." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "AudioConcat": { @@ -86,6 +172,11 @@ "name": "направление", "tooltip": "Добавлять audio2 после или перед audio1." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "AudioEncoderEncode": { @@ -131,6 +222,11 @@ "name": "метод_слияния", "tooltip": "Метод, используемый для объединения аудиоволн." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BasicGuider": { @@ -142,6 +238,11 @@ "model": { "name": "модель" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BasicScheduler": { @@ -159,6 +260,50 @@ "steps": { "name": "шаги" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchImagesNode": { + "display_name": "Пакет изображений", + "inputs": { + "images": { + "name": "изображения" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchLatentsNode": { + "display_name": "Пакет латентных", + "inputs": { + "latents": { + "name": "латентные" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchMasksNode": { + "display_name": "Пакет масок", + "inputs": { + "masks": { + "name": "маски" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BetaSamplingScheduler": { @@ -176,6 +321,73 @@ "steps": { "name": "шаги" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BriaImageEditNode": { + "description": "Редактирование изображений с помощью новейшей модели Bria", + "display_name": "Bria Image Edit", + "inputs": { + "control_after_generate": { + "name": "контроль после генерации" + }, + "guidance_scale": { + "name": "guidance_scale", + "tooltip": "Большее значение заставляет изображение точнее следовать промпту." + }, + "image": { + "name": "изображение" + }, + "mask": { + "name": "маска", + "tooltip": "Если не указано, редактирование применяется ко всему изображению." + }, + "model": { + "name": "model" + }, + "moderation": { + "name": "модерация", + "tooltip": "Настройки модерации" + }, + "moderation_prompt_content_moderation": { + "name": "модерация_контента_промпта" + }, + "moderation_visual_input_moderation": { + "name": "модерация_визуального_входа" + }, + "moderation_visual_output_moderation": { + "name": "модерация_визуального_выхода" + }, + "negative_prompt": { + "name": "негативный_промпт" + }, + "prompt": { + "name": "промпт", + "tooltip": "Инструкция для редактирования изображения" + }, + "seed": { + "name": "seed" + }, + "steps": { + "name": "шаги" + }, + "structured_prompt": { + "name": "структурированный_промпт", + "tooltip": "Строка, содержащая структурированный промпт для редактирования в формате JSON. Используйте вместо обычного промпта для точного, программного управления." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "структурированный_промпт", + "tooltip": null + } } }, "ByteDanceFirstLastFrameNode": { @@ -201,13 +413,16 @@ "name": "first_frame", "tooltip": "Первый кадр, который будет использоваться для видео." }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "Этот параметр игнорируется для всех моделей, кроме seedance-1-5-pro." + }, "last_frame": { "name": "last_frame", "tooltip": "Последний кадр, который будет использоваться для видео." }, "model": { - "name": "модель", - "tooltip": "Название модели" + "name": "модель" }, "prompt": { "name": "промпт", @@ -248,8 +463,7 @@ "tooltip": "Базовое изображение для редактирования" }, "model": { - "name": "model", - "tooltip": "Название модели" + "name": "model" }, "prompt": { "name": "prompt", @@ -286,8 +500,7 @@ "tooltip": "Пользовательская высота изображения. Значение работает только если `размер_пресет` установлен в `Пользовательский`" }, "model": { - "name": "model", - "tooltip": "Название модели" + "name": "model" }, "prompt": { "name": "промпт", @@ -336,8 +549,7 @@ "tooltip": "От одного до четырёх изображений." }, "model": { - "name": "модель", - "tooltip": "Название модели" + "name": "модель" }, "prompt": { "name": "промпт", @@ -381,13 +593,16 @@ "name": "duration", "tooltip": "Продолжительность видео в секундах." }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "Этот параметр игнорируется для всех моделей, кроме seedance-1-5-pro." + }, "image": { "name": "image", "tooltip": "Первый кадр, который будет использоваться для видео." }, "model": { - "name": "model", - "tooltip": "Название модели" + "name": "model" }, "prompt": { "name": "prompt", @@ -489,9 +704,12 @@ "name": "длительность", "tooltip": "Длительность выходного видео в секундах." }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "Этот параметр игнорируется для всех моделей, кроме seedance-1-5-pro." + }, "model": { - "name": "модель", - "tooltip": "Название модели" + "name": "модель" }, "prompt": { "name": "промпт", @@ -531,6 +749,11 @@ "positive": { "name": "положительный" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "CFGNorm": { @@ -769,6 +992,25 @@ } } }, + "CLIPTextEncodeKandinsky5": { + "display_name": "CLIPTextEncodeKandinsky5", + "inputs": { + "clip": { + "name": "clip" + }, + "clip_l": { + "name": "clip_l" + }, + "qwen25_7b": { + "name": "qwen25_7b" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "CLIPTextEncodeLumina2": { "description": "Кодирует системный запрос и запрос пользователя с помощью модели CLIP во встраиваемый элемент, который можно использовать для направления модели диффузии на генерацию конкретных изображений.", "display_name": "CLIP Text Encode для Lumina2", @@ -959,6 +1201,29 @@ } } }, + "CenterCropImages": { + "display_name": "Обрезка по центру", + "inputs": { + "height": { + "name": "высота", + "tooltip": "Высота обрезки." + }, + "images": { + "name": "изображения", + "tooltip": "Изображение для обработки." + }, + "width": { + "name": "ширина", + "tooltip": "Ширина обрезки." + } + }, + "outputs": { + "0": { + "name": "изображения", + "tooltip": "Обработанные изображения" + } + } + }, "CheckpointLoader": { "display_name": "Загрузить сheckpoint с конфигурацией (УСТАРЕЛО)", "inputs": { @@ -1095,6 +1360,26 @@ } } }, + "ComfySwitchNode": { + "display_name": "Переключатель", + "inputs": { + "on_false": { + "name": "если_ложь" + }, + "on_true": { + "name": "если_истина" + }, + "switch": { + "name": "переключатель" + } + }, + "outputs": { + "0": { + "name": "выход", + "tooltip": null + } + } + }, "ConditioningAverage": { "display_name": "Среднее кондиционирование", "inputs": { @@ -1327,14 +1612,14 @@ "name": "всего_секунд" } }, - "outputs": { - "0": { - "name": "положительный" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "отрицательный" + { + "tooltip": null } - } + ] }, "ConditioningTimestepsRange": { "display_name": "Диапазон временных шагов", @@ -1391,6 +1676,10 @@ "name": "измерение", "tooltip": "Измерение, к которому применяются контекстные окна." }, + "freenoise": { + "name": "свободный_шум", + "tooltip": "Применять ли перемешивание шума FreeNoise, улучшает смешивание окон." + }, "fuse_method": { "name": "метод_объединения", "tooltip": "Метод объединения контекстных окон." @@ -1791,8 +2080,32 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, + "CustomCombo": { + "display_name": "Пользовательская комбинация", + "inputs": { + "choice": { + "name": "выбор" + }, + "index": { + }, + "option1": { + } + }, + "outputs": [ + null, + { + "name": "ИНДЕКС", + "tooltip": null + } + ] + }, "DiffControlNetLoader": { "display_name": "Загрузить модель ControlNet (дифф)", "inputs": { @@ -1829,7 +2142,12 @@ } }, "DisableNoise": { - "display_name": "Отключить шум" + "display_name": "Отключить шум", + "outputs": { + "0": { + "tooltip": null + } + } }, "DualCFGGuider": { "display_name": "Двойной CFG Гид", @@ -1855,6 +2173,11 @@ "style": { "name": "стиль" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "DualCLIPLoader": { @@ -1938,6 +2261,11 @@ "name": "частота_дискретизации", "tooltip": "Частота дискретизации пустого аудиоклипа." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyChromaRadianceLatentImage": { @@ -1981,6 +2309,25 @@ } } }, + "EmptyFlux2LatentImage": { + "display_name": "Пустой Flux 2 latent", + "inputs": { + "batch_size": { + "name": "размер_пакета" + }, + "height": { + "name": "высота" + }, + "width": { + "name": "ширина" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptyHunyuanImageLatent": { "display_name": "EmptyHunyuanImageLatent", "inputs": { @@ -2022,6 +2369,28 @@ } } }, + "EmptyHunyuanVideo15Latent": { + "display_name": "Пустой HunyuanVideo 1.5 latent", + "inputs": { + "batch_size": { + "name": "размер_пакета" + }, + "height": { + "name": "высота" + }, + "length": { + "name": "длина" + }, + "width": { + "name": "ширина" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptyImage": { "display_name": "Пустое изображение", "inputs": { @@ -2071,6 +2440,11 @@ "seconds": { "name": "секунды" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyLatentHunyuan3Dv2": { @@ -2083,6 +2457,11 @@ "resolution": { "name": "разрешение" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyLatentImage": { @@ -2130,6 +2509,28 @@ } } }, + "EmptyQwenImageLayeredLatentImage": { + "display_name": "Пустой Qwen Image Layered latent", + "inputs": { + "batch_size": { + "name": "размер_пакета" + }, + "height": { + "name": "высота" + }, + "layers": { + "name": "слои" + }, + "width": { + "name": "ширина" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptySD3LatentImage": { "display_name": "Пустой SD3LatentImage", "inputs": { @@ -2177,6 +2578,11 @@ "steps": { "name": "шаги" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ExtendIntermediateSigmas": { @@ -2197,6 +2603,11 @@ "steps": { "name": "шаги" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FeatherMask": { @@ -2217,6 +2628,11 @@ "top": { "name": "сверху" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FlipSigmas": { @@ -2225,6 +2641,102 @@ "sigmas": { "name": "sigmas" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2MaxImageNode": { + "description": "Генерирует изображения синхронно на основе запроса и разрешения.", + "display_name": "Flux.2 [max] Image", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "height": { + "name": "height" + }, + "images": { + "name": "images", + "tooltip": "До 9 изображений, которые могут быть использованы в качестве референсов." + }, + "prompt": { + "name": "prompt", + "tooltip": "Запрос для генерации или редактирования изображения" + }, + "prompt_upsampling": { + "name": "prompt_upsampling", + "tooltip": "Выполнять ли апсемплинг запроса. Если включено, автоматически модифицирует запрос для более креативной генерации." + }, + "seed": { + "name": "seed", + "tooltip": "Случайное зерно, используемое для создания шума." + }, + "width": { + "name": "width" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2ProImageNode": { + "description": "Генерирует изображения синхронно на основе запроса и разрешения.", + "display_name": "Flux.2 [pro] Image", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "height": { + "name": "height" + }, + "images": { + "name": "images", + "tooltip": "До 9 изображений, которые могут быть использованы в качестве референсов." + }, + "prompt": { + "name": "prompt", + "tooltip": "Запрос для генерации или редактирования изображения" + }, + "prompt_upsampling": { + "name": "prompt_upsampling", + "tooltip": "Выполнять ли апсемплинг запроса. Если включено, автоматически модифицирует запрос для более креативной генерации." + }, + "seed": { + "name": "seed", + "tooltip": "Случайное зерно, используемое для создания шума." + }, + "width": { + "name": "width" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2Scheduler": { + "display_name": "Flux2Scheduler", + "inputs": { + "height": { + "name": "height" + }, + "steps": { + "name": "steps" + }, + "width": { + "name": "width" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FluxDisableGuidance": { @@ -2547,6 +3059,11 @@ "s2": { "name": "s2" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FreeU_V2": { @@ -2567,6 +3084,11 @@ "s2": { "name": "s2" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "GITSScheduler": { @@ -2625,6 +3147,58 @@ } } }, + "GeminiImage2Node": { + "description": "Генерируйте или редактируйте изображения синхронно через Google Vertex API.", + "display_name": "Nano Banana Pro (Google Gemini Image)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "Если установлено значение 'auto', используется соотношение сторон вашего входного изображения; если изображение не предоставлено, обычно генерируется квадрат 16:9." + }, + "control_after_generate": { + "name": "control after generate" + }, + "files": { + "name": "files", + "tooltip": "Необязательные файлы для использования в качестве контекста для модели. Принимает входные данные от узла Gemini Generate Content Input Files." + }, + "images": { + "name": "images", + "tooltip": "Необязательное(ые) референсное(ые) изображение(я). Для добавления нескольких изображений используйте узел Batch Images (до 14)." + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "Текстовый запрос, описывающий изображение для генерации или редактирования. Укажите любые ограничения, стили или детали, которым должна следовать модель." + }, + "resolution": { + "name": "resolution", + "tooltip": "Целевое разрешение вывода. Для 2K/4K используется собственный Gemini upscaler." + }, + "response_modalities": { + "name": "response_modalities", + "tooltip": "Выберите 'IMAGE' для вывода только изображения или 'IMAGE+TEXT', чтобы получить как сгенерированное изображение, так и текстовый ответ." + }, + "seed": { + "name": "seed", + "tooltip": "Когда seed зафиксирован на определённом значении, модель старается выдавать одинаковый ответ при повторных запросах. Детерминированный результат не гарантируется. Также изменение модели или параметров, таких как температура, может привести к различиям в ответе даже при одинаковом seed. По умолчанию используется случайное значение seed." + }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "Базовые инструкции, определяющие поведение ИИ." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "tooltip": null + } + } + }, "GeminiImageNode": { "description": "Редактировать изображения синхронно через Google API.", "display_name": "Google Gemini Image", @@ -2652,9 +3226,17 @@ "name": "prompt", "tooltip": "Текстовый промпт для генерации" }, + "response_modalities": { + "name": "response_modalities", + "tooltip": "Выберите 'IMAGE' для вывода только изображения или 'IMAGE+TEXT', чтобы получить как сгенерированное изображение, так и текстовый ответ." + }, "seed": { "name": "seed", "tooltip": "Когда сид зафиксирован на определённом значении, модель прилагает все усилия, чтобы предоставить одинаковый ответ при повторных запросах. Детерминированный вывод не гарантируется. Также изменение модели или параметров, таких как температура, может вызвать вариации в ответе даже при использовании того же значения сида. По умолчанию используется случайное значение сида." + }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "Базовые инструкции, определяющие поведение ИИ." } }, "outputs": { @@ -2716,6 +3298,10 @@ "name": "сид", "tooltip": "Когда сид зафиксирован на определённом значении, модель прилагает все усилия, чтобы предоставить одинаковый ответ для повторных запросов. Детерминированный вывод не гарантируется. Также изменение модели или параметров, таких как температура, может вызвать вариации в ответе даже при использовании того же значения сида. По умолчанию используется случайное значение сида." }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "Базовые инструкции, определяющие поведение ИИ." + }, "video": { "name": "видео", "tooltip": "Необязательное видео для использования в качестве контекста для модели." @@ -2727,6 +3313,72 @@ } } }, + "GenerateTracks": { + "display_name": "GenerateTracks", + "inputs": { + "bezier": { + "name": "bezier", + "tooltip": "Включить путь по кривой Безье, используя среднюю точку как управляющую." + }, + "end_x": { + "name": "end_x", + "tooltip": "Нормализованная X-координата (0-1) для конечной позиции." + }, + "end_y": { + "name": "end_y", + "tooltip": "Нормализованная Y-координата (0-1) для конечной позиции." + }, + "height": { + "name": "height" + }, + "interpolation": { + "name": "interpolation", + "tooltip": "Управляет таймингом/скоростью движения вдоль пути." + }, + "mid_x": { + "name": "mid_x", + "tooltip": "Нормализованная X-координата управляющей точки для кривой Безье. Используется только при включённом 'bezier'." + }, + "mid_y": { + "name": "mid_y", + "tooltip": "Нормализованная Y-координата управляющей точки для кривой Безье. Используется только при включённом 'bezier'." + }, + "num_frames": { + "name": "num_frames" + }, + "num_tracks": { + "name": "num_tracks" + }, + "start_x": { + "name": "start_x", + "tooltip": "Нормализованная X-координата (0-1) для начальной позиции." + }, + "start_y": { + "name": "start_y", + "tooltip": "Нормализованная Y-координата (0-1) для начальной позиции." + }, + "track_mask": { + "name": "track_mask", + "tooltip": "Необязательная маска для указания видимых кадров." + }, + "track_spread": { + "name": "track_spread", + "tooltip": "Нормализованное расстояние между треками. Треки распределяются перпендикулярно направлению движения." + }, + "width": { + "name": "width" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "track_length", + "tooltip": null + } + } + }, "GetImageSize": { "description": "Возвращает ширину и высоту изображения и передаёт его без изменений.", "display_name": "Получить размер изображения", @@ -2735,17 +3387,17 @@ "name": "изображение" } }, - "outputs": { - "0": { - "name": "ширина" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "высота" + { + "tooltip": null }, - "2": { - "name": "размер_пакета" + { + "tooltip": null } - } + ] }, "GetVideoComponents": { "description": "Извлекает все компоненты из видео: кадры, аудио и частоту кадров.", @@ -2783,6 +3435,11 @@ "tapered_corners": { "name": "заостренные_углы" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Hunyuan3Dv2Conditioning": { @@ -2792,14 +3449,14 @@ "name": "выход_clip_vision" } }, - "outputs": { - "0": { - "name": "положительный" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "отрицательный" + { + "tooltip": null } - } + ] }, "Hunyuan3Dv2ConditioningMultiView": { "display_name": "Hunyuan3Dv2ConditioningMultiView", @@ -2817,14 +3474,14 @@ "name": "справа" } }, - "outputs": { - "0": { - "name": "положительный" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "отрицательный" + { + "tooltip": null } - } + ] }, "HunyuanImageToVideo": { "display_name": "HunyuanImageToVideo", @@ -2896,6 +3553,120 @@ } } }, + "HunyuanVideo15ImageToVideo": { + "display_name": "HunyuanVideo15ImageToVideo", + "inputs": { + "batch_size": { + "name": "batch_size" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "height": { + "name": "height" + }, + "length": { + "name": "length" + }, + "negative": { + "name": "negative" + }, + "positive": { + "name": "positive" + }, + "start_image": { + "name": "start_image" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "width" + } + }, + "outputs": { + "0": { + "name": "positive", + "tooltip": null + }, + "1": { + "name": "negative", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "HunyuanVideo15LatentUpscaleWithModel": { + "display_name": "Hunyuan Video 15 Latent Upscale With Model", + "inputs": { + "crop": { + "name": "crop" + }, + "height": { + "name": "height" + }, + "model": { + "name": "model" + }, + "samples": { + "name": "samples" + }, + "upscale_method": { + "name": "upscale_method" + }, + "width": { + "name": "width" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "HunyuanVideo15SuperResolution": { + "display_name": "HunyuanVideo15SuperResolution", + "inputs": { + "clip_vision_output": { + "name": "clip_vision_output" + }, + "latent": { + "name": "латентный" + }, + "negative": { + "name": "отрицательный" + }, + "noise_augmentation": { + "name": "добавление шума" + }, + "positive": { + "name": "положительный" + }, + "start_image": { + "name": "начальное изображение" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "положительный", + "tooltip": null + }, + "1": { + "name": "отрицательный", + "tooltip": null + }, + "2": { + "name": "латентный", + "tooltip": null + } + } + }, "HyperTile": { "display_name": "Гиперплитка", "inputs": { @@ -3100,6 +3871,11 @@ "strength": { "name": "сила" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageBatch": { @@ -3163,6 +3939,26 @@ "image": { "name": "изображение" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageCompare": { + "description": "Сравнивает два изображения бок о бок с помощью ползунка.", + "display_name": "Сравнение изображений", + "inputs": { + "compare_view": { + "name": "compare_view" + }, + "image_a": { + "name": "image_a" + }, + "image_b": { + "name": "image_b" + } } }, "ImageCompositeMasked": { @@ -3186,6 +3982,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageCrop": { @@ -3206,6 +4007,30 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageDeduplication": { + "display_name": "Удаление дубликатов изображений", + "inputs": { + "images": { + "name": "изображения", + "tooltip": "Список изображений для обработки." + }, + "similarity_threshold": { + "name": "порог сходства", + "tooltip": "Порог сходства (0-1). Чем выше значение, тем больше сходство. Изображения выше этого порога считаются дубликатами." + } + }, + "outputs": { + "0": { + "name": "изображения", + "tooltip": "Обработанные изображения" + } } }, "ImageFlip": { @@ -3217,6 +4042,11 @@ "image": { "name": "изображение" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageFromBatch": { @@ -3231,6 +4061,42 @@ "length": { "name": "длина" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageGrid": { + "display_name": "Сетка изображений", + "inputs": { + "cell_height": { + "name": "высота ячейки", + "tooltip": "Высота каждой ячейки в сетке." + }, + "cell_width": { + "name": "ширина ячейки", + "tooltip": "Ширина каждой ячейки в сетке." + }, + "columns": { + "name": "колонки", + "tooltip": "Количество колонок в сетке." + }, + "images": { + "name": "изображения", + "tooltip": "Список изображений для обработки." + }, + "padding": { + "name": "отступ", + "tooltip": "Отступ между изображениями." + } + }, + "outputs": { + "0": { + "name": "изображения", + "tooltip": "Обработанные изображения" + } } }, "ImageInvert": { @@ -3339,6 +4205,11 @@ "rotation": { "name": "вращение" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageScale": { @@ -3387,6 +4258,11 @@ "upscale_method": { "name": "метод увеличения" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageScaleToTotalPixels": { @@ -3398,6 +4274,9 @@ "megapixels": { "name": "мегапиксели" }, + "resolution_steps": { + "name": "шаги разрешения" + }, "upscale_method": { "name": "метод_апскейла" } @@ -3452,6 +4331,11 @@ "spacing_width": { "name": "ширина отступа" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageToMask": { @@ -3463,6 +4347,11 @@ "image": { "name": "изображение" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageUpscaleWithModel": { @@ -3572,6 +4461,29 @@ "mask": { "name": "маска" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "JoinAudioChannels": { + "description": "Объединяет левый и правый моно аудиоканалы в стерео аудио.", + "display_name": "Объединить аудиоканалы", + "inputs": { + "audio_left": { + "name": "audio_left" + }, + "audio_right": { + "name": "audio_right" + } + }, + "outputs": { + "0": { + "name": "аудио", + "tooltip": null + } } }, "JoinImageWithAlpha": { @@ -3697,6 +4609,58 @@ "sampler_name": { "name": "название_сэмплера" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Kandinsky5ImageToVideo": { + "display_name": "Kandinsky5ImageToVideo", + "inputs": { + "batch_size": { + "name": "размер_пакета" + }, + "height": { + "name": "высота" + }, + "length": { + "name": "длина" + }, + "negative": { + "name": "негативный" + }, + "positive": { + "name": "позитивный" + }, + "start_image": { + "name": "стартовое_изображение" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "ширина" + } + }, + "outputs": { + "0": { + "name": "позитивный", + "tooltip": null + }, + "1": { + "name": "негативный", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": "Пустое латентное видео" + }, + "3": { + "name": "cond_latent", + "tooltip": "Очищенные закодированные стартовые изображения, используются для замены зашумленного старта латентов вывода модели" + } } }, "KarrasScheduler": { @@ -3714,6 +4678,11 @@ "steps": { "name": "шаги" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "KlingCameraControlI2VNode": { @@ -3869,7 +4838,6 @@ } }, "KlingImage2VideoNode": { - "description": "Узел Kling Image to Video", "display_name": "Kling Image to Video", "inputs": { "aspect_ratio": { @@ -3957,6 +4925,35 @@ } } }, + "KlingImageToVideoWithAudio": { + "display_name": "Kling: изображение (первый кадр) в видео с аудио", + "inputs": { + "duration": { + "name": "длительность" + }, + "generate_audio": { + "name": "создать_аудио" + }, + "mode": { + "name": "режим" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "промпт", + "tooltip": "Положительный текстовый промпт." + }, + "start_frame": { + "name": "стартовый_кадр" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingLipSyncAudioToVideoNode": { "description": "Узел Kling Lip Sync Audio to Video. Синхронизирует движения рта на видео с аудиосодержимым аудиофайла.", "display_name": "Kling синхронизация губ в видео с аудио", @@ -4018,6 +5015,227 @@ } } }, + "KlingMotionControl": { + "display_name": "Kling: управление движением", + "inputs": { + "character_orientation": { + "name": "ориентация_персонажа", + "tooltip": "Управляет тем, откуда берется направление/ориентация персонажа.\nвидео: движения, выражения, движения камеры и ориентация следуют за референсным видео движения (остальные детали через промпт).\nизображение: движения и выражения также следуют за референсным видео, но ориентация персонажа соответствует референсному изображению (камера/другие детали через промпт)." + }, + "keep_original_sound": { + "name": "сохранить_оригинальный_звук" + }, + "mode": { + "name": "режим" + }, + "prompt": { + "name": "промпт" + }, + "reference_image": { + "name": "референсное_изображение" + }, + "reference_video": { + "name": "референсное_видео", + "tooltip": "Референсное видео движения, используемое для управления движением/выражением.\nОграничения по длительности зависят от character_orientation:\n - изображение: 3–10с (максимум 10с)\n - видео: 3–30с (максимум 30с)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProEditVideoNode": { + "description": "Редактируйте существующее видео с помощью последней модели от Kling.", + "display_name": "Kling: Omni редактирование видео (Pro)", + "inputs": { + "keep_original_sound": { + "name": "сохранить_оригинальный_звук" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "промпт", + "tooltip": "Текстовый промпт, описывающий содержимое видео. Может включать как положительные, так и отрицательные описания." + }, + "reference_images": { + "name": "референсные_изображения", + "tooltip": "До 4 дополнительных референсных изображений." + }, + "resolution": { + "name": "разрешение" + }, + "video": { + "name": "видео", + "tooltip": "Видео для редактирования. Длина выходного видео будет такой же." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProFirstLastFrameNode": { + "description": "Используйте начальный кадр, необязательный конечный кадр или референсные изображения с новейшей моделью Kling.", + "display_name": "Kling Omni Первый-Последний Кадр в Видео (Pro)", + "inputs": { + "duration": { + "name": "duration" + }, + "end_frame": { + "name": "end_frame", + "tooltip": "Необязательный конечный кадр для видео. Не может использоваться одновременно с 'reference_images'." + }, + "first_frame": { + "name": "first_frame" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Текстовый запрос, описывающий содержимое видео. Может включать как положительные, так и отрицательные описания." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "До 6 дополнительных референсных изображений." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageNode": { + "description": "Создавайте или редактируйте изображения с помощью новейшей модели Kling.", + "display_name": "Kling Omni Изображение (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Текстовый запрос, описывающий содержимое изображения. Может включать как положительные, так и отрицательные описания." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "До 10 дополнительных референсных изображений." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageToVideoNode": { + "description": "Используйте до 7 референсных изображений для генерации видео с помощью новейшей модели Kling.", + "display_name": "Kling Omni Изображение в Видео (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Текстовый запрос, описывающий содержимое видео. Может включать как положительные, так и отрицательные описания." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "До 7 референсных изображений." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProTextToVideoNode": { + "description": "Используйте текстовые запросы для генерации видео с помощью новейшей модели Kling.", + "display_name": "Kling Omni Текст в Видео (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Текстовый запрос, описывающий содержимое видео. Может включать как положительные, так и отрицательные описания." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProVideoToVideoNode": { + "description": "Используйте видео и до 4 референсных изображений для генерации видео с помощью новейшей модели Kling.", + "display_name": "Kling Omni Video to Video (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "keep_original_sound": { + "name": "keep_original_sound" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Текстовый запрос, описывающий содержимое видео. Может включать как положительные, так и отрицательные описания." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "До 4 дополнительных референсных изображений." + }, + "reference_video": { + "name": "reference_video", + "tooltip": "Видео для использования в качестве референса." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingSingleImageVideoEffectNode": { "description": "Достигайте различных спецэффектов при генерации видео на основе effect_scene.", "display_name": "Kling Видеоэффекты", @@ -4132,6 +5350,35 @@ } } }, + "KlingTextToVideoWithAudio": { + "display_name": "Kling Text to Video with Audio", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "generate_audio": { + "name": "generate_audio" + }, + "mode": { + "name": "mode" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Положительный текстовый запрос." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingVideoExtendNode": { "description": "Узел Kling Video Extend. Расширяет видео, созданные другими узлами Kling. video_id создаётся с помощью других узлов Kling.", "display_name": "Kling Video Extend", @@ -4186,6 +5433,26 @@ } } }, + "LTXAVTextEncoderLoader": { + "description": "[Рецепты]\n\nltxav: gemma 3 12B", + "display_name": "LTXV Загрузчик аудио-текстового энкодера", + "inputs": { + "ckpt_name": { + "name": "ckpt_name" + }, + "device": { + "name": "device" + }, + "text_encoder": { + "name": "text_encoder" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LTXVAddGuide": { "display_name": "LTXVAddGuide", "inputs": { @@ -4228,6 +5495,76 @@ } } }, + "LTXVAudioVAEDecode": { + "display_name": "LTXV Декодирование аудио VAE", + "inputs": { + "audio_vae": { + "name": "audio_vae", + "tooltip": "Модель Audio VAE, используемая для декодирования латентного пространства." + }, + "samples": { + "name": "samples", + "tooltip": "Латентное пространство для декодирования." + } + }, + "outputs": { + "0": { + "name": "Audio", + "tooltip": null + } + } + }, + "LTXVAudioVAEEncode": { + "display_name": "LTXV Кодирование аудио VAE", + "inputs": { + "audio": { + "name": "audio", + "tooltip": "Аудио для кодирования." + }, + "audio_vae": { + "name": "audio_vae", + "tooltip": "Модель Audio VAE для кодирования." + } + }, + "outputs": { + "0": { + "name": "Audio Latent", + "tooltip": null + } + } + }, + "LTXVAudioVAELoader": { + "display_name": "LTXV Загрузчик аудио VAE", + "inputs": { + "ckpt_name": { + "name": "ckpt_name", + "tooltip": "Контрольная точка Audio VAE для загрузки." + } + }, + "outputs": { + "0": { + "name": "Audio VAE", + "tooltip": null + } + } + }, + "LTXVConcatAVLatent": { + "display_name": "LTXVConcatAVLatent", + "inputs": { + "audio_latent": { + "name": "audio_latent" + }, + "video_latent": { + "name": "video_latent" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, "LTXVConditioning": { "display_name": "Кондиционирование LTXV", "inputs": { @@ -4280,6 +5617,33 @@ } } }, + "LTXVEmptyLatentAudio": { + "display_name": "LTXV Пустое латентное аудио", + "inputs": { + "audio_vae": { + "name": "audio_vae", + "tooltip": "Модель Audio VAE для получения конфигурации." + }, + "batch_size": { + "name": "batch_size", + "tooltip": "Количество латентных аудиосэмплов в батче." + }, + "frame_rate": { + "name": "frame_rate", + "tooltip": "Количество кадров в секунду." + }, + "frames_number": { + "name": "frames_number", + "tooltip": "Количество кадров." + } + }, + "outputs": { + "0": { + "name": "Latent", + "tooltip": null + } + } + }, "LTXVImgToVideo": { "display_name": "LTXVImgToVideo", "inputs": { @@ -4326,6 +5690,47 @@ } } }, + "LTXVImgToVideoInplace": { + "display_name": "LTXVImgToVideoInplace", + "inputs": { + "bypass": { + "name": "обход", + "tooltip": "Обойти кондиционирование." + }, + "image": { + "name": "изображение" + }, + "latent": { + "name": "latent" + }, + "strength": { + "name": "интенсивность" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, + "LTXVLatentUpsampler": { + "display_name": "LTXVLatentUpsampler", + "inputs": { + "samples": { + "name": "образцы" + }, + "upscale_model": { + "name": "модель увеличения" + }, + "vae": { + "name": "vae" + } + } + }, "LTXVPreprocess": { "display_name": "LTXVPreprocess", "inputs": { @@ -4374,6 +5779,25 @@ } } }, + "LTXVSeparateAVLatent": { + "description": "LTXV Separate AV Latent", + "display_name": "LTXVSeparateAVLatent", + "inputs": { + "av_latent": { + "name": "av_latent" + } + }, + "outputs": { + "0": { + "name": "video_latent", + "tooltip": null + }, + "1": { + "name": "audio_latent", + "tooltip": null + } + } + }, "LaplaceScheduler": { "display_name": "Scheduler Лапласа", "inputs": { @@ -4392,6 +5816,11 @@ "steps": { "name": "шаги" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LatentAdd": { @@ -4529,6 +5958,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LatentConcat": { @@ -4592,6 +6026,25 @@ } } }, + "LatentCutToBatch": { + "display_name": "LatentCutToBatch", + "inputs": { + "dim": { + "name": "dim" + }, + "samples": { + "name": "samples" + }, + "slice_size": { + "name": "slice_size" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LatentFlip": { "display_name": "Перевернуть латент", "inputs": { @@ -4745,6 +6198,19 @@ } } }, + "LatentUpscaleModelLoader": { + "display_name": "Загрузить модель увеличения latent", + "inputs": { + "model_name": { + "name": "model_name" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LazyCache": { "description": "Самодельная версия EasyCache - ещё более 'простая' версия EasyCache для реализации. В целом работает хуже, чем EasyCache, но лучше в некоторых редких случаях И имеет универсальную совместимость со всем в ComfyUI.", "display_name": "Ленивый кэш", @@ -4792,70 +6258,32 @@ }, "upload 3d model": { }, - "width": { - "name": "ширина" - } - }, - "outputs": { - "0": { - "name": "изображение" - }, - "1": { - "name": "mask" - }, - "2": { - "name": "путь к mesh" - }, - "3": { - "name": "нормаль" - }, - "4": { - "name": "линейный рисунок" - }, - "5": { - "name": "информация о камере" - }, - "6": { - "name": "recording_video" - } - } - }, - "Load3DAnimation": { - "display_name": "Загрузить 3D - Анимация", - "inputs": { - "height": { - "name": "высота" - }, - "image": { - "name": "изображение" - }, - "model_file": { - "name": "файл_модели" + "upload extra resources": { }, "width": { "name": "ширина" } }, - "outputs": { - "0": { - "name": "изображение" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "mask" + { + "tooltip": null }, - "2": { - "name": "путь_к_модели" + { + "tooltip": null }, - "3": { - "name": "нормаль" + { + "tooltip": null }, - "4": { - "name": "информация_о_камере" + { + "tooltip": null }, - "5": { - "name": "recording_video" + { + "tooltip": null } - } + ] }, "LoadAudio": { "display_name": "Загрузить аудио", @@ -4869,6 +6297,11 @@ "upload": { "name": "выберите файл для загрузки" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LoadImage": { @@ -4882,6 +6315,21 @@ } } }, + "LoadImageDataSetFromFolder": { + "display_name": "Загрузить набор изображений из папки", + "inputs": { + "folder": { + "name": "folder", + "tooltip": "Папка, из которой загружаются изображения." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Список загруженных изображений" + } + } + }, "LoadImageMask": { "display_name": "Загрузить изображение (как маску)", "inputs": { @@ -4900,6 +6348,8 @@ "description": "Загрузите изображение из папки вывода. При нажатии кнопки обновления, узел обновит список изображений и автоматически выберет первое изображение, что позволяет легко итерировать.", "display_name": "Загрузить изображение (из выходных данных)", "inputs": { + "Auto-refresh after generation": { + }, "image": { "name": "изображение" }, @@ -4910,41 +6360,22 @@ } } }, - "LoadImageSetFromFolderNode": { - "description": "Загружает пакет изображений из директории для обучения.", - "display_name": "Загрузить набор изображений из папки", + "LoadImageTextDataSetFromFolder": { + "display_name": "Загрузить набор изображений и текстов из папки", "inputs": { "folder": { "name": "folder", - "tooltip": "Папка для загрузки изображений." - }, - "resize_method": { - "name": "resize_method" + "tooltip": "Папка, из которой загружаются изображения." } - } - }, - "LoadImageTextSetFromFolderNode": { - "description": "Загружает пакет изображений и подписей из директории для обучения.", - "display_name": "Загрузить набор изображений и текста из папки", - "inputs": { - "clip": { - "name": "clip", - "tooltip": "Модель CLIP, используемая для кодирования текста." + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Список загруженных изображений" }, - "folder": { - "name": "folder", - "tooltip": "Папка для загрузки изображений." - }, - "height": { - "name": "height", - "tooltip": "Высота для изменения размера изображений. -1 означает использование исходной высоты." - }, - "resize_method": { - "name": "resize_method" - }, - "width": { - "name": "width", - "tooltip": "Ширина для изменения размера изображений. -1 означает использование исходной ширины." + "1": { + "name": "texts", + "tooltip": "Список текстовых подписей" } } }, @@ -4956,6 +6387,25 @@ } } }, + "LoadTrainingDataset": { + "display_name": "Загрузить обучающий набор данных", + "inputs": { + "folder_name": { + "name": "folder_name", + "tooltip": "Имя папки, содержащей сохранённый набор данных (внутри выходного каталога)." + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "Список словарей латентных пространств" + }, + "1": { + "name": "conditioning", + "tooltip": "Список списков условий" + } + } + }, "LoadVideo": { "display_name": "Загрузить видео", "inputs": { @@ -5027,7 +6477,6 @@ } }, "LoraModelLoader": { - "description": "Загрузить обученные веса LoRA из узла Train LoRA.", "display_name": "Загрузить модель LoRA", "inputs": { "lora": { @@ -5043,11 +6492,11 @@ "tooltip": "Степень модификации диффузионной модели. Это значение может быть отрицательным." } }, - "outputs": { - "0": { - "tooltip": "Модифицированная диффузионная модель." + "outputs": [ + { + "name": "model" } - } + ] }, "LoraSave": { "display_name": "Извлечь и сохранить LoRA", @@ -5075,14 +6524,15 @@ } }, "LossGraphNode": { - "description": "Строит график потерь и сохраняет его в выходной каталог.", "display_name": "Построить график потерь", "inputs": { "filename_prefix": { - "name": "префикс_имени_файла" + "name": "префикс_имени_файла", + "tooltip": "Префикс для сохранённого изображения графика потерь." }, "loss": { - "name": "потери" + "name": "потери", + "tooltip": "Карта потерь из обучающего узла." } } }, @@ -5388,6 +6838,50 @@ } } }, + "MakeTrainingDataset": { + "display_name": "Создать обучающий датасет", + "inputs": { + "clip": { + "name": "clip", + "tooltip": "Модель CLIP для кодирования текста в кондиционирование." + }, + "images": { + "name": "изображения", + "tooltip": "Список изображений для кодирования." + }, + "texts": { + "name": "тексты", + "tooltip": "Список текстовых подписей. Может быть длиной n (соответствует изображениям), 1 (повторяется для всех) или отсутствовать (используется пустая строка)." + }, + "vae": { + "name": "vae", + "tooltip": "Модель VAE для кодирования изображений в latent." + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "Список latent-словарей" + }, + "1": { + "name": "conditioning", + "tooltip": "Список списков кондиционирования" + } + } + }, + "ManualSigmas": { + "display_name": "ManualSigmas", + "inputs": { + "sigmas": { + "name": "сигмы" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "MaskComposite": { "display_name": "Составная маска", "inputs": { @@ -5406,6 +6900,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "MaskPreview": { @@ -5423,6 +6922,315 @@ "mask": { "name": "маска" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MergeImageLists": { + "display_name": "Объединить списки изображений", + "inputs": { + "images": { + "name": "изображения", + "tooltip": "Список изображений для обработки." + } + }, + "outputs": { + "0": { + "name": "изображения", + "tooltip": "Обработанные изображения" + } + } + }, + "MergeTextLists": { + "display_name": "Объединить списки текстов", + "inputs": { + "texts": { + "name": "тексты", + "tooltip": "Список текстов для обработки." + } + }, + "outputs": { + "0": { + "name": "тексты", + "tooltip": "Обработанные тексты" + } + } + }, + "MeshyAnimateModelNode": { + "description": "Применить определённое анимационное действие к ранее скелетированной персонажу.", + "display_name": "Meshy: Анимировать модель", + "inputs": { + "action_id": { + "name": "action_id", + "tooltip": "Посетите https://docs.meshy.ai/en/api/animation-library для списка доступных значений." + }, + "rig_task_id": { + "name": "rig_task_id" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + } + } + }, + "MeshyImageToModelNode": { + "display_name": "Meshy: Изображение в модель", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "image": { + "name": "image" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "Укажите режим позы для создаваемой модели." + }, + "seed": { + "name": "seed", + "tooltip": "Seed управляет тем, должен ли узел запускаться повторно; результаты недетерминированы независимо от seed." + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "Если установлено в false, возвращается необработанная треугольная сетка." + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "should_texture": { + "name": "should_texture", + "tooltip": "Определяет, будут ли сгенерированы текстуры. Если установить в false, этап текстурирования пропускается и возвращается сетка без текстур." + }, + "should_texture_enable_pbr": { + "name": "enable_pbr" + }, + "should_texture_texture_prompt": { + "name": "texture_prompt" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyMultiImageToModelNode": { + "display_name": "Meshy: Мульти-изображение в модель", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "Укажите режим позы для создаваемой модели." + }, + "seed": { + "name": "seed", + "tooltip": "Seed управляет тем, должен ли узел запускаться повторно; результаты недетерминированы независимо от seed." + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "Если установлено в false, возвращается необработанная треугольная сетка." + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "should_texture": { + "name": "should_texture", + "tooltip": "Определяет, будут ли сгенерированы текстуры. Если установить в false, этап текстурирования пропускается и возвращается сетка без текстур." + }, + "should_texture_enable_pbr": { + "name": "enable_pbr" + }, + "should_texture_texture_prompt": { + "name": "texture_prompt" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRefineNode": { + "description": "Улучшить ранее созданную черновую модель.", + "display_name": "Meshy: Улучшить Черновую Модель", + "inputs": { + "enable_pbr": { + "name": "enable_pbr", + "tooltip": "Генерировать PBR-карты (металличность, шероховатость, нормали) в дополнение к базовому цвету. Важно: установите значение false при использовании стиля Sculpture, так как стиль Sculpture создает собственные PBR-карты." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "model": { + "name": "model" + }, + "texture_image": { + "name": "texture_image", + "tooltip": "Одновременно можно использовать только один из параметров: 'texture_image' или 'texture_prompt'." + }, + "texture_prompt": { + "name": "texture_prompt", + "tooltip": "Введите текстовый запрос для управления процессом текстурирования. Максимум 600 символов. Не может использоваться одновременно с 'texture_image'." + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRigModelNode": { + "description": "Предоставляет персонажа с риггингом в стандартных форматах. Автоматический риггинг в настоящее время не подходит для моделей без текстур, не-гуманоидных объектов или гуманоидов с нечеткой структурой конечностей и тела.", + "display_name": "Meshy: Скелетная Анимация Модели", + "inputs": { + "height_meters": { + "name": "height_meters", + "tooltip": "Примерная высота модели персонажа в метрах. Это помогает для масштабирования и точности риггинга." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "texture_image": { + "name": "texture_image", + "tooltip": "UV-развернутая текстура базового цвета модели." + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "rig_task_id", + "tooltip": null + } + } + }, + "MeshyTextToModelNode": { + "display_name": "Meshy: Текст в Модель", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "Укажите режим позы для создаваемой модели." + }, + "prompt": { + "name": "prompt" + }, + "seed": { + "name": "seed", + "tooltip": "Seed управляет тем, должен ли узел запускаться повторно; результаты всегда недетерминированы, независимо от seed." + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "Если установлено значение false, возвращается необработанная треугольная сетка." + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "style": { + "name": "style" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyTextureNode": { + "display_name": "Meshy: Модель текстуры", + "inputs": { + "enable_original_uv": { + "name": "enable_original_uv", + "tooltip": "Использовать оригинальные UV модели вместо генерации новых UV. При включении Meshy сохраняет существующие текстуры из загруженной модели. Если у модели нет оригинальных UV, качество результата может быть ниже." + }, + "image_style": { + "name": "image_style", + "tooltip": "2D-изображение для направления процесса текстурирования. Нельзя использовать одновременно с 'text_style_prompt'." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "model": { + "name": "model" + }, + "pbr": { + "name": "pbr" + }, + "text_style_prompt": { + "name": "text_style_prompt", + "tooltip": "Опишите желаемый стиль текстуры объекта с помощью текста. Максимум 600 символов. Нельзя использовать одновременно с 'image_style'." + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } } }, "MinimaxHailuoVideoNode": { @@ -7866,6 +9674,52 @@ } } }, + "NormalizeImages": { + "display_name": "Нормализовать изображения", + "inputs": { + "images": { + "name": "изображения", + "tooltip": "Изображение для обработки." + }, + "mean": { + "name": "среднее", + "tooltip": "Среднее значение для нормализации." + }, + "std": { + "name": "стандартное отклонение", + "tooltip": "Стандартное отклонение для нормализации." + } + }, + "outputs": { + "0": { + "name": "изображения", + "tooltip": "Обработанные изображения" + } + } + }, + "NormalizeVideoLatentStart": { + "description": "Нормализует начальные кадры видео latent, чтобы их среднее значение и стандартное отклонение соответствовали последующим опорным кадрам. Помогает уменьшить различия между начальными кадрами и остальной частью видео.", + "display_name": "NormalizeVideoLatentStart", + "inputs": { + "latent": { + "name": "latent" + }, + "reference_frame_count": { + "name": "reference_frame_count", + "tooltip": "Количество кадров latent после начальных, используемых в качестве опорных" + }, + "start_frame_count": { + "name": "start_frame_count", + "tooltip": "Количество кадров latent для нормализации, начиная с первого" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, "OpenAIChatConfig": { "description": "Позволяет задать расширенные параметры конфигурации для узлов OpenAI Chat.", "display_name": "Расширенные настройки OpenAI ChatGPT", @@ -8015,6 +9869,9 @@ "name": "mask", "tooltip": "Необязательная маска для дорисовки (белые области будут заменены)" }, + "model": { + "name": "model" + }, "n": { "name": "n", "tooltip": "Сколько изображений сгенерировать" @@ -8373,265 +10230,6 @@ } } }, - "PikaImageToVideoNode2_2": { - "description": "Отправляет изображение и подсказку в Pika API v2.2 для генерации видео.", - "display_name": "Pika: преобразование изображения в видео", - "inputs": { - "control_after_generate": { - "name": "контроль после генерации" - }, - "duration": { - "name": "длительность" - }, - "image": { - "name": "изображение", - "tooltip": "Изображение для преобразования в видео" - }, - "negative_prompt": { - "name": "негативная подсказка" - }, - "prompt_text": { - "name": "текст подсказки" - }, - "resolution": { - "name": "разрешение" - }, - "seed": { - "name": "seed" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaScenesV2_2": { - "description": "Объединяйте ваши изображения для создания видео с содержащимися в них объектами. Загрузите несколько изображений в качестве ингредиентов и создайте высококачественное видео, включающее все из них.", - "display_name": "Pika Scenes (Видеокомпозиция изображений)", - "inputs": { - "aspect_ratio": { - "name": "aspect_ratio", - "tooltip": "Соотношение сторон (ширина / высота)" - }, - "control_after_generate": { - "name": "control after generate" - }, - "duration": { - "name": "duration" - }, - "image_ingredient_1": { - "name": "image_ingredient_1", - "tooltip": "Изображение, которое будет использовано как ингредиент для создания видео." - }, - "image_ingredient_2": { - "name": "image_ingredient_2", - "tooltip": "Изображение, которое будет использовано как ингредиент для создания видео." - }, - "image_ingredient_3": { - "name": "image_ingredient_3", - "tooltip": "Изображение, которое будет использовано как ингредиент для создания видео." - }, - "image_ingredient_4": { - "name": "image_ingredient_4", - "tooltip": "Изображение, которое будет использовано как ингредиент для создания видео." - }, - "image_ingredient_5": { - "name": "image_ingredient_5", - "tooltip": "Изображение, которое будет использовано как ингредиент для создания видео." - }, - "ingredients_mode": { - "name": "ingredients_mode" - }, - "negative_prompt": { - "name": "negative_prompt" - }, - "prompt_text": { - "name": "prompt_text" - }, - "resolution": { - "name": "resolution" - }, - "seed": { - "name": "seed" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaStartEndFrameNode2_2": { - "description": "Создайте видео, объединив первый и последний кадры. Загрузите два изображения, чтобы определить начальную и конечную точки, и позвольте ИИ создать плавный переход между ними.", - "display_name": "Pika: видео из начального и конечного кадров", - "inputs": { - "control_after_generate": { - "name": "control after generate" - }, - "duration": { - "name": "duration" - }, - "image_end": { - "name": "image_end", - "tooltip": "Последнее изображение для объединения." - }, - "image_start": { - "name": "image_start", - "tooltip": "Первое изображение для объединения." - }, - "negative_prompt": { - "name": "negative_prompt" - }, - "prompt_text": { - "name": "prompt_text" - }, - "resolution": { - "name": "resolution" - }, - "seed": { - "name": "seed" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaTextToVideoNode2_2": { - "description": "Отправляет текстовый запрос в Pika API v2.2 для генерации видео.", - "display_name": "Pika: Текст в видео", - "inputs": { - "aspect_ratio": { - "name": "соотношение сторон", - "tooltip": "Соотношение сторон (ширина / высота)" - }, - "control_after_generate": { - "name": "управление после генерации" - }, - "duration": { - "name": "длительность" - }, - "negative_prompt": { - "name": "negative_prompt" - }, - "prompt_text": { - "name": "prompt_text" - }, - "resolution": { - "name": "разрешение" - }, - "seed": { - "name": "seed" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikadditions": { - "description": "Добавьте любой объект или изображение в ваше видео. Загрузите видео и укажите, что вы хотите добавить, чтобы получить гармонично интегрированный результат.", - "display_name": "Pikadditions (Вставка объектов в видео)", - "inputs": { - "control_after_generate": { - "name": "контроль после генерации" - }, - "image": { - "name": "изображение", - "tooltip": "Изображение, которое будет добавлено в видео." - }, - "negative_prompt": { - "name": "негативный запрос" - }, - "prompt_text": { - "name": "текстовый запрос" - }, - "seed": { - "name": "seed" - }, - "video": { - "name": "видео", - "tooltip": "Видео, в которое будет добавлено изображение." - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikaffects": { - "description": "Создайте видео с определённым Pikaffect. Поддерживаемые Pikaffects: Cake-ify, Crumble, Crush, Decapitate, Deflate, Dissolve, Explode, Eye-pop, Inflate, Levitate, Melt, Peel, Poke, Squish, Ta-da, Tear", - "display_name": "Pikaffects (Видеоэффекты)", - "inputs": { - "control_after_generate": { - "name": "контроль после генерации" - }, - "image": { - "name": "изображение", - "tooltip": "Референсное изображение, к которому будет применён Pikaffect." - }, - "negative_prompt": { - "name": "негативный запрос" - }, - "pikaffect": { - "name": "pikaffect" - }, - "prompt_text": { - "name": "текст запроса" - }, - "seed": { - "name": "seed" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikaswaps": { - "description": "Заменяйте любой объект или область на вашем видео новым изображением или объектом. Определяйте области для замены с помощью маски или координат.", - "display_name": "Pika Swaps (Замена объектов на видео)", - "inputs": { - "control_after_generate": { - "name": "контроль после генерации" - }, - "image": { - "name": "изображение", - "tooltip": "Изображение, используемое для замены замаскированного объекта на видео." - }, - "mask": { - "name": "маска", - "tooltip": "Используйте маску для определения областей на видео, которые нужно заменить" - }, - "negative_prompt": { - "name": "негативный запрос" - }, - "prompt_text": { - "name": "текстовый запрос" - }, - "region_to_modify": { - "name": "область_для_изменения", - "tooltip": "Текстовое описание объекта / области для изменения." - }, - "seed": { - "name": "seed" - }, - "video": { - "name": "видео", - "tooltip": "Видео, в котором будет заменён объект." - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, "PixverseImageToVideoNode": { "description": "Синхронно генерирует видео на основе запроса и размера вывода.", "display_name": "PixVerse: изображение в видео", @@ -8786,6 +10384,11 @@ "steps": { "name": "шаги" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "PorterDuffImageComposite": { @@ -8819,6 +10422,9 @@ "Preview3D": { "display_name": "Предварительный просмотр 3D", "inputs": { + "bg_image": { + "name": "bg_image" + }, "camera_info": { "name": "информация_камеры" }, @@ -8830,22 +10436,13 @@ } } }, - "Preview3DAnimation": { - "display_name": "Предварительный просмотр 3D - Анимация", - "inputs": { - "camera_info": { - "name": "camera_info" - }, - "model_file": { - "name": "файл_модели" - } - } - }, "PreviewAny": { "display_name": "Предпросмотр любого", "inputs": { "preview": { }, + "previewMode": { + }, "source": { "name": "источник" } @@ -8985,6 +10582,36 @@ } } }, + "RandomCropImages": { + "display_name": "Случайное кадрирование изображений", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "height": { + "name": "height", + "tooltip": "Высота кадра." + }, + "images": { + "name": "images", + "tooltip": "Изображение для обработки." + }, + "seed": { + "name": "seed", + "tooltip": "Случайное зерно." + }, + "width": { + "name": "width", + "tooltip": "Ширина кадра." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Обработанные изображения" + } + } + }, "RandomNoise": { "display_name": "Случайный шум", "inputs": { @@ -8994,6 +10621,11 @@ "noise_seed": { "name": "сид_шума" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RebatchImages": { @@ -9034,6 +10666,11 @@ "audio": { "name": "аудио" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RecraftColorRGB": { @@ -9538,6 +11175,11 @@ "image": { "name": "изображение" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RepeatLatentBatch": { @@ -9551,6 +11193,51 @@ } } }, + "ReplaceText": { + "display_name": "Заменить текст", + "inputs": { + "find": { + "name": "find", + "tooltip": "Текст для поиска." + }, + "replace": { + "name": "replace", + "tooltip": "Текст для замены." + }, + "texts": { + "name": "texts", + "tooltip": "Текст для обработки." + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "Обработанные тексты" + } + } + }, + "ReplaceVideoLatentFrames": { + "display_name": "Заменить кадры видео latent", + "inputs": { + "destination": { + "name": "destination", + "tooltip": "Latent-назначение, в который будут заменены кадры." + }, + "index": { + "name": "index", + "tooltip": "Начальный индекс кадра latent в latent-назначении, куда будут вставлены кадры из источника. Отрицательные значения считаются с конца." + }, + "source": { + "name": "source", + "tooltip": "Latent-источник, предоставляющий кадры для вставки в latent-назначение. Если не указано, latent-назначение возвращается без изменений." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "RescaleCFG": { "display_name": "Масштабировать CFG", "inputs": { @@ -9580,6 +11267,104 @@ "target_width": { "name": "целевая_ширина" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ResizeImageMaskNode": { + "description": "Изменить размер изображения или mask с помощью различных методов масштабирования.", + "display_name": "Изменить размер изображения/маски", + "inputs": { + "input": { + "name": "input" + }, + "resize_type": { + "name": "resize_type", + "tooltip": "Выберите способ изменения размера: по точным размерам, коэффициенту масштабирования, подгонке под другое изображение и т.д." + }, + "resize_type_crop": { + "name": "обрезка" + }, + "resize_type_height": { + "name": "высота" + }, + "resize_type_width": { + "name": "ширина" + }, + "scale_method": { + "name": "scale_method", + "tooltip": "Алгоритм интерполяции. 'area' лучше всего подходит для уменьшения, 'lanczos' — для увеличения, 'nearest-exact' — для пиксель-арта." + } + }, + "outputs": { + "0": { + "name": "resized", + "tooltip": null + } + } + }, + "ResizeImagesByLongerEdge": { + "display_name": "Изменить размер изображений по длинной стороне", + "inputs": { + "images": { + "name": "images", + "tooltip": "Изображение для обработки." + }, + "longer_edge": { + "name": "longer_edge", + "tooltip": "Целевая длина для длинной стороны." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Обработанные изображения" + } + } + }, + "ResizeImagesByShorterEdge": { + "display_name": "Изменить размер изображений по короткой стороне", + "inputs": { + "images": { + "name": "images", + "tooltip": "Изображение для обработки." + }, + "shorter_edge": { + "name": "shorter_edge", + "tooltip": "Целевая длина для короткой стороны." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Обработанные изображения" + } + } + }, + "ResolutionBucket": { + "display_name": "Группировка по разрешению", + "inputs": { + "conditioning": { + "name": "conditioning", + "tooltip": "Список списков условий (должен соответствовать длине latents)." + }, + "latents": { + "name": "latents", + "tooltip": "Список латентных словарей для группировки по разрешению." + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "Список сгруппированных латентных словарей, по одному на каждую группу разрешения." + }, + "1": { + "name": "conditioning", + "tooltip": "Список списков условий, по одному на каждую группу разрешения." + } } }, "Rodin3D_Detail": { @@ -9833,6 +11618,11 @@ "steps": { "name": "шаги" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SD_4XUpscale_Conditioning": { @@ -9986,14 +11776,14 @@ "name": "сигмы" } }, - "outputs": { - "0": { - "name": "выход" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "очищенный_выход" + { + "tooltip": null } - } + ] }, "SamplerCustomAdvanced": { "display_name": "Пользовательский сэмплер (Расширенный)", @@ -10014,14 +11804,14 @@ "name": "сигмы" } }, - "outputs": { - "0": { - "name": "выход" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "очищенный_выход" + { + "tooltip": null } - } + ] }, "SamplerDPMAdaptative": { "display_name": "Адаптивный сэмплер DPM", @@ -10056,6 +11846,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_2M_SDE": { @@ -10073,6 +11868,11 @@ "solver_type": { "name": "тип_решателя" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_2S_Ancestral": { @@ -10084,6 +11884,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_3M_SDE": { @@ -10098,6 +11903,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_SDE": { @@ -10115,6 +11925,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerER_SDE": { @@ -10133,6 +11948,11 @@ "solver_type": { "name": "тип_решателя" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerAncestral": { @@ -10144,6 +11964,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerAncestralCFGPP": { @@ -10155,6 +11980,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerCFGpp": { @@ -10195,6 +12025,11 @@ "order": { "name": "порядок" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerSASolver": { @@ -10227,6 +12062,37 @@ "use_pece": { "name": "использовать_pece" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerSEEDS2": { + "description": "Этот узел сэмплера может представлять несколько сэмплеров:\n\nseeds_2\n- настройка по умолчанию\n\nexp_heun_2_x0\n- solver_type=phi_2, r=1.0, eta=0.0\n\nexp_heun_2_x0_sde\n- solver_type=phi_2, r=1.0, eta=1.0, s_noise=1.0", + "display_name": "SamplerSEEDS2", + "inputs": { + "eta": { + "name": "eta", + "tooltip": "Сила стохастичности" + }, + "r": { + "name": "r", + "tooltip": "Относительный размер шага для промежуточной стадии (узел c2)" + }, + "s_noise": { + "name": "s_noise", + "tooltip": "Множитель шума SDE" + }, + "solver_type": { + "name": "solver_type" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplingPercentToSigma": { @@ -10243,11 +12109,11 @@ "name": "процент_дискретизации" } }, - "outputs": { - "0": { - "name": "значение_сигмы" + "outputs": [ + { + "tooltip": null } - } + ] }, "SaveAnimatedPNG": { "display_name": "Сохранить анимированный PNG", @@ -10365,6 +12231,44 @@ } } }, + "SaveImageDataSetToFolder": { + "display_name": "Сохранить набор изображений в папку", + "inputs": { + "filename_prefix": { + "name": "filename_prefix", + "tooltip": "Префикс для имен файлов сохранённых изображений." + }, + "folder_name": { + "name": "folder_name", + "tooltip": "Имя папки для сохранения изображений (внутри выходного каталога)." + }, + "images": { + "name": "images", + "tooltip": "Список изображений для сохранения." + } + } + }, + "SaveImageTextDataSetToFolder": { + "display_name": "Сохранить набор изображений и текстов в папку", + "inputs": { + "filename_prefix": { + "name": "filename_prefix", + "tooltip": "Префикс для имен файлов сохранённых изображений." + }, + "folder_name": { + "name": "folder_name", + "tooltip": "Имя папки для сохранения изображений (внутри выходного каталога)." + }, + "images": { + "name": "images", + "tooltip": "Список изображений для сохранения." + }, + "texts": { + "name": "texts", + "tooltip": "Список текстовых подписей для сохранения." + } + } + }, "SaveImageWebsocket": { "display_name": "Сохранить изображение через веб-сокет", "inputs": { @@ -10384,7 +12288,7 @@ } } }, - "SaveLoRANode": { + "SaveLoRA": { "display_name": "Сохранить веса LoRA", "inputs": { "lora": { @@ -10392,12 +12296,12 @@ "tooltip": "Модель LoRA для сохранения. Не используйте модель со слоями LoRA." }, "prefix": { - "name": "префикс", - "tooltip": "Префикс для сохраняемого файла LoRA." + "name": "prefix", + "tooltip": "Префикс для сохранённого файла LoRA." }, "steps": { - "name": "шаги", - "tooltip": "Опционально: Количество шагов, на которых обучалась LoRA, используется для именования сохраняемого файла." + "name": "steps", + "tooltip": "Необязательно: количество шагов, на которые обучалась LoRA, используется для имени файла." } } }, @@ -10414,6 +12318,27 @@ } } }, + "SaveTrainingDataset": { + "display_name": "Сохранить обучающий набор данных", + "inputs": { + "conditioning": { + "name": "conditioning", + "tooltip": "Список списков conditioning из MakeTrainingDataset." + }, + "folder_name": { + "name": "folder_name", + "tooltip": "Имя папки для сохранения набора данных (внутри выходного каталога)." + }, + "latents": { + "name": "latents", + "tooltip": "Список словарей latent из MakeTrainingDataset." + }, + "shard_size": { + "name": "shard_size", + "tooltip": "Количество образцов в каждом файле-части." + } + } + }, "SaveVideo": { "description": "Сохраняет входные изображения в вашу папку вывода ComfyUI.", "display_name": "Сохранить видео", @@ -10534,6 +12459,11 @@ "sigmas": { "name": "сигмы" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SetHookKeyframes": { @@ -10574,6 +12504,58 @@ } } }, + "ShuffleDataset": { + "display_name": "Перемешать набор изображений", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images", + "tooltip": "Список изображений для обработки." + }, + "seed": { + "name": "seed", + "tooltip": "Случайное зерно." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Обработанные изображения" + } + } + }, + "ShuffleImageTextDataset": { + "display_name": "Перемешать набор изображений и текстов", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images", + "tooltip": "Список изображений для перемешивания." + }, + "seed": { + "name": "seed", + "tooltip": "Случайное зерно." + }, + "texts": { + "name": "texts", + "tooltip": "Список текстов для перемешивания." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Перемешанные изображения" + }, + "1": { + "name": "texts", + "tooltip": "Перемешанные тексты" + } + } + }, "SkipLayerGuidanceDiT": { "description": "Универсальная версия ноды SkipLayerGuidance, которую можно использовать на любой модели DiT.", "display_name": "Пропустить руководство по слоям DiT", @@ -10670,6 +12652,11 @@ "width": { "name": "ширина" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SplitAudioChannels": { @@ -10680,14 +12667,14 @@ "name": "аудио" } }, - "outputs": { - "0": { - "name": "левый" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "правый" + { + "tooltip": null } - } + ] }, "SplitImageWithAlpha": { "display_name": "Разделить изображение с альфа-каналом", @@ -10715,14 +12702,14 @@ "name": "шаг" } }, - "outputs": { - "0": { - "name": "высокие_сигмы" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "низкие_сигмы" + { + "tooltip": null } - } + ] }, "SplitSigmasDenoise": { "display_name": "Разделить сигмы для удаления шума", @@ -10734,14 +12721,14 @@ "name": "сигмы" } }, - "outputs": { - "0": { - "name": "высокие_сигмы" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "низкие_сигмы" + { + "tooltip": null } - } + ] }, "StabilityAudioInpaint": { "description": "Преобразует часть существующего аудиосэмпла с использованием текстовых инструкций.", @@ -11343,6 +13330,21 @@ } } }, + "StripWhitespace": { + "display_name": "Удалить пробелы", + "inputs": { + "texts": { + "name": "тексты", + "tooltip": "Текст для обработки." + } + }, + "outputs": { + "0": { + "name": "тексты", + "tooltip": "Обработанные тексты" + } + } + }, "StyleModelApply": { "display_name": "Применить модель стиля", "inputs": { @@ -11428,6 +13430,84 @@ } } }, + "TencentImageToModelNode": { + "display_name": "Hunyuan3D: Изображение(я) в модель (Pro)", + "inputs": { + "control_after_generate": { + "name": "контроль после генерации" + }, + "face_count": { + "name": "количество граней" + }, + "generate_type": { + "name": "тип генерации" + }, + "generate_type_pbr": { + "name": "pbr" + }, + "image": { + "name": "изображение" + }, + "image_back": { + "name": "изображение сзади" + }, + "image_left": { + "name": "изображение слева" + }, + "image_right": { + "name": "изображение справа" + }, + "model": { + "name": "модель", + "tooltip": "Опция LowPoly недоступна для модели `3.1`." + }, + "seed": { + "name": "seed", + "tooltip": "Seed управляет тем, будет ли узел запускаться повторно; результаты всегда недетерминированы, независимо от seed." + } + }, + "outputs": { + "0": { + "name": "файл_модели", + "tooltip": null + } + } + }, + "TencentTextToModelNode": { + "display_name": "Hunyuan3D: Текст в модель (Pro)", + "inputs": { + "control_after_generate": { + "name": "контроль после генерации" + }, + "face_count": { + "name": "количество граней" + }, + "generate_type": { + "name": "тип генерации" + }, + "generate_type_pbr": { + "name": "pbr" + }, + "model": { + "name": "модель", + "tooltip": "Опция LowPoly недоступна для модели `3.1`." + }, + "prompt": { + "name": "промпт", + "tooltip": "Поддерживается до 1024 символов." + }, + "seed": { + "name": "seed", + "tooltip": "Seed управляет тем, будет ли узел запускаться повторно; результаты всегда недетерминированы, независимо от seed." + } + }, + "outputs": { + "0": { + "name": "файл_модели", + "tooltip": null + } + } + }, "TextEncodeAceStepAudio": { "display_name": "TextEncodeAceStepAudio", "inputs": { @@ -11523,6 +13603,70 @@ } } }, + "TextEncodeZImageOmni": { + "display_name": "TextEncodeZImageOmni", + "inputs": { + "auto_resize_images": { + "name": "автоматическое_изменение_размера_изображений" + }, + "clip": { + "name": "clip" + }, + "image1": { + "name": "изображение1" + }, + "image2": { + "name": "изображение2" + }, + "image3": { + "name": "изображение3" + }, + "image_encoder": { + "name": "кодировщик_изображения" + }, + "prompt": { + "name": "промпт" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TextToLowercase": { + "display_name": "Преобразовать текст в нижний регистр", + "inputs": { + "texts": { + "name": "тексты", + "tooltip": "Текст для обработки." + } + }, + "outputs": { + "0": { + "name": "тексты", + "tooltip": "Обработанные тексты" + } + } + }, + "TextToUppercase": { + "display_name": "Преобразовать текст в верхний регистр", + "inputs": { + "texts": { + "name": "тексты", + "tooltip": "Текст для обработки." + } + }, + "outputs": { + "0": { + "name": "тексты", + "tooltip": "Обработанные тексты" + } + } + }, "ThresholdMask": { "display_name": "Пороговая маска", "inputs": { @@ -11532,6 +13676,11 @@ "value": { "name": "значение" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "TomePatchModel": { @@ -11550,6 +13699,118 @@ } } }, + "TopazImageEnhance": { + "description": "Промышленный стандарт увеличения и улучшения изображений.", + "display_name": "Topaz Улучшение изображения", + "inputs": { + "color_preservation": { + "name": "сохранение цвета", + "tooltip": "Сохранять оригинальные цвета." + }, + "creativity": { + "name": "креативность" + }, + "crop_to_fill": { + "name": "обрезать до заполнения", + "tooltip": "По умолчанию изображение добавляется с полями, если соотношение сторон отличается. Включите, чтобы обрезать изображение до заполнения выходных размеров." + }, + "face_enhancement": { + "name": "улучшение лица", + "tooltip": "Улучшить лица (если присутствуют) во время обработки." + }, + "face_enhancement_creativity": { + "name": "креативность улучшения лица", + "tooltip": "Установить уровень креативности для улучшения лица." + }, + "face_enhancement_strength": { + "name": "степень улучшения лица", + "tooltip": "Контролирует, насколько четкими будут улучшенные лица по сравнению с фоном." + }, + "face_preservation": { + "name": "сохранение лица", + "tooltip": "Сохранять идентичность лица объекта." + }, + "image": { + "name": "изображение" + }, + "model": { + "name": "модель" + }, + "output_height": { + "name": "выходная высота", + "tooltip": "Нулевое значение означает вывод в той же высоте, что и оригинал, или output_width." + }, + "output_width": { + "name": "выходная ширина", + "tooltip": "Нулевое значение означает автоматический расчет (обычно это будет исходный размер или output_height, если указано)." + }, + "prompt": { + "name": "промпт", + "tooltip": "Необязательный текстовый промпт для творческого управления увеличением." + }, + "subject_detection": { + "name": "обнаружение объекта" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TopazVideoEnhance": { + "description": "Оживите видео с помощью мощных технологий увеличения разрешения и восстановления.", + "display_name": "Topaz Video Enhance", + "inputs": { + "dynamic_compression_level": { + "name": "уровень динамического сжатия", + "tooltip": "Уровень CQP." + }, + "interpolation_duplicate": { + "name": "удаление дубликатов кадров", + "tooltip": "Анализировать входное видео на наличие дублирующихся кадров и удалять их." + }, + "interpolation_duplicate_threshold": { + "name": "порог дубликатов кадров", + "tooltip": "Чувствительность обнаружения дублирующихся кадров." + }, + "interpolation_enabled": { + "name": "включить интерполяцию" + }, + "interpolation_frame_rate": { + "name": "частота кадров интерполяции", + "tooltip": "Частота кадров на выходе." + }, + "interpolation_model": { + "name": "модель интерполяции" + }, + "interpolation_slowmo": { + "name": "замедление интерполяции", + "tooltip": "Фактор замедления, применяемый к входному видео. Например, 2 делает выход в два раза медленнее и удваивает длительность." + }, + "upscaler_creativity": { + "name": "креативность увеличителя", + "tooltip": "Уровень креативности (применяется только к Starlight (Astra) Creative)." + }, + "upscaler_enabled": { + "name": "включить увеличитель" + }, + "upscaler_model": { + "name": "модель увеличителя" + }, + "upscaler_resolution": { + "name": "разрешение увеличителя" + }, + "video": { + "name": "видео" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "TorchCompileModel": { "display_name": "Скомпилировать модель Torch", "inputs": { @@ -11577,6 +13838,10 @@ "name": "размер пакета", "tooltip": "Размер пакета, используемый для обучения." }, + "bucket_mode": { + "name": "режим корзины разрешений", + "tooltip": "Включить режим корзины разрешений. При включении ожидает предварительно разбитые латенты от узла ResolutionBucket." + }, "control_after_generate": { "name": "управление после генерации" }, @@ -11637,20 +13902,20 @@ "tooltip": "Тип данных, используемый для обучения." } }, - "outputs": { - "0": { - "name": "модель с lora" + "outputs": [ + { + "tooltip": "Модель с применённой LoRA" }, - "1": { - "name": "lora" + { + "tooltip": "Веса LoRA" }, - "2": { - "name": "потери" + { + "tooltip": "История потерь" }, - "3": { - "name": "шаги" + { + "tooltip": "Всего шагов обучения" } - } + ] }, "TrimAudioDuration": { "description": "Обрезать аудио тензор в выбранном временном диапазоне.", @@ -11667,6 +13932,11 @@ "name": "start_index", "tooltip": "Время начала в секундах, может быть отрицательным для отсчёта с конца (поддерживает доли секунд)." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "TrimVideoLatent": { @@ -11708,23 +13978,62 @@ "TripoConversionNode": { "display_name": "Tripo: Конвертировать модель", "inputs": { + "animate_in_place": { + "name": "анимировать на месте" + }, + "bake": { + "name": "запечь" + }, + "export_orientation": { + "name": "экспортировать ориентацию" + }, + "export_vertex_colors": { + "name": "экспортировать цвета вершин" + }, "face_limit": { "name": "face_limit" }, + "fbx_preset": { + "name": "FBX-пресет" + }, + "flatten_bottom": { + "name": "выравнивание низа" + }, + "flatten_bottom_threshold": { + "name": "порог выравнивания низа" + }, + "force_symmetry": { + "name": "принудительная симметрия" + }, "format": { "name": "format" }, "original_model_task_id": { "name": "original_model_task_id" }, + "pack_uv": { + "name": "упаковать UV" + }, + "part_names": { + "name": "имена частей" + }, + "pivot_to_center_bottom": { + "name": "центрировать нижнюю точку" + }, "quad": { "name": "quad" }, + "scale_factor": { + "name": "коэффициент масштабирования" + }, "texture_format": { "name": "texture_format" }, "texture_size": { "name": "texture_size" + }, + "with_animation": { + "name": "с анимацией" } } }, @@ -11734,6 +14043,9 @@ "face_limit": { "name": "face_limit" }, + "geometry_quality": { + "name": "качество геометрии" + }, "image": { "name": "image" }, @@ -11786,6 +14098,9 @@ "face_limit": { "name": "лимит_лиц" }, + "geometry_quality": { + "name": "качество геометрии" + }, "image": { "name": "image" }, @@ -11903,6 +14218,9 @@ "face_limit": { "name": "face_limit" }, + "geometry_quality": { + "name": "качество геометрии" + }, "image_seed": { "name": "image_seed" }, @@ -11981,6 +14299,25 @@ } } }, + "TruncateText": { + "display_name": "Обрезать текст", + "inputs": { + "max_length": { + "name": "максимальная длина", + "tooltip": "Максимальная длина текста." + }, + "texts": { + "name": "тексты", + "tooltip": "Текст для обработки." + } + }, + "outputs": { + "0": { + "name": "тексты", + "tooltip": "Обработанные тексты" + } + } + }, "UNETLoader": { "display_name": "Загрузить модель диффузии", "inputs": { @@ -12122,6 +14459,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEDecodeHunyuan3D": { @@ -12139,6 +14481,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEDecodeTiled": { @@ -12186,6 +14533,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEEncodeForInpaint": { @@ -12264,6 +14616,63 @@ "steps": { "name": "шаги" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Veo3FirstLastFrameNode": { + "description": "Генерировать видео по текстовому описанию и первым и последним кадрам.", + "display_name": "Google Veo 3: от первого до последнего кадра в видео", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "Соотношение сторон выходного видео" + }, + "control_after_generate": { + "name": "control after generate" + }, + "duration": { + "name": "duration", + "tooltip": "Длительность выходного видео в секундах" + }, + "first_frame": { + "name": "first_frame", + "tooltip": "Начальный кадр" + }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "Сгенерировать аудио для видео." + }, + "last_frame": { + "name": "last_frame", + "tooltip": "Конечный кадр" + }, + "model": { + "name": "model" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Негативный текстовый запрос для указания, чего избегать в видео" + }, + "prompt": { + "name": "prompt", + "tooltip": "Текстовое описание видео" + }, + "resolution": { + "name": "resolution" + }, + "seed": { + "name": "seed", + "tooltip": "Сид для генерации видео" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Veo3VideoGenerationNode": { @@ -12392,6 +14801,166 @@ } } }, + "Vidu2ImageToVideoNode": { + "description": "Создаёт видео из изображения и необязательной текстовой подсказки.", + "display_name": "Vidu2: генерация видео из изображения", + "inputs": { + "control_after_generate": { + "name": "управление после генерации" + }, + "duration": { + "name": "длительность" + }, + "image": { + "name": "изображение", + "tooltip": "Изображение, которое будет использовано в качестве первого кадра сгенерированного видео." + }, + "model": { + "name": "модель" + }, + "movement_amplitude": { + "name": "амплитуда движения", + "tooltip": "Амплитуда движения объектов в кадре." + }, + "prompt": { + "name": "подсказка", + "tooltip": "Необязательная текстовая подсказка для генерации видео (максимум 2000 символов)." + }, + "resolution": { + "name": "разрешение" + }, + "seed": { + "name": "seed" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2ReferenceVideoNode": { + "description": "Создаёт видео из нескольких референсных изображений и подсказки.", + "display_name": "Vidu2: генерация видео по референсам", + "inputs": { + "aspect_ratio": { + "name": "соотношение сторон" + }, + "audio": { + "name": "аудио", + "tooltip": "Если включено, видео будет содержать сгенерированную речь и фоновую музыку на основе подсказки." + }, + "control_after_generate": { + "name": "управление после генерации" + }, + "duration": { + "name": "длительность" + }, + "model": { + "name": "модель" + }, + "movement_amplitude": { + "name": "амплитуда движения", + "tooltip": "Амплитуда движения объектов в кадре." + }, + "prompt": { + "name": "подсказка", + "tooltip": "Если включено, видео будет содержать сгенерированную речь и фоновую музыку на основе подсказки." + }, + "resolution": { + "name": "разрешение" + }, + "seed": { + "name": "seed" + }, + "subjects": { + "name": "объекты", + "tooltip": "Для каждого объекта предоставьте до 3 референсных изображений (всего до 7 изображений для всех объектов). Ссылайтесь на них в подсказках через @subject{subject_id}." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2StartEndToVideoNode": { + "description": "Создайте видео из начального кадра, конечного кадра и текстового запроса.", + "display_name": "Vidu2 Генерация видео по начальному и конечному кадрам", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "duration": { + "name": "duration" + }, + "end_frame": { + "name": "end_frame" + }, + "first_frame": { + "name": "first_frame" + }, + "model": { + "name": "model" + }, + "movement_amplitude": { + "name": "movement_amplitude", + "tooltip": "Амплитуда движения объектов в кадре." + }, + "prompt": { + "name": "prompt", + "tooltip": "Описание запроса (максимум 2000 символов)." + }, + "resolution": { + "name": "resolution" + }, + "seed": { + "name": "seed" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2TextToVideoNode": { + "description": "Создайте видео по текстовому запросу", + "display_name": "Vidu2 Генерация видео по тексту", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "background_music": { + "name": "background_music", + "tooltip": "Добавлять ли фоновую музыку к сгенерированному видео." + }, + "control_after_generate": { + "name": "control after generate" + }, + "duration": { + "name": "duration" + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "Текстовое описание для генерации видео, максимум 2000 символов." + }, + "resolution": { + "name": "resolution" + }, + "seed": { + "name": "seed" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "ViduImageToVideoNode": { "description": "Генерация видео из изображения и необязательного промпта", "display_name": "Vidu Image To Video Generation", @@ -12580,6 +15149,11 @@ "voxel": { "name": "voxel" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VoxelToMeshBasic": { @@ -12591,6 +15165,11 @@ "voxel": { "name": "воксель" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Wan22FunControlToVideo": { @@ -12870,6 +15449,10 @@ "name": "шаг_контекста", "tooltip": "Шаг контекстного окна; применимо только к равномерным расписаниям." }, + "freenoise": { + "name": "freenoise", + "tooltip": "Применять ли FreeNoise для перемешивания шума, улучшает смешивание окон." + }, "fuse_method": { "name": "метод_объединения", "tooltip": "Метод объединения контекстных окон." @@ -13210,6 +15793,10 @@ "name": "сид", "tooltip": "Сид для использования при генерации." }, + "shot_type": { + "name": "shot_type", + "tooltip": "Указывает тип кадра для сгенерированного видео, то есть будет ли видео одним непрерывным кадром или несколькими с переходами. Этот параметр действует только если prompt_extend установлен в True." + }, "watermark": { "name": "водяной_знак", "tooltip": "Добавлять ли водяной знак \"Сгенерировано ИИ\" к результату." @@ -13221,6 +15808,196 @@ } } }, + "WanInfiniteTalkToVideo": { + "display_name": "WanInfiniteTalkToVideo", + "inputs": { + "audio_encoder_output_1": { + "name": "выход аудиоэнкодера 1" + }, + "audio_scale": { + "name": "масштаб аудио" + }, + "clip_vision_output": { + "name": "выход clip vision" + }, + "height": { + "name": "высота" + }, + "length": { + "name": "длина" + }, + "mode": { + "name": "режим" + }, + "model": { + "name": "модель" + }, + "model_patch": { + "name": "патч модели" + }, + "motion_frame_count": { + "name": "количество кадров движения", + "tooltip": "Количество предыдущих кадров, используемых как контекст движения." + }, + "negative": { + "name": "негативный" + }, + "positive": { + "name": "позитивный" + }, + "previous_frames": { + "name": "предыдущие кадры" + }, + "start_image": { + "name": "стартовое изображение" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "ширина" + } + }, + "outputs": { + "0": { + "name": "модель", + "tooltip": null + }, + "1": { + "name": "позитивный", + "tooltip": null + }, + "2": { + "name": "негативный", + "tooltip": null + }, + "3": { + "name": "latent", + "tooltip": null + }, + "4": { + "name": "обрезанное изображение", + "tooltip": null + } + } + }, + "WanMoveConcatTrack": { + "display_name": "WanMoveConcatTrack", + "inputs": { + "tracks_1": { + "name": "tracks_1" + }, + "tracks_2": { + "name": "tracks_2" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanMoveTrackToVideo": { + "display_name": "WanMoveTrackToVideo", + "inputs": { + "batch_size": { + "name": "размер_пакета" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "height": { + "name": "высота" + }, + "length": { + "name": "длина" + }, + "negative": { + "name": "negative" + }, + "positive": { + "name": "positive" + }, + "start_image": { + "name": "стартовое_изображение" + }, + "strength": { + "name": "интенсивность", + "tooltip": "Сила трековой кондиционировки." + }, + "tracks": { + "name": "треки" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "ширина" + } + }, + "outputs": { + "0": { + "name": "positive", + "tooltip": null + }, + "1": { + "name": "negative", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "WanMoveTracksFromCoords": { + "display_name": "WanMoveTracksFromCoords", + "inputs": { + "track_coords": { + "name": "track_coords" + }, + "track_mask": { + "name": "track_mask" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "track_length", + "tooltip": null + } + } + }, + "WanMoveVisualizeTracks": { + "display_name": "WanMoveVisualizeTracks", + "inputs": { + "circle_size": { + "name": "размер_круга" + }, + "images": { + "name": "изображения" + }, + "line_resolution": { + "name": "разрешение_линии" + }, + "line_width": { + "name": "толщина_линии" + }, + "opacity": { + "name": "непрозрачность" + }, + "tracks": { + "name": "треки" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WanPhantomSubjectToVideo": { "display_name": "WanPhantomSubjectToVideo", "inputs": { @@ -13268,6 +16045,51 @@ } } }, + "WanReferenceVideoApi": { + "description": "Используйте персонажа и голос из входных видео, чтобы с помощью промпта сгенерировать новое видео с сохранением целостности персонажа.", + "display_name": "Wan Reference to Video", + "inputs": { + "control_after_generate": { + "name": "контроль_после_генерации" + }, + "duration": { + "name": "длительность" + }, + "model": { + "name": "модель" + }, + "negative_prompt": { + "name": "негативный_промпт", + "tooltip": "Негативный промпт, описывающий, чего следует избегать." + }, + "prompt": { + "name": "промпт", + "tooltip": "Промпт, описывающий элементы и визуальные особенности. Поддерживает английский и китайский языки. Используйте идентификаторы, такие как `character1` и `character2`, чтобы ссылаться на референсных персонажей." + }, + "reference_videos": { + "name": "референсные_видео" + }, + "seed": { + "name": "seed" + }, + "shot_type": { + "name": "тип_кадра", + "tooltip": "Указывает тип кадра для генерируемого видео: один непрерывный кадр или несколько с монтажом." + }, + "size": { + "name": "размер" + }, + "watermark": { + "name": "водяной_знак", + "tooltip": "Добавлять ли AI-сгенерированный водяной знак к результату." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WanSoundImageToVideo": { "display_name": "WanSoundImageToVideo", "inputs": { @@ -13446,6 +16268,10 @@ "name": "seed", "tooltip": "Сид для генерации." }, + "shot_type": { + "name": "тип_кадра", + "tooltip": "Указывает тип кадра для генерируемого видео: один непрерывный кадр или несколько с монтажом. Этот параметр действует только если prompt_extend установлен в True." + }, "size": { "name": "size" }, @@ -13571,6 +16397,43 @@ } } }, + "WavespeedFlashVSRNode": { + "description": "Быстрый и качественный апскейлер видео, повышающий разрешение и восстанавливающий чёткость для низкокачественных или размытых кадров.", + "display_name": "FlashVSR Видео Апскейл", + "inputs": { + "target_resolution": { + "name": "целевое_разрешение" + }, + "video": { + "name": "видео" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WavespeedImageUpscaleNode": { + "description": "Увеличьте разрешение и качество изображения, апскейл фотографий до 4K или 8K для чётких и детализированных результатов.", + "display_name": "WaveSpeed Апскейл Изображения", + "inputs": { + "image": { + "name": "изображение" + }, + "model": { + "name": "model" + }, + "target_resolution": { + "name": "целевое_разрешение" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WebcamCapture": { "display_name": "Захват с веб-камеры", "inputs": { @@ -13590,6 +16453,32 @@ } } }, + "ZImageFunControlnet": { + "display_name": "ZImageFunControlnet", + "inputs": { + "image": { + "name": "изображение" + }, + "inpaint_image": { + "name": "inpaint_изображение" + }, + "mask": { + "name": "mask" + }, + "model": { + "name": "модель" + }, + "model_patch": { + "name": "model_patch" + }, + "strength": { + "name": "интенсивность" + }, + "vae": { + "name": "vae" + } + } + }, "unCLIPCheckpointLoader": { "display_name": "Загрузчик контрольной точки unCLIP", "inputs": { @@ -13614,5 +16503,19 @@ "name": "сила" } } + }, + "wanBlockSwap": { + "description": "NOP", + "display_name": "wanBlockSwap", + "inputs": { + "model": { + "name": "model" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } } } diff --git a/src/locales/ru/settings.json b/src/locales/ru/settings.json index 675dfd7c3..9848c4382 100644 --- a/src/locales/ru/settings.json +++ b/src/locales/ru/settings.json @@ -29,12 +29,26 @@ "name": "Фоновое изображение холста", "tooltip": "URL изображения для фона холста. Вы можете кликнуть правой кнопкой мыши на изображении в панели результатов и выбрать «Установить как фон», чтобы использовать его." }, + "Comfy_Canvas_LeftMouseClickBehavior": { + "name": "Поведение при клике левой кнопкой мыши", + "options": { + "Panning": "Перемещение", + "Select": "Выбрать" + } + }, + "Comfy_Canvas_MouseWheelScroll": { + "name": "Прокрутка колесиком мыши", + "options": { + "Panning": "Перемещение", + "Zoom in/out": "Приближение/отдаление" + } + }, "Comfy_Canvas_NavigationMode": { "name": "Режим навигации по холсту", "options": { + "Custom": "Пользовательское", "Drag Navigation": "Перетаскивание", - "Standard (New)": "Стандартный (новый)", - "Custom": "Пользовательское" + "Standard (New)": "Стандартный (новый)" } }, "Comfy_Canvas_SelectionToolbox": { @@ -65,6 +79,17 @@ "Comfy_EnableWorkflowViewRestore": { "name": "Сохранять и восстанавливать положение и уровень масштабирования холста в рабочих процессах" }, + "Comfy_Execution_PreviewMethod": { + "name": "Метод живого предпросмотра", + "options": { + "auto": "auto", + "default": "default", + "latent2rgb": "latent2rgb", + "none": "none", + "taesd": "taesd" + }, + "tooltip": "Метод живого предпросмотра во время генерации изображения. «default» использует настройку CLI сервера." + }, "Comfy_FloatRoundingPrecision": { "name": "Количество знаков после запятой для округления плавающего виджета [0 = авто].", "tooltip": "(требуется перезагрузка страницы)" @@ -86,6 +111,10 @@ "None": "Нет" } }, + "Comfy_Graph_LiveSelection": { + "name": "Живое выделение", + "tooltip": "Если включено, узлы выделяются/снимаются в реальном времени при перетаскивании прямоугольника выделения, как в других графических редакторах." + }, "Comfy_Graph_ZoomSpeed": { "name": "Скорость зума холста" }, @@ -152,6 +181,15 @@ "name": "Минимальная интенсивность света", "tooltip": "Устанавливает минимально допустимое значение интенсивности света для 3D-сцен. Определяет нижний предел яркости, который можно установить при настройке освещения в любом 3D-виджете." }, + "Comfy_Load3D_PLYEngine": { + "name": "Движок PLY", + "options": { + "fastply": "fastply", + "sparkjs": "sparkjs", + "threejs": "threejs" + }, + "tooltip": "Выберите движок для загрузки PLY-файлов. «threejs» использует встроенный PLYLoader Three.js (лучше всего подходит для файлов сетки PLY). «fastply» использует оптимизированный загрузчик для ASCII PLY-файлов облака точек. «sparkjs» использует Spark.js для PLY-файлов 3D Gaussian Splatting." + }, "Comfy_Load3D_ShowGrid": { "name": "Показать сетку", "tooltip": "Переключиться, чтобы показывать сетку по умолчанию" @@ -167,10 +205,6 @@ "name": "Закрепить регулировку кисти по доминирующей оси", "tooltip": "При включении регулировки кисти будет влиять только на размер или жёсткость в зависимости от того, в каком направлении вы двигаетесь больше" }, - "Comfy_MaskEditor_UseNewEditor": { - "name": "Использовать новый редактор масок", - "tooltip": "Переключиться на новый интерфейс редактора масок" - }, "Comfy_ModelLibrary_AutoLoadAll": { "name": "Автоматически загружать все папки моделей", "tooltip": "Если true, все папки будут загружены, как только вы откроете библиотеку моделей (это может вызвать задержки при загрузке). Если false, корневые папки моделей будут загружены только после нажатия на них." @@ -238,6 +272,10 @@ "Comfy_Node_AllowImageSizeDraw": { "name": "Показывать ширину × высоту под предварительным просмотром изображения" }, + "Comfy_Node_AlwaysShowAdvancedWidgets": { + "name": "Всегда показывать расширенные виджеты на всех узлах", + "tooltip": "Если включено, расширенные виджеты всегда видны на всех узлах без необходимости раскрывать их по отдельности." + }, "Comfy_Node_AutoSnapLinkToSlot": { "name": "Автоматически привязывать ссылку к слоту ноды", "tooltip": "При перетаскивании ссылки над нодой ссылка автоматически привязывается к подходящему входному слоту ноды" @@ -298,6 +336,10 @@ "name": "Размер истории очереди", "tooltip": "Максимальное количество задач, отображаемых в истории очереди." }, + "Comfy_Queue_QPOV2": { + "name": "Использовать объединённую очередь заданий в боковой панели ресурсов", + "tooltip": "Заменяет плавающую панель очереди заданий на аналогичную очередь, встроенную в боковую панель ресурсов. Вы можете отключить эту опцию, чтобы вернуться к плавающей панели." + }, "Comfy_Sidebar_Location": { "name": "Расположение боковой панели", "options": { @@ -312,6 +354,13 @@ "small": "маленький" } }, + "Comfy_Sidebar_Style": { + "name": "Стиль боковой панели", + "options": { + "connected": "прикреплённая", + "floating": "плавающая" + } + }, "Comfy_Sidebar_UnifiedWidth": { "name": "Унифицированная ширина боковой панели" }, @@ -328,6 +377,14 @@ "Comfy_TreeExplorer_ItemPadding": { "name": "Отступ элемента в проводнике дерева" }, + "Comfy_UI_TabBarLayout": { + "name": "Макет панели вкладок", + "options": { + "Default": "По умолчанию", + "Integrated": "Интегрированный" + }, + "tooltip": "Управляет расположением панели вкладок. «Интегрированный» перемещает элементы управления Справкой и Пользователем в область панели вкладок." + }, "Comfy_UseNewMenu": { "name": "Использовать новое меню", "options": { @@ -339,6 +396,14 @@ "Comfy_Validation_Workflows": { "name": "Проверка рабочих процессов" }, + "Comfy_VueNodes_AutoScaleLayout": { + "name": "Автомасштабирование макета (Vue узлы)", + "tooltip": "Автоматически масштабировать позиции узлов при переключении на Vue рендеринг для предотвращения наложения" + }, + "Comfy_VueNodes_Enabled": { + "name": "Современный дизайн узлов (Vue узлы)", + "tooltip": "Современный: DOM-рендеринг с улучшенной интерактивностью, нативными функциями браузера и обновлённым визуальным дизайном. Классический: Традиционный рендеринг на холсте." + }, "Comfy_WidgetControlMode": { "name": "Режим управления виджетом", "options": { @@ -376,6 +441,9 @@ "Comfy_Workflow_SortNodeIdOnSave": { "name": "Сортировать ID нод при сохранении рабочего процесса" }, + "Comfy_Workflow_WarnBlueprintOverwrite": { + "name": "Требовать подтверждение для перезаписи существующего шаблона подграфа" + }, "Comfy_Workflow_WorkflowTabsPosition": { "name": "Положение открытых рабочих процессов", "options": { @@ -407,37 +475,5 @@ }, "pysssss_SnapToGrid": { "name": "Всегда привязываться к сетке" - }, - "Comfy_Canvas_LeftMouseClickBehavior": { - "name": "Поведение при клике левой кнопкой мыши", - "options": { - "Panning": "Перемещение", - "Select": "Выбрать" - } - }, - "Comfy_Canvas_MouseWheelScroll": { - "name": "Прокрутка колесиком мыши", - "options": { - "Panning": "Перемещение", - "Zoom in/out": "Приближение/отдаление" - } - }, - "Comfy_Sidebar_Style": { - "name": "Стиль боковой панели", - "options": { - "floating": "плавающая", - "connected": "прикреплённая" - } - }, - "Comfy_VueNodes_AutoScaleLayout": { - "name": "Автомасштабирование макета (Vue узлы)", - "tooltip": "Автоматически масштабировать позиции узлов при переключении на Vue рендеринг для предотвращения наложения" - }, - "Comfy_VueNodes_Enabled": { - "name": "Современный дизайн узлов (Vue узлы)", - "tooltip": "Современный: DOM-рендеринг с улучшенной интерактивностью, нативными функциями браузера и обновлённым визуальным дизайном. Классический: Традиционный рендеринг на холсте." - }, - "Comfy_Workflow_WarnBlueprintOverwrite": { - "name": "Требовать подтверждение для перезаписи существующего шаблона подграфа" } } diff --git a/src/locales/tr/commands.json b/src/locales/tr/commands.json index a502c3278..66fb0657a 100644 --- a/src/locales/tr/commands.json +++ b/src/locales/tr/commands.json @@ -1,4 +1,40 @@ { + "Comfy-Desktop_CheckForUpdates": { + "label": "Güncellemeleri Kontrol Et" + }, + "Comfy-Desktop_Folders_OpenCustomNodesFolder": { + "label": "Özel Düğüm Klasörünü Aç" + }, + "Comfy-Desktop_Folders_OpenInputsFolder": { + "label": "Girdi Klasörünü Aç" + }, + "Comfy-Desktop_Folders_OpenLogsFolder": { + "label": "Kayıt Klasörünü Aç" + }, + "Comfy-Desktop_Folders_OpenModelConfig": { + "label": "extra_model_paths.yaml dosyasını Aç" + }, + "Comfy-Desktop_Folders_OpenModelsFolder": { + "label": "Model Klasörünü Aç" + }, + "Comfy-Desktop_Folders_OpenOutputsFolder": { + "label": "Çıktı Klasörünü Aç" + }, + "Comfy-Desktop_OpenDevTools": { + "label": "Geliştirici Araçlarını Aç" + }, + "Comfy-Desktop_OpenUserGuide": { + "label": "Masaüstü Kullanıcı Kılavuzu" + }, + "Comfy-Desktop_Quit": { + "label": "Çıkış" + }, + "Comfy-Desktop_Reinstall": { + "label": "Yeniden Yükle" + }, + "Comfy-Desktop_Restart": { + "label": "Yeniden Başlat" + }, "Comfy_3DViewer_Open3DViewer": { "label": "Seçili Düğüm için 3D Görüntüleyiciyi (Beta) Aç" }, @@ -155,18 +191,30 @@ "Comfy_Manager_ShowUpdateAvailablePacks": { "label": "Özel Düğüm Güncellemelerini Kontrol Et" }, - "Comfy_Manager_ToggleManagerProgressDialog": { - "label": "Özel Düğüm Yöneticisi İlerleme Çubuğunu Aç/Kapat" - }, "Comfy_MaskEditor_BrushSize_Decrease": { "label": "Maske Düzenleyicide Fırça Boyutunu Azalt" }, "Comfy_MaskEditor_BrushSize_Increase": { "label": "Maske Düzenleyicide Fırça Boyutunu Artır" }, + "Comfy_MaskEditor_ColorPicker": { + "label": "MaskEditor'da Renk Seçiciyi Aç" + }, + "Comfy_MaskEditor_Mirror_Horizontal": { + "label": "MaskEditor'da Yatay Aynala" + }, + "Comfy_MaskEditor_Mirror_Vertical": { + "label": "MaskEditor'da Dikey Aynala" + }, "Comfy_MaskEditor_OpenMaskEditor": { "label": "Seçili Düğüm için Maske Düzenleyiciyi Aç" }, + "Comfy_MaskEditor_Rotate_Left": { + "label": "MaskEditor'da Sola Döndür" + }, + "Comfy_MaskEditor_Rotate_Right": { + "label": "MaskEditor'da Sağa Döndür" + }, "Comfy_Memory_UnloadModels": { "label": "Modelleri Boşalt" }, @@ -197,12 +245,18 @@ "Comfy_QueueSelectedOutputNodes": { "label": "Seçili Çıktı Düğümlerini Kuyruğa Al" }, + "Comfy_Queue_ToggleOverlay": { + "label": "İş Geçmişini Göster/Gizle" + }, "Comfy_Redo": { "label": "Yinele" }, "Comfy_RefreshNodeDefinitions": { "label": "Düğüm Tanımlarını Yenile" }, + "Comfy_RenameWorkflow": { + "label": "Çalışma Akışını Yeniden Adlandır" + }, "Comfy_SaveWorkflow": { "label": "İş Akışını Kaydet" }, @@ -221,6 +275,12 @@ "Comfy_ToggleHelpCenter": { "label": "Yardım Merkezi" }, + "Comfy_ToggleLinear": { + "label": "Doğrusal Modu Aç/Kapat" + }, + "Comfy_ToggleQPOV2": { + "label": "Kuyruk Paneli V2'yi Aç/Kapat" + }, "Comfy_ToggleTheme": { "label": "Temayı Değiştir (Karanlık/Açık)" }, diff --git a/src/locales/tr/main.json b/src/locales/tr/main.json index 66aadb352..89128b82f 100644 --- a/src/locales/tr/main.json +++ b/src/locales/tr/main.json @@ -1,6 +1,8 @@ { "actionbar": { - "dockToTop": "Üste sabitle" + "dockToTop": "Üste sabitle", + "feedback": "Geri bildirim", + "feedbackTooltip": "Geri bildirim" }, "apiNodesCostBreakdown": { "costPerRun": "Çalıştırma başına maliyet", @@ -18,23 +20,141 @@ "assetCard": "{name} - {type} varlık", "loadingAsset": "Varlık yükleniyor" }, + "assetCollection": "Varlık koleksiyonu", "assets": "Varlıklar", "baseModels": "Temel modeller", "browseAssets": "Varlıklara Göz At", + "byType": "Türe göre", + "checkpoints": "Kontrol noktaları", + "civitaiLinkExample": "{example} {link}", + "civitaiLinkExampleStrong": "Örnek:", + "civitaiLinkExampleUrl": "https://civitai.com/models/10706/luisap-z-image-and-qwen-pixel-art-refiner?modelVersionId=2225295", + "civitaiLinkLabel": "Civitai model {download} bağlantısı", + "civitaiLinkLabelDownload": "indir", + "civitaiLinkPlaceholder": "Bağlantıyı buraya yapıştırın", + "confirmModelDetails": "Model Detaylarını Onayla", "connectionError": "Lütfen bağlantınızı kontrol edin ve tekrar deneyin", + "deletion": { + "body": "Bu model kütüphanenizden kalıcı olarak silinecek.", + "complete": "{assetName} silindi.", + "failed": "{assetName} silinemedi.", + "header": "Bu modeli silmek istiyor musunuz?", + "inProgress": "{assetName} siliniyor..." + }, + "download": { + "complete": "İndirme tamamlandı", + "failed": "İndirme başarısız oldu", + "inProgress": "{assetName} indiriliyor..." + }, + "emptyImported": { + "canImport": "Henüz içe aktarılmış model yok. Kendi modelinizi eklemek için \"Model İçe Aktar\"a tıklayın.", + "restricted": "Kişisel modeller yalnızca Creator ve üzeri seviyelerde kullanılabilir." + }, + "errorFileTooLarge": "Dosya izin verilen maksimum boyut sınırını aşıyor", + "errorFormatNotAllowed": "Yalnızca SafeTensor formatı destekleniyor", + "errorModelTypeNotSupported": "Bu model türü desteklenmiyor", + "errorUnknown": "Beklenmeyen bir hata oluştu", + "errorUnsafePickleScan": "CivitAI bu dosyada potansiyel olarak güvensiz kod tespit etti", + "errorUnsafeVirusScan": "CivitAI bu dosyada kötü amaçlı yazılım veya şüpheli içerik tespit etti", + "errorUploadFailed": "Varlık içe aktarılamadı. Lütfen tekrar deneyin.", "failedToCreateNode": "Düğüm oluşturulamadı. Lütfen tekrar deneyin veya ayrıntılar için konsolu kontrol edin.", "fileFormats": "Dosya formatları", + "fileName": "Dosya Adı", + "fileSize": "Dosya Boyutu", + "filterBy": "Filtrele", + "findInLibrary": "Bunu modeller kütüphanesinin {type} bölümünde bulabilirsiniz.", + "finish": "Bitir", + "genericLinkPlaceholder": "Bağlantıyı buraya yapıştırın", + "importAnother": "Başka Birini İçe Aktar", + "imported": "İçe aktarıldı", + "jobId": "İş ID", "loadingModels": "{type} yükleniyor...", + "maxFileSize": "Maksimum dosya boyutu: {size}", + "maxFileSizeValue": "1 GB", + "media": { + "audioPlaceholder": "Ses", + "threeDModelPlaceholder": "3D Model" + }, + "modelAssociatedWithLink": "Sağladığınız bağlantı ile ilişkili model:", + "modelInfo": { + "addBaseModel": "Taban model ekle...", + "addTag": "Etiket ekle...", + "additionalTags": "Ek Etiketler", + "baseModelUnknown": "Taban model bilinmiyor", + "basicInfo": "Temel Bilgiler", + "compatibleBaseModels": "Uyumlu Taban Modelleri", + "description": "Açıklama", + "descriptionNotSet": "Açıklama ayarlanmadı", + "descriptionPlaceholder": "Bu model için bir açıklama ekleyin...", + "displayName": "Görünen Ad", + "editDisplayName": "Görünen Adı Düzenle", + "fileName": "Dosya Adı", + "modelDescription": "Model Açıklaması", + "modelTagging": "Model Etiketleme", + "modelType": "Model Türü", + "noAdditionalTags": "Ek etiket yok", + "selectModelPrompt": "Bilgilerini görmek için bir model seçin", + "selectModelType": "Model türü seç...", + "source": "Kaynak", + "title": "Model Bilgisi", + "triggerPhrases": "Tetikleyici İfadeler", + "viewOnSource": "{source} üzerinde görüntüle" + }, + "modelName": "Model Adı", + "modelNamePlaceholder": "Bu model için bir ad girin", + "modelTypeSelectorLabel": "Bu hangi model türü?", + "modelTypeSelectorPlaceholder": "Model türünü seçin", + "modelUploaded": "Model başarıyla içe aktarıldı.", "noAssetsFound": "Varlık bulunamadı", "noModelsInFolder": "Bu klasörde {type} mevcut değil", - "searchAssetsPlaceholder": "Varlık ara...", + "noValidSourceDetected": "Geçerli bir içe aktarma kaynağı tespit edilmedi", + "notSureLeaveAsIs": "Emin değil misiniz? Olduğu gibi bırakın", + "onlyCivitaiUrlsSupported": "Yalnızca Civitai URL'leri destekleniyor", + "ownership": "Sahiplik", + "ownershipAll": "Tümü", + "ownershipMyModels": "Modellerim", + "ownershipPublicModels": "Herkese açık modeller", + "processingModel": "İndirme başladı", + "processingModelDescription": "Bu pencereyi kapatabilirsiniz. İndirme arka planda devam edecek.", + "providerCivitai": "Civitai", + "providerHuggingFace": "Hugging Face", + "rename": { + "failed": "Varlık yeniden adlandırılamadı." + }, + "selectFrameworks": "Framework'leri Seçin", + "selectModelType": "Model türünü seçin", + "selectProjects": "Projeleri Seçin", "sortAZ": "A-Z", "sortBy": "Sırala", "sortPopular": "Popüler", "sortRecent": "En yeni", "sortZA": "Z-A", + "sortingType": "Sıralama Türü", + "tags": "Etiketler", + "tagsHelp": "Etiketleri virgül ile ayırın", + "tagsPlaceholder": "örn. modeller, checkpoint", "tryAdjustingFilters": "Aramanızı veya filtrelerinizi değiştirmeyi deneyin", - "unknown": "Bilinmeyen" + "unknown": "Bilinmeyen", + "unsupportedUrlSource": "Yalnızca {sources} kaynaklarından gelen URL'ler destekleniyor", + "upgradeFeatureDescription": "Bu özellik yalnızca Creator veya Pro planlarında mevcuttur.", + "upgradeToUnlockFeature": "Bu özelliğin kilidini açmak için yükseltin", + "upload": "İçe Aktar", + "uploadFailed": "İçe aktarma başarısız oldu", + "uploadModel": "İçe Aktar", + "uploadModelDescription1": "Bir Civitai model indirme bağlantısı yapıştırarak kütüphanenize ekleyin.", + "uploadModelDescription1Generic": "Bir model indirme bağlantısı yapıştırarak kütüphanenize ekleyin.", + "uploadModelDescription2": "Şu anda yalnızca {link} bağlantıları destekleniyor", + "uploadModelDescription2Generic": "Şu anda yalnızca aşağıdaki sağlayıcılardan gelen URL'ler destekleniyor:", + "uploadModelDescription2Link": "https://civitai.com/models", + "uploadModelDescription3": "Maksimum dosya boyutu: {size}", + "uploadModelFailedToRetrieveMetadata": "Meta veriler alınamadı. Lütfen bağlantıyı kontrol edip tekrar deneyin.", + "uploadModelFromCivitai": "Civitai'den model içe aktar", + "uploadModelGeneric": "Model içe aktar", + "uploadModelHelpFooterText": "URL'leri bulmakta yardıma mı ihtiyacınız var? Aşağıdan bir sağlayıcıya tıklayarak nasıl yapılır videosunu izleyin.", + "uploadModelHelpVideo": "Model Yükleme Yardım Videosu", + "uploadModelHowDoIFindThis": "Bunu nasıl bulurum?", + "uploadSuccess": "Model başarıyla içe aktarıldı!", + "uploadingModel": "Model içe aktarılıyor..." }, "auth": { "apiKey": { @@ -148,12 +268,19 @@ "title": "Hesap oluşturun" } }, + "boundingBox": { + "height": "Yükseklik", + "width": "Genişlik", + "x": "X", + "y": "Y" + }, "breadcrumbsMenu": { "clearWorkflow": "İş Akışını Temizle", "deleteBlueprint": "Taslağı Sil", "deleteWorkflow": "İş Akışını Sil", "duplicate": "Çoğalt", - "enterNewName": "Yeni isim girin" + "enterNewName": "Yeni isim girin", + "missingNodesWarning": "İş akışında desteklenmeyen düğümler var (kırmızı ile vurgulanmış)." }, "clipboard": { "errorMessage": "Panoya kopyalanamadı", @@ -207,6 +334,7 @@ }, "retry": "Tekrar Dene", "retrying": "Yeniden deneniyor...", + "skipToCloudApp": "Bulut uygulamasına geç", "start": { "desc": "Kurulum gerekmez. Her cihazda çalışır.", "download": "ComfyUI'ı İndir", @@ -340,6 +468,8 @@ "Edit Subgraph Widgets": "Alt Grafik Widget'larını Düzenle", "Expand": "Genişlet", "Expand Node": "Düğümü Genişlet", + "Extensions": "Eklentiler", + "FavoriteWidget": "Widget'ı Favorilere Ekle", "Horizontal": "Yatay", "Inputs": "Girdiler", "Left": "Sol", @@ -359,6 +489,7 @@ "Remove": "Kaldır", "Remove Bypass": "Atlamayı Kaldır", "Rename": "Yeniden Adlandır", + "RenameWidget": "Widget'ı Yeniden Adlandır", "Resize": "Yeniden Boyutlandır", "Right": "Sağ", "Run Branch": "Dalı Çalıştır", @@ -369,6 +500,7 @@ "Shapes": "Şekiller", "Title": "Başlık", "Top": "Üst", + "UnfavoriteWidget": "Widget'ı Favorilerden Kaldır", "Unpack Subgraph": "Alt Grafiği Aç", "Unpin": "Sabitlemeyi Kaldır", "Vertical": "Dikey", @@ -382,6 +514,7 @@ "additionalInfo": "Ek Bilgi", "apiPricing": "API Fiyatlandırması", "credits": "Krediler", + "creditsAvailable": "Mevcut kredi", "details": "Detaylar", "eventType": "Etkinlik Türü", "faqs": "SSS", @@ -390,15 +523,46 @@ "messageSupport": "Destek Mesajı", "model": "Model", "purchaseCredits": "Kredi Satın Al", + "refreshes": "{date} tarihinde yenilenir", "time": "Zaman", "topUp": { + "addMoreCredits": "Daha fazla kredi ekle", + "addMoreCreditsToRun": "Çalıştırmak için daha fazla kredi ekle", + "amountToPayLabel": "Ödenecek tutar (dolar)", + "buy": "Satın Al", + "buyCredits": "Ödemeye devam et", "buyNow": "Şimdi al", + "contactUs": "Bize ulaşın", + "creditsDescription": "Krediler, iş akışlarını veya partner düğümlerini çalıştırmak için kullanılır.", + "creditsPerDollar": "dolar başına kredi", + "creditsToReceiveLabel": "Alınacak kredi", + "howManyCredits": "Kaç kredi eklemek istersiniz?", "insufficientMessage": "Bu iş akışını çalıştırmak için yeterli krediniz yok.", "insufficientTitle": "Yetersiz Kredi", + "insufficientWorkflowMessage": "Bu iş akışını çalıştırmak için yeterli krediniz yok.", + "maxAllowed": "En fazla {credits} kredi.", "maxAmount": "(Maks. 1.000 USD)", + "maximumAmount": "En fazla ${amount}.", + "minRequired": "En az {credits} kredi", + "minimumPurchase": "En az ${amount} ({credits} kredi)", + "needMore": "Daha fazlasına mı ihtiyacınız var?", + "purchaseError": "Satın Alma Başarısız", + "purchaseErrorDetail": "Kredi satın alınamadı: {error}", "quickPurchase": "Hızlı Satın Alma", "seeDetails": "Detayları gör", - "topUp": "Yükleme Yap" + "selectAmount": "Tutar seç", + "templateNote": "*Wan Fun Control şablonu ile oluşturuldu", + "topUp": "Yükleme Yap", + "unknownError": "Bilinmeyen bir hata oluştu", + "usdAmount": "${amount}", + "videosEstimate": "~{count} video", + "viewPricing": "Fiyatlandırma detaylarını görüntüle", + "youGet": "Kredi", + "youPay": "Tutar (USD)" + }, + "unified": { + "message": "Krediler birleştirildi", + "tooltip": "Comfy genelinde ödemeleri birleştirdik. Artık her şey Comfy Kredileri ile çalışıyor:\n- Partner Düğümleri (eski adıyla API düğümleri)\n- Bulut iş akışları\n\nMevcut Partner düğümü bakiyeniz kredilere dönüştürüldü." }, "yourCreditBalance": "Kredi bakiyeniz" }, @@ -414,6 +578,9 @@ "CLIP_VISION": "CLIP_VISION", "CLIP_VISION_OUTPUT": "CLIP_VISION_ÇIKTISI", "COMBO": "COMBO", + "COMFY_AUTOGROW_V3": "COMFY_AUTOGROW_V3", + "COMFY_DYNAMICCOMBO_V3": "COMFY_DYNAMICCOMBO_V3", + "COMFY_MATCHTYPE_V3": "COMFY_MATCHTYPE_V3", "CONDITIONING": "KOŞULLANDIRMA", "CONTROL_NET": "KONTROL_AĞI", "FLOAT": "FLOAT", @@ -424,18 +591,21 @@ "HOOKS": "Kancalar", "HOOK_KEYFRAMES": "Kanca_anahtar_kareleri", "IMAGE": "GÖRÜNTÜ", + "IMAGECOMPARE": "GÖRSELKARŞILAŞTIR", "INT": "TAMSAYI", "LATENT": "GİZLİ", "LATENT_OPERATION": "GİZLİ_İŞLEM", + "LATENT_UPSCALE_MODEL": "LATENT_UPSCALE_MODEL", "LOAD3D_CAMERA": "YÜKLE3D_KAMERA", "LOAD_3D": "3D_YÜKLE", - "LOAD_3D_ANIMATION": "3D_ANİMASYON_YÜKLE", "LORA_MODEL": "LORA_MODEL", "LOSS_MAP": "KAYIP_HARITASI", "LUMA_CONCEPTS": "LUMA_KAVRAMLARI", "LUMA_REF": "LUMA_REF", "MASK": "MASKE", "MESH": "MESH", + "MESHY_RIGGED_TASK_ID": "MESHY_RIGGED_TASK_ID", + "MESHY_TASK_ID": "MESHY_TASK_ID", "MODEL": "MODEL", "MODEL_PATCH": "MODEL_YAMASI", "MODEL_TASK_ID": "MODEL_GÖREV_ID", @@ -455,6 +625,7 @@ "STYLE_MODEL": "STİL_MODELİ", "SVG": "SVG", "TIMESTEPS_RANGE": "ZAMAN_ADIMLARI_ARALIĞI", + "TRACKS": "TRACKS", "UPSCALE_MODEL": "YÜKSELTME_MODELİ", "VAE": "VAE", "VIDEO": "VİDEO", @@ -523,14 +694,17 @@ "amount": "Miktar", "apply": "Uygula", "architecture": "Mimari", + "asset": "{count} varlık | {count} varlık | {count} varlık", "audioFailedToLoad": "Ses yüklenemedi", "audioProgress": "Ses ilerlemesi", "author": "Yazar", "back": "Geri", + "batchRename": "Toplu Yeniden Adlandır", "beta": "BETA", "bookmark": "Kütüphaneye Kaydet", "calculatingDimensions": "Boyutlar hesaplanıyor", "cancel": "İptal", + "cancelled": "İptal Edildi", "capture": "yakala", "category": "Kategori", "chart": "Grafik", @@ -540,6 +714,7 @@ "clearAll": "Tümünü temizle", "clearFilters": "Filtreleri Temizle", "close": "Kapat", + "closeDialog": "Diyaloğu kapat", "color": "Renk", "comfy": "Comfy", "comfyOrgLogoAlt": "ComfyOrg Logosu", @@ -556,13 +731,17 @@ "control_before_generate": "oluşturmadan önce kontrol et", "copied": "Kopyalandı", "copy": "Kopyala", + "copyAll": "Tümünü Kopyala", "copyJobId": "İş Kimliğini Kopyala", "copyToClipboard": "Panoya Kopyala", "copyURL": "URL'yi Kopyala", + "core": "Çekirdek", "currentUser": "Mevcut Kullanıcı", + "custom": "Özel", "customBackground": "Özel Arka Plan", "customize": "Özelleştir", "customizeFolder": "Klasörü Özelleştir", + "decrement": "Azalt", "defaultBanner": "varsayılan banner", "delete": "Sil", "deleteAudioFile": "Ses dosyasını sil", @@ -571,27 +750,35 @@ "description": "Açıklama", "devices": "Cihazlar", "disableAll": "Tümünü Devre Dışı Bırak", + "disableSelected": "Seçilenleri Devre Dışı Bırak", + "disableThirdParty": "Üçüncü Tarafı Devre Dışı Bırak", "disabling": "{id} devre dışı bırakılıyor", "dismiss": "Kapat", "download": "İndir", "downloadImage": "Görüntüyü indir", "downloadVideo": "Videoyu indir", + "downloading": "İndiriliyor", "dropYourFileOr": "Dosyanızı buraya sürükleyin veya", "duplicate": "Çoğalt", "edit": "Düzenle", "editImage": "Görseli düzenle", "editOrMaskImage": "Görüntüyü düzenle veya maskele", + "emDash": "—", "empty": "Boş", "enableAll": "Tümünü Etkinleştir", "enableOrDisablePack": "Paketi etkinleştir veya devre dışı bırak", + "enableSelected": "Seçilenleri Etkinleştir", "enabled": "Etkin", "enabling": "{id} etkinleştiriliyor", + "enterBaseName": "Temel adı girin", + "enterNewName": "Yeni adı girin", "error": "Hata", "errorLoadingImage": "Görüntü yüklenirken hata", "errorLoadingVideo": "Video yüklenirken hata", "experimental": "BETA", "export": "Dışa Aktar", "extensionName": "Uzantı Adı", + "failed": "Başarısız", "failedToCopyJobId": "İş kimliği kopyalanamadı", "failedToDownloadImage": "Görüntü indirilemedi", "failedToDownloadVideo": "Video indirilemedi", @@ -607,12 +794,15 @@ "goToNode": "Düğüme Git", "graphNavigation": "Grafik gezintisi", "halfSpeed": "0.5x", + "hideLeftPanel": "Sol paneli gizle", + "hideRightPanel": "Sağ paneli gizle", "icon": "Simge", "imageFailedToLoad": "Görsel yüklenemedi", "imagePreview": "Görüntü önizlemesi - Görüntüler arasında gezinmek için ok tuşlarını kullanın", "imageUrl": "Görsel URL'si", "import": "İçe Aktar", "inProgress": "Devam ediyor", + "increment": "Arttır", "info": "Düğüm Bilgisi", "insert": "Ekle", "install": "Yükle", @@ -620,7 +810,9 @@ "installing": "Yükleniyor", "interrupted": "Kesintiye uğradı", "itemSelected": "{selectedCount} öğe seçildi", + "itemsCopiedToClipboard": "Öğeler panoya kopyalandı", "itemsSelected": "{selectedCount} öğe seçildi", + "job": "Görev", "jobIdCopied": "İş kimliği panoya kopyalandı", "keybinding": "Tuş Ataması", "keybindingAlreadyExists": "Tuş ataması zaten mevcut:", @@ -638,14 +830,18 @@ "micPermissionDenied": "Mikrofon izni reddedildi", "migrate": "Taşı", "missing": "Eksik", + "more": "Daha Fazla", "moreOptions": "Daha Fazla Seçenek", "moreWorkflows": "Daha fazla iş akışı", "multiSelectDropdown": "Çoklu seçim açılır menüsü", "name": "Ad", "newFolder": "Yeni Klasör", "next": "İleri", + "nightly": "NIGHTLY", "no": "Hayır", "noAudioRecorded": "Ses kaydedilmedi", + "noItems": "Öğe yok", + "noResults": "Sonuç Yok", "noResultsFound": "Sonuç Bulunamadı", "noTasksFound": "Görev Bulunamadı", "noTasksFoundMessage": "Kuyrukta görev yok.", @@ -656,26 +852,45 @@ "nodeSlotsError": "Düğüm Yuva Hatası", "nodeWidgetsError": "Düğüm Widget Hatası", "nodes": "Düğümler", + "nodesCount": "{count} düğüm | {count} düğüm | {count} düğüm", "nodesRunning": "düğüm çalışıyor", "none": "Hiçbiri", + "nothingToCopy": "Kopyalanacak bir şey yok", + "nothingToDelete": "Silinecek bir şey yok", + "nothingToDuplicate": "Çoğaltılacak bir şey yok", + "nothingToRename": "Yeniden adlandırılacak bir şey yok", "ok": "Tamam", "openManager": "Yöneticiyi Aç", "openNewIssue": "Yeni Sorun Aç", + "or": "veya", "overwrite": "Üzerine Yaz", + "playPause": "Oynat/Duraklat", "playRecording": "Kaydı Oynat", "playbackSpeed": "Oynatma Hızı", "playing": "Oynatılıyor", "pressKeysForNewBinding": "Yeni bağlama için tuşlara basın", "preview": "ÖNİZLEME", + "profile": "Profil", "progressCountOf": "/", + "queued": "Kuyrukta", "ready": "Hazır", "reconnected": "Yeniden bağlandı", "reconnecting": "Yeniden bağlanılıyor", "refresh": "Yenile", "refreshNode": "Düğümü Yenile", + "relativeTime": { + "daysAgo": "{count}g önce", + "hoursAgo": "{count}s önce", + "minutesAgo": "{count}dk önce", + "monthsAgo": "{count}ay önce", + "now": "şimdi", + "weeksAgo": "{count}hft önce", + "yearsAgo": "{count}y önce" + }, "releaseTitle": "{package} {version} Sürümü", "reloadToApplyChanges": "Değişiklikleri uygulamak için yeniden yükleyin", "removeImage": "Görüntüyü kaldır", + "removeTag": "Etiketi kaldır", "removeVideo": "Videoyu kaldır", "rename": "Yeniden Adlandır", "reportIssue": "Rapor Gönder", @@ -690,21 +905,31 @@ "resizeFromTopRight": "Sağ üst köşeden yeniden boyutlandır", "restart": "Yeniden Başlat", "resultsCount": "{count} Sonuç Bulundu", + "running": "Çalışıyor", "save": "Kaydet", "saving": "Kaydediliyor", + "scrollLeft": "Sola Kaydır", + "scrollRight": "Sağa Kaydır", "search": "Ara", "searchExtensions": "Uzantıları Ara", "searchFailedMessage": "Aramanızla eşleşen herhangi bir ayar bulamadık. Arama terimlerinizi değiştirmeyi deneyin.", "searchKeybindings": "Tuş Atamalarını Ara", "searchModels": "Modelleri Ara", "searchNodes": "Düğümleri Ara", + "searchPlaceholder": "Ara...", "searchSettings": "Ayarları Ara", "searchWorkflows": "İş Akışlarını Ara", "seeTutorial": "Bir eğitim görün", + "selectItemsToCopy": "Kopyalanacak öğeleri seçin", + "selectItemsToDelete": "Silinecek öğeleri seçin", + "selectItemsToDuplicate": "Çoğaltılacak öğeleri seçin", + "selectItemsToRename": "Yeniden adlandırılacak öğeleri seçin", "selectedFile": "Seçilen dosya", "setAsBackground": "Arka Plan Olarak Ayarla", "settings": "Ayarlar", + "showLeftPanel": "Sol paneli göster", "showReport": "Raporu Göster", + "showRightPanel": "Sağ paneli göster", "singleSelectDropdown": "Tekli seçim açılır menüsü", "sort": "Sırala", "source": "Kaynak", @@ -712,12 +937,14 @@ "status": "Durum", "stopPlayback": "Oynatmayı Durdur", "stopRecording": "Kaydı Durdur", + "submit": "Gönder", "success": "Başarılı", "systemInfo": "Sistem Bilgisi", "terminal": "Terminal", "title": "Başlık", "triggerPhrase": "Tetikleyici ifade", "unknownError": "Bilinmeyen hata", + "untitled": "Başlıksız", "update": "Güncelle", "updateAvailable": "Güncelleme Mevcut", "updateFrontend": "Ön Ucu Güncelle", @@ -725,6 +952,7 @@ "updating": "{id} güncelleniyor", "upload": "Yükle", "usageHint": "Kullanım ipucu", + "use": "Kullan", "user": "Kullanıcı", "versionMismatchWarning": "Sürüm Uyumluluk Uyarısı", "versionMismatchWarningMessage": "{warning}: {detail} Güncelleme talimatları için https://docs.comfy.org/installation/update_comfyui#common-update-issues adresini ziyaret edin.", @@ -732,11 +960,10 @@ "videoPreview": "Video önizlemesi - Videolar arasında gezinmek için ok tuşlarını kullanın", "viewImageOfTotal": "{total} görüntüden {index}. görüntüyü görüntüle", "viewVideoOfTotal": "{total} videodan {index}. videoyu görüntüle", - "vitePreloadErrorMessage": "Uygulamanın yeni bir sürümü yayınlandı. Yeniden yüklemek ister misiniz?\nEğer yüklemezseniz, uygulamanın bazı bölümleri beklenildiği gibi çalışmayabilir.\nİlerlemenizi kaydettikten sonra yeniden yüklemek için çekinmeyin.", - "vitePreloadErrorTitle": "Yeni Sürüm Mevcut", "volume": "Ses", "warning": "Uyarı", - "workflow": "İş Akışı" + "workflow": "İş Akışı", + "you": "Sen" }, "graphCanvasMenu": { "fitView": "Görünüme Sığdır", @@ -758,12 +985,17 @@ "create": "Grup düğümü oluştur", "enterName": "İsim girin" }, + "help": { + "helpCenterMenu": "Yardım Merkezi Menüsü", + "recentReleases": "Son sürümler" + }, "helpCenter": { "clickToLearnMore": "Daha fazla bilgi için tıklayın →", "desktopUserGuide": "Masaüstü Kullanıcı Kılavuzu", "docs": "Belgeler", + "feedback": "Geri Bildirim Ver", "github": "Github", - "helpFeedback": "Yardım ve Geri Bildirim", + "help": "Yardım & Destek", "loadingReleases": "Sürümler yükleniyor...", "managerExtension": "Yönetici Uzantısı", "more": "Daha Fazla...", @@ -772,6 +1004,12 @@ "recentReleases": "Son sürümler", "reinstall": "Yeniden Yükle", "updateAvailable": "Güncelle", + "updateComfyUI": "ComfyUI'yi Güncelle", + "updateComfyUIFailed": "ComfyUI güncellenemedi. Lütfen tekrar deneyin.", + "updateComfyUIStarted": "Güncelleme Başladı", + "updateComfyUIStartedDetail": "ComfyUI güncellemesi kuyruğa alındı. Lütfen bekleyin...", + "updateComfyUISuccess": "Güncelleme Tamamlandı", + "updateComfyUISuccessDetail": "ComfyUI güncellendi. Yeniden başlatılıyor...", "whatsNew": "Yenilikler Neler?" }, "icon": { @@ -785,6 +1023,18 @@ "inbox": "Gelen Kutusu", "star": "Yıldız" }, + "imageCompare": { + "noImages": "Karşılaştırılacak görsel yok" + }, + "imageCrop": { + "cropPreviewAlt": "Kırpma önizlemesi", + "loading": "Yükleniyor...", + "noInputImage": "Bağlı giriş görseli yok" + }, + "importFailed": { + "copyError": "Kopyalama Hatası", + "title": "İçe Aktarma Başarısız" + }, "install": { "appDataLocationTooltip": "ComfyUI'nin uygulama veri dizini. Saklar:\n- Kayıtlar\n- Sunucu yapılandırmaları", "appPathLocationTooltip": "ComfyUI'nin uygulama varlık dizini. ComfyUI kodunu ve varlıklarını saklar", @@ -798,6 +1048,8 @@ "failedToSelectDirectory": "Dizin seçilemedi", "gpu": "GPU", "gpuPicker": { + "amdDescription": "En iyi performans için AMD GPU'nuzu ROCm™ hızlandırmasıyla kullanın.", + "amdSubtitle": "AMD ROCm™", "appleMetalDescription": "Daha hızlı hız ve daha iyi genel deneyim için Mac'inizin GPU'sunu kullanır", "cpuDescription": "GPU hızlandırma kullanılamadığında uyumluluk için CPU modunu kullanın", "cpuSubtitle": "CPU Modu", @@ -824,6 +1076,8 @@ "selectGpuDescription": "Sahip olduğunuz GPU türünü seçin" }, "helpImprove": "Lütfen ComfyUI'yi geliştirmeye yardımcı olun", + "insideAppInstallDir": "Bu klasör ComfyUI Desktop uygulama paketinin içindedir ve güncellemeler sırasında silinir. Belgeler/ComfyUI gibi kurulum klasörü dışında bir dizin seçin.", + "insideUpdaterCache": "Bu klasör ComfyUI güncelleyici önbelleğinin içindedir ve her güncellemede temizlenir. Verileriniz için farklı bir konum seçin.", "installLocation": "Kurulum Yeri", "installLocationDescription": "ComfyUI'nin kullanıcı verileri için dizini seçin. Seçilen konuma bir python ortamı yüklenecektir.", "installLocationTooltip": "ComfyUI'nin kullanıcı veri dizini. Saklar:\n- Python Ortamı\n- Modeller\n- Özel düğümler\n", @@ -898,6 +1152,16 @@ "issueReport": { "helpFix": "Bunu Düzeltmeye Yardım Et" }, + "linearMode": { + "beta": "Beta - Geri Bildirim Verin", + "downloadAll": "Tümünü İndir", + "dragAndDropImage": "Bir görseli sürükleyip bırakın", + "graphMode": "Grafik Modu", + "linearMode": "Basit Mod", + "rerun": "Tekrar Çalıştır", + "reuseParameters": "Parametreleri Yeniden Kullan", + "runCount": "Çalıştırma sayısı:" + }, "load3d": { "applyingTexture": "Doku uygulanıyor...", "backgroundColor": "Arka Plan Rengi", @@ -924,20 +1188,24 @@ "lineart": "Çizgi Sanatı", "normal": "Normal", "original": "Orijinal", + "pointCloud": "Nokta Bulutu", "wireframe": "Tel Kafes" }, "model": "Model", "openIn3DViewer": "3D Görüntüleyicide Aç", + "panoramaMode": "Panorama", "previewOutput": "Çıktıyı Önizle", "reloadingModel": "Model yeniden yükleniyor...", "removeBackgroundImage": "Arka Plan Resmini Kaldır", "resizeNodeMatchOutput": "Düğümü çıktıya uyacak şekilde yeniden boyutlandır", "scene": "Sahne", "showGrid": "Izgarayı Göster", + "showSkeleton": "İskeleti Göster", "startRecording": "Kaydı Başlat", "stopRecording": "Kaydı Durdur", "switchCamera": "Kamerayı Değiştir", "switchingMaterialMode": "Malzeme Modu Değiştiriliyor...", + "tiledMode": "Döşemeli", "unsupportedFileType": "Desteklenmeyen dosya türü (.gltf, .glb, .obj, .fbx, .stl desteklenir)", "upDirection": "Yukarı Yön", "upDirections": { @@ -958,6 +1226,11 @@ "title": "3D Görüntüleyici (Beta)" } }, + "loadWorkflowWarning": { + "coreNodesFromVersion": "Sürüm {version} çekirdek düğümleri:", + "outdatedVersion": "Bu iş akışı ComfyUI'nin daha yeni bir sürümüyle oluşturulmuş ({version}). Bazı düğümler düzgün çalışmayabilir.", + "outdatedVersionGeneric": "Bu iş akışı ComfyUI'nin daha yeni bir sürümüyle oluşturulmuş. Bazı düğümler düzgün çalışmayabilir." + }, "maintenance": { "None": "Yok", "OK": "Tamam", @@ -976,7 +1249,15 @@ "showManual": "Bakım görevlerini göster", "status": "Durum", "terminalDefaultMessage": "Bir sorun giderme komutu çalıştırdığınızda, herhangi bir çıktı burada gösterilecektir.", - "title": "Bakım" + "title": "Bakım", + "unsafeMigration": { + "action": "ComfyUI'yi güvenli bir konuma taşımak için aşağıdaki \"Ana yol\" bakım görevini kullanın.", + "appInstallDir": "Ana yolunuz ComfyUI Masaüstü uygulama paketinin içinde. Bu klasör güncellemeler sırasında silinebilir veya üzerine yazılabilir. Kurulum klasörü dışında, örneğin Belgeler/ComfyUI gibi bir dizin seçin.", + "generic": "Mevcut ComfyUI ana yolunuz, güncellemeler sırasında silinebilecek veya değiştirilebilecek bir konumda. Veri kaybını önlemek için güvenli bir klasöre taşıyın.", + "oneDrive": "Ana yolunuz OneDrive üzerinde, bu senkronizasyon sorunlarına ve kazara veri kaybına neden olabilir. OneDrive tarafından yönetilmeyen yerel bir klasör seçin.", + "title": "Güvensiz kurulum yeri tespit edildi", + "updaterCache": "Ana yolunuz ComfyUI güncelleyici önbelleğinin içinde, bu klasör her güncellemede temizlenir. Verileriniz için farklı bir konum seçin." + } }, "manager": { "allMissingNodesInstalled": "Tüm eksik düğümler başarıyla yüklendi", @@ -1077,6 +1358,8 @@ "totalNodes": "Toplam Düğüm", "tryAgainLater": "Lütfen daha sonra tekrar deneyin.", "tryDifferentSearch": "Lütfen farklı bir arama sorgusu deneyin.", + "tryUpdate": "Güncellemeyi Dene", + "tryUpdateTooltip": "Depodan en son değişiklikleri çekin. Nightly sürümlerde otomatik olarak algılanamayan güncellemeler olabilir.", "uninstall": "Kaldır", "uninstallSelected": "Seçilenleri Kaldır", "uninstalling": "{id} kaldırılıyor", @@ -1087,31 +1370,110 @@ "version": "Sürüm" }, "maskEditor": { + "activateLayer": "Katmanı Aktifleştir", + "applyToWholeImage": "Tüm Görüntüye Uygula", + "baseImageLayer": "Temel Görüntü Katmanı", + "baseLayerPreview": "Temel katman önizlemesi", + "black": "Siyah", + "brushSettings": "Fırça Ayarları", + "brushShape": "Fırça Şekli", + "clear": "Temizle", + "clickToResetZoom": "Yakınlaştırmayı sıfırlamak için tıklayın", + "colorSelectSettings": "Renk Seçimi Ayarları", + "colorSelector": "Renk Seçici", + "fillOpacity": "Doldurma Opaklığı", + "hardness": "Sertlik", + "imageLayer": "Görüntü Katmanı", + "invert": "Ters Çevir", + "layers": "Katmanlar", + "livePreview": "Canlı Önizleme", + "maskBlendingOptions": "Maske Karıştırma Seçenekleri", + "maskLayer": "Maske Katmanı", + "maskOpacity": "Maske Opaklığı", + "maskTolerance": "Maske Toleransı", + "method": "Yöntem", + "mirrorHorizontal": "Yatay ayna", + "mirrorVertical": "Dikey ayna", + "negative": "Negatif", + "opacity": "Opaklık", + "paintBucketSettings": "Boya Kovası Ayarları", + "paintLayer": "Boya Katmanı", + "redo": "Yinele", + "resetToDefault": "Varsayılana Sıfırla", + "rotateLeft": "Sola döndür", + "rotateRight": "Sağa döndür", + "selectionOpacity": "Seçim Opaklığı", + "smoothingPrecision": "Yumuşatma Hassasiyeti", + "stepSize": "Adım Boyutu", + "stopAtMask": "Maskede Durdur", + "thickness": "Kalınlık", + "title": "Maske Editörü", + "tolerance": "Tolerans", + "undo": "Geri Al", + "white": "Beyaz" }, "mediaAsset": { + "actions": { + "copyJobId": "İş ID'sini kopyala", + "delete": "Sil", + "download": "İndir", + "exportWorkflow": "İş akışını dışa aktar", + "insertAsNodeInWorkflow": "Çalışma akışına düğüm olarak ekle", + "inspect": "Varlığı incele", + "more": "Daha fazla seçenek", + "moreOptions": "Daha fazla seçenek", + "openWorkflow": "Yeni sekmede iş akışı olarak aç", + "seeMoreOutputs": "Daha fazla çıktı gör", + "zoom": "Yakınlaştır" + }, "assetDeletedSuccessfully": "Varlık başarıyla silindi", "deleteAssetDescription": "Bu varlık kalıcı olarak kaldırılacak.", "deleteAssetTitle": "Bu varlık silinsin mi?", "deleteSelectedDescription": "{count} varlık kalıcı olarak kaldırılacak.", "deleteSelectedTitle": "Seçilen varlıklar silinsin mi?", "deletingImportedFilesCloudOnly": "İçe aktarılan dosyaların silinmesi yalnızca bulut sürümünde desteklenir", + "failedToCreateNode": "Düğüm oluşturulamadı", "failedToDeleteAsset": "Varlık silinemedi", + "failedToExportWorkflow": "İş akışı dışa aktarılamadı", "jobIdToast": { "copied": "Kopyalandı", "error": "Hata", "jobIdCopied": "İş Kimliği panoya kopyalandı", "jobIdCopyFailed": "İş Kimliği kopyalanamadı" }, + "noJobIdFound": "Bu varlık için iş ID'si bulunamadı", + "noWorkflowDataFound": "Bu varlıkta iş akışı verisi bulunamadı", + "nodeAddedToWorkflow": "{nodeType} düğümü iş akışına eklendi", + "nodeTypeNotFound": "{nodeType} düğüm türü bulunamadı", "selection": { "assetsDeletedSuccessfully": "{count} varlık başarıyla silindi", "deleteSelected": "Sil", + "deleteSelectedAll": "Tümünü sil", "deselectAll": "Tümünü seçimi kaldır", "downloadSelected": "İndir", + "downloadSelectedAll": "Tümünü indir", "downloadStarted": "{count} dosya indiriliyor...", "downloadsStarted": "{count} dosya indirilmeye başlandı", + "exportWorkflowAll": "Tüm çalışma akışlarını dışa aktar", + "failedToAddNodes": "Düğümler çalışma akışına eklenemedi", "failedToDeleteAssets": "Seçilen varlıklar silinemedi", - "selectedCount": "Seçilen Varlıklar: {count}" - } + "insertAllAssetsAsNodes": "Tüm varlıkları düğüm olarak ekle", + "multipleSelectedAssets": "Birden fazla varlık seçildi", + "noWorkflowsFound": "Seçilen varlıklarda çalışma akışı verisi bulunamadı", + "noWorkflowsToExport": "Dışa aktarılacak çalışma akışı verisi bulunamadı", + "nodesAddedToWorkflow": "{count} düğüm çalışma akışına eklendi", + "openWorkflowAll": "Tüm çalışma akışlarını aç", + "partialAddNodesSuccess": "{succeeded} başarıyla eklendi, {failed} başarısız oldu", + "partialDeleteSuccess": "{succeeded} başarıyla silindi, {failed} silinemedi", + "partialWorkflowsExported": "{succeeded} başarıyla dışa aktarıldı, {failed} başarısız oldu", + "partialWorkflowsOpened": "{succeeded} çalışma akışı açıldı, {failed} başarısız oldu", + "selectedCount": "Seçilen Varlıklar: {count}", + "workflowsExported": "{count} çalışma akışı başarıyla dışa aktarıldı", + "workflowsOpened": "{count} çalışma akışı yeni sekmede açıldı" + }, + "unsupportedFileType": "Yükleyici düğümü için desteklenmeyen dosya türü", + "workflowExportedSuccessfully": "İş akışı başarıyla dışa aktarıldı", + "workflowOpenedInNewTab": "İş akışı yeni sekmede açıldı" }, "menu": { "autoQueue": "Otomatik Kuyruk", @@ -1119,11 +1481,13 @@ "batchCountTooltip": "İş akışı oluşturma işleminin kaç kez kuyruğa alınması gerektiği", "clear": "İş akışını temizle", "clipspace": "Clipspace'i Aç", + "customNodesManager": "Özel Düğüm Yöneticisi", "dark": "Karanlık", "disabled": "Devre Dışı", "disabledTooltip": "İş akışı otomatik olarak kuyruğa alınmayacak", "execute": "Yürüt", "help": "Yardım", + "helpAndFeedback": "Yardım ve Geri Bildirim", "hideMenu": "Menüyü Gizle", "instant": "Anında", "instantTooltip": "İş akışı, bir oluşturma işlemi bittikten sonra anında kuyruğa alınacak", @@ -1137,6 +1501,7 @@ "resetView": "Tuval görünümünü sıfırla", "run": "Çalıştır", "runWorkflow": "İş akışını çalıştır (Öne kuyruklamak için Shift)", + "runWorkflowDisabled": "İş akışında desteklenmeyen düğümler var (kırmızı ile vurgulanmış). İş akışını çalıştırmak için bunları kaldırın.", "runWorkflowFront": "İş akışını çalıştır (Öne kuyrukla)", "settings": "Ayarlar", "showMenu": "Menüyü Göster", @@ -1152,6 +1517,7 @@ "Canvas Performance": "Tuval Performansı", "Canvas Toggle Lock": "Tuval Kilidini Aç/Kapat", "Check for Custom Node Updates": "Özel Düğüm Güncellemelerini Kontrol Et", + "Check for Updates": "Güncellemeleri Kontrol Et", "Clear Pending Tasks": "Bekleyen Görevleri Temizle", "Clear Workflow": "İş Akışını Temizle", "Clipspace": "Clipspace", @@ -1168,13 +1534,14 @@ "Custom Nodes Manager": "Özel Düğüm Yöneticisi", "Decrease Brush Size in MaskEditor": "MaskEditor'da Fırça Boyutunu Azalt", "Delete Selected Items": "Seçili Öğeleri Sil", + "Desktop User Guide": "Masaüstü Kullanıcı Kılavuzu", "Duplicate Current Workflow": "Mevcut İş Akışını Çoğalt", "Edit": "Düzenle", "Edit Subgraph Widgets": "Alt Grafik Widget'larını Düzenle", "Exit Subgraph": "Alt Grafikten Çık", "Experimental: Browse Model Assets": "Deneysel: Model Varlıklarını Gözat", "Experimental: Enable AssetAPI": "Deneysel: AssetAPI'yi Etkinleştir", - "Experimental: Enable Vue Nodes": "Deneysel: Vue Düğümlerini Etkinleştir", + "Experimental: Enable Nodes 2_0": "Deneysel: Nodes 2.0'ı Etkinleştir", "Export": "Dışa Aktar", "Export (API)": "Dışa Aktar (API)", "File": "Dosya", @@ -1186,12 +1553,15 @@ "Increase Brush Size in MaskEditor": "MaskEditor'da Fırça Boyutunu Artır", "Install Missing Custom Nodes": "Eksik Özel Düğümleri Yükle", "Interrupt": "Kes", + "Job History": "İş Geçmişi", "Load Default Workflow": "Varsayılan İş Akışını Yükle", "Lock Canvas": "Tuvali Kilitle", "Manage group nodes": "Grup düğümlerini yönet", "Manager": "Yönetici", "Manager Menu (Legacy)": "Yönetici Menüsü (Eski)", "Minimap": "Mini Harita", + "Mirror Horizontal in MaskEditor": "MaskEditor'da yatay ayna", + "Mirror Vertical in MaskEditor": "MaskEditor'da dikey ayna", "Model Library": "Model Kütüphanesi", "Move Selected Nodes Down": "Seçili Düğümleri Aşağı Taşı", "Move Selected Nodes Left": "Seçili Düğümleri Sola Taşı", @@ -1204,8 +1574,16 @@ "Node Links": "Düğüm Bağlantıları", "Open": "Aç", "Open 3D Viewer (Beta) for Selected Node": "Seçili Düğüm için 3D Görüntüleyiciyi (Beta) Aç", + "Open Color Picker in MaskEditor": "MaskEditor'da Renk Seçiciyi Aç", + "Open Custom Nodes Folder": "Özel Düğüm Klasörünü Aç", + "Open DevTools": "Geliştirici Araçlarını Aç", + "Open Inputs Folder": "Girdi Klasörünü Aç", + "Open Logs Folder": "Kayıtlar Klasörünü Aç", "Open Mask Editor for Selected Node": "Seçili Düğüm için Maske Düzenleyiciyi Aç", + "Open Models Folder": "Modeller Klasörünü Aç", + "Open Outputs Folder": "Çıktı Klasörünü Aç", "Open Sign In Dialog": "Giriş Yapma İletişim Kutusunu Aç", + "Open extra_model_paths_yaml": "extra_model_paths.yaml Dosyasını Aç", "Pin/Unpin Selected Items": "Seçili Öğeleri Sabitle/Kaldır", "Pin/Unpin Selected Nodes": "Seçili Düğümleri Sabitle/Kaldır", "Previous Opened Workflow": "Önceki Açılan İş Akışı", @@ -1213,10 +1591,16 @@ "Queue Prompt": "İstemi Kuyruğa Al", "Queue Prompt (Front)": "İstemi Kuyruğa Al (Ön)", "Queue Selected Output Nodes": "Seçili Çıktı Düğümlerini Kuyruğa Al", + "Quit": "Çık", "Redo": "Yinele", "Refresh Node Definitions": "Düğüm Tanımlarını Yenile", + "Reinstall": "Yeniden Yükle", + "Rename": "Yeniden Adlandır", "Reset View": "Görünümü Sıfırla", "Resize Selected Nodes": "Seçili Düğümleri Yeniden Boyutlandır", + "Restart": "Yeniden Başlat", + "Rotate Left in MaskEditor": "MaskEditor'da sola döndür", + "Rotate Right in MaskEditor": "MaskEditor'da sağa döndür", "Save": "Kaydet", "Save As": "Farklı Kaydet", "Show Keybindings Dialog": "Tuş Atamaları İletişim Kutusunu Göster", @@ -1225,12 +1609,13 @@ "Sign Out": "Çıkış Yap", "Toggle Essential Bottom Panel": "Temel Alt Paneli Aç/Kapat", "Toggle Logs Bottom Panel": "Kayıtlar Alt Panelini Aç/Kapat", + "Toggle Queue Panel V2": "Kuyruk Paneli V2'yi Aç/Kapat", "Toggle Search Box": "Arama Kutusunu Aç/Kapat", + "Toggle Simple Mode": "Basit Modu Aç/Kapat", "Toggle Terminal Bottom Panel": "Terminal Alt Panelini Aç/Kapat", "Toggle Theme (Dark/Light)": "Temayı Değiştir (Karanlık/Açık)", "Toggle View Controls Bottom Panel": "Görünüm Kontrolleri Alt Panelini Aç/Kapat", "Toggle promotion of hovered widget": "Üzerine gelinen widget'ı yükseltmeyi aç/kapat", - "Toggle the Custom Nodes Manager Progress Bar": "Özel Düğüm Yöneticisi İlerleme Çubuğunu Aç/Kapat", "Undo": "Geri Al", "Ungroup selected group nodes": "Seçili grup düğümlerinin grubunu çöz", "Unload Models": "Modelleri Boşalt", @@ -1255,30 +1640,56 @@ "missingModels": "Eksik Modeller", "missingModelsMessage": "Grafik yüklenirken aşağıdaki modeller bulunamadı" }, + "missingNodes": { + "cloud": { + "description": "Bu iş akışı, Cloud sürümünde henüz desteklenmeyen özel düğümler kullanıyor.", + "gotIt": "Tamam, anladım", + "learnMore": "Daha fazla bilgi", + "priorityMessage": "Bu düğümleri otomatik olarak işaretledik, öncelikli olarak ekleyeceğiz.", + "replacementInstruction": "Bu arada, bu düğümleri (tuvalde kırmızı ile vurgulanan) mümkünse desteklenenlerle değiştirin veya farklı bir iş akışı deneyin.", + "title": "Bu düğümler henüz Comfy Cloud'da mevcut değil" + }, + "oss": { + "description": "Bu iş akışı, henüz yüklemediğiniz özel düğümler kullanıyor.", + "replacementInstruction": "Bu iş akışını çalıştırmak için bu düğümleri yükleyin veya yüklü alternatiflerle değiştirin. Eksik düğümler tuvalde kırmızı ile vurgulanır.", + "title": "Bu iş akışında eksik düğümler var" + } + }, + "nightly": { + "badge": { + "label": "Önizleme Sürümü", + "tooltip": "ComfyUI'nin nightly sürümünü kullanıyorsunuz. Lütfen bu özelliklerle ilgili görüşlerinizi paylaşmak için geri bildirim butonunu kullanın." + } + }, "nodeCategories": { + "": "", "3d": "3d", "3d_models": "3d_modeller", "BFL": "BFL", + "Bria": "Bria", "ByteDance": "ByteDance", "Gemini": "Gemini", "Ideogram": "Ideogram", "Kling": "Kling", "LTXV": "LTXV", "Luma": "Luma", + "Meshy": "Meshy", "MiniMax": "MiniMax", "Moonvalley Marey": "Moonvalley Marey", "OpenAI": "OpenAI", - "Pika": "Pika", "PixVerse": "PixVerse", "Recraft": "Recraft", "Rodin": "Rodin", "Runway": "Runway", "Sora": "Sora", "Stability AI": "Stability AI", + "Tencent": "Tencent", + "Topaz": "Topaz", "Tripo": "Tripo", "Veo": "Veo", "Vidu": "Vidu", "Wan": "Wan", + "WaveSpeed": "WaveSpeed", "_for_testing": "_test_için", "advanced": "gelişmiş", "animation": "animasyon", @@ -1299,6 +1710,7 @@ "controlnet": "controlnet", "create": "oluştur", "custom_sampling": "özel_örnekleme", + "dataset": "veri seti", "debug": "hata ayıklama", "deprecated": "kullanımdan kaldırılmış", "edit_models": "modelleri_düzenle", @@ -1310,8 +1722,10 @@ "image": "görüntü", "inpaint": "inpaint", "instructpix2pix": "instructpix2pix", + "kandinsky5": "kandinsky5", "latent": "gizli", "loaders": "yükleyiciler", + "logic": "mantık", "lotus": "lotus", "ltxv": "ltxv", "mask": "maske", @@ -1345,7 +1759,15 @@ "upscaling": "yükseltme", "utils": "yardımcı programlar", "video": "video", - "video_models": "video_modelleri" + "video_models": "video_modelleri", + "zimage": "zimage" + }, + "nodeErrors": { + "content": "Düğüm İçerik Hatası", + "header": "Düğüm Başlık Hatası", + "render": "Düğüm Oluşturma Hatası", + "slots": "Düğüm Slot Hatası", + "widgets": "Düğüm Widget Hatası" }, "nodeHelpPage": { "documentationPage": "belgelendirme sayfası", @@ -1362,6 +1784,7 @@ "notSupported": { "continue": "Devam Et", "continueTooltip": "Cihazımın desteklendiğinden eminim", + "illustrationAlt": "Üzgün kız illüstrasyonu", "learnMore": "Daha Fazla Bilgi Edinin", "message": "Yalnızca aşağıdaki cihazlar desteklenmektedir:", "reportIssue": "Sorun Bildir", @@ -1371,12 +1794,136 @@ }, "title": "Cihazınız desteklenmiyor" }, + "progressToast": { + "allDownloadsCompleted": "Tüm indirmeler tamamlandı", + "downloadingModel": "Model indiriliyor...", + "downloadsFailed": "{count} indirme başarısız oldu | {count} indirme başarısız oldu | {count} indirme başarısız oldu", + "failed": "Başarısız", + "filter": { + "all": "Tümü", + "completed": "Tamamlandı", + "failed": "Başarısız" + }, + "finished": "Tamamlandı", + "importingModels": "Modeller İçe Aktarılıyor", + "noImportsInQueue": "Kuyrukta {filter} yok", + "pending": "Beklemede", + "progressCount": "{completed} / {total}" + }, + "queue": { + "completedIn": "{duration} içinde tamamlandı", + "inQueue": "Kuyrukta...", + "initializingAlmostReady": "Başlatılıyor - Neredeyse hazır", + "jobAddedToQueue": "İş kuyruğa eklendi", + "jobDetails": { + "computeHoursUsed": "Kullanılan hesaplama saati", + "errorMessage": "Hata mesajı", + "estimatedFinishIn": "Bitmesi tahmini", + "estimatedStartIn": "Başlaması tahmini", + "eta": { + "minutes": "Yaklaşık {count} dakika | Yaklaşık {count} dakika", + "minutesRange": "Yaklaşık {lo}-{hi} dakika", + "seconds": "Yaklaşık {count} saniye | Yaklaşık {count} saniye", + "secondsRange": "Yaklaşık {lo}-{hi} saniye" + }, + "failedAfter": "Şu süreden sonra başarısız oldu", + "generatedOn": "Üretildiği zaman", + "header": "İş Detayları", + "jobId": "İş Kimliği", + "queuePosition": "Kuyruk pozisyonu", + "queuePositionValue": "Sizden önce yaklaşık {count} iş var | Sizden önce yaklaşık {count} iş var", + "queuedAt": "Kuyruğa alındı", + "report": "Bildir", + "timeElapsed": "Geçen süre", + "totalGenerationTime": "Toplam üretim süresi", + "workflow": "Çalışma Akışı" + }, + "jobHistory": "İş Geçmişi", + "jobList": { + "sortComputeHoursUsed": "Kullanılan hesaplama saati (en çok ilk)", + "sortMostRecent": "En yeni", + "sortTotalGenerationTime": "Toplam üretim süresi (en uzun ilk)", + "undated": "Tarihsiz" + }, + "jobMenu": { + "addToCurrentWorkflow": "Mevcut çalışma akışına ekle", + "cancelJob": "İşi İptal Et", + "copyErrorMessage": "Hata mesajını kopyala", + "copyJobId": "İş Kimliğini Kopyala", + "delete": "Sil", + "deleteAsset": "Varlığı sil", + "download": "İndir", + "exportWorkflow": "Çalışma akışını dışa aktar", + "inspectAsset": "Varlığı İncele", + "openAsWorkflowNewTab": "Çalışma akışı olarak yeni sekmede aç", + "openWorkflowNewTab": "Çalışma akışını yeni sekmede aç", + "removeJob": "İşi kaldır", + "reportError": "Hata bildir" + }, + "toggleJobHistory": "İş Geçmişini Aç/Kapat" + }, "releaseToast": { + "description": "Bu güncellemedeki en son iyileştirmelere ve özelliklere göz atın.", "newVersionAvailable": "Yeni Sürüm Mevcut!", "skip": "Atla", "update": "Güncelle", "whatsNew": "Yenilikler Neler?" }, + "rightSidePanel": { + "addFavorite": "Favorilere Ekle", + "advancedInputs": "GELİŞMİŞ GİRDİLER", + "bypass": "Atla", + "color": "Düğüm rengi", + "fallbackGroupTitle": "Grup", + "fallbackNodeTitle": "Düğüm", + "favorites": "FAVORİ GİRDİLER", + "favoritesNone": "FAVORİ GİRDİ YOK", + "favoritesNoneDesc": "Favorilere eklediğiniz girdiler burada görünecek", + "favoritesNoneTooltip": "Düğüm seçmeden hızlı erişim için widget'ları yıldızlayın", + "globalSettings": { + "canvas": "TUVAL", + "connectionLinks": "BAĞLANTI LİNKLERİ", + "gridSpacing": "Izgara aralığı", + "linkShape": "Bağlantı şekli", + "nodes": "DÜĞÜMLER", + "nodes2": "Düğümler 2.0", + "searchPlaceholder": "Hızlı ayarlarda ara...", + "showAdvanced": "Gelişmiş parametreleri göster", + "showAdvancedTooltip": "Bu önemli bir ayardır. TRUE olarak ayarlandığında düğümler için tüm gelişmiş parametreleri gösterir", + "showConnectedLinks": "Bağlı linkleri göster", + "showInfoBadges": "Bilgi rozetlerini göster", + "showToolbox": "Seçimde araç kutusunu göster", + "snapNodesToGrid": "Düğümleri ızgaraya hizala", + "title": "Genel Ayarlar", + "viewAllSettings": "Tüm ayarları görüntüle" + }, + "groupSettings": "Grup Ayarları", + "groups": "Gruplar", + "hideAdvancedInputsButton": "Gelişmiş girişleri gizle", + "hideInput": "Girdiyi gizle", + "info": "Bilgi", + "inputs": "GİRİŞLER", + "inputsNone": "GİRİŞ YOK", + "inputsNoneTooltip": "Düğümün girişi yok", + "locateNode": "Düğümü tuvalde bul", + "mute": "Sessiz", + "noSelection": "Bir düğüm seçerek özelliklerini ve bilgisini görebilirsiniz.", + "nodeState": "Düğüm durumu", + "nodes": "Düğümler", + "nodesNoneDesc": "DÜĞÜM YOK", + "noneSearchDesc": "Aramanızla eşleşen öğe yok", + "normal": "Normal", + "parameters": "Parametreler", + "pinned": "Sabitlendi", + "properties": "Özellikler", + "removeFavorite": "Favorilerden Kaldır", + "settings": "Ayarlar", + "showAdvancedInputsButton": "Gelişmiş girdileri göster", + "showInput": "Girdiyi göster", + "title": "Seçili düğüm yok | 1 düğüm seçili | {count} düğüm seçili", + "togglePanel": "Özellikler panelini aç/kapat", + "workflowOverview": "Çalışma Akışı Genel Bakış" + }, "selectionToolbox": { "Bypass Group Nodes": "Bypass Group Nodes", "Set Group Nodes to Always": "Set Group Nodes to Always", @@ -1389,6 +1936,8 @@ "serverConfig": { "modifiedConfigs": "Aşağıdaki sunucu yapılandırmalarını değiştirdiniz. Değişiklikleri uygulamak için yeniden başlatın.", "restart": "Yeniden Başlat", + "restartRequiredToastDetail": "Sunucu yapılandırma değişikliklerini uygulamak için uygulamayı yeniden başlatın.", + "restartRequiredToastSummary": "Yeniden başlatma gerekli", "revertChanges": "Değişiklikleri Geri Al" }, "serverConfigCategories": { @@ -1457,6 +2006,10 @@ "enable-cors-header": { "name": "CORS başlığını etkinleştir: Tüm kaynaklar için \"*\" kullanın veya alan adı belirtin" }, + "enable-manager-legacy-ui": { + "name": "Klasik Manager Arayüzünü Kullan", + "tooltip": "Yeni arayüz yerine klasik ComfyUI-Manager arayüzünü kullanır." + }, "fast": { "name": "Test edilmemiş ve potansiyel olarak kaliteyi düşüren bazı optimizasyonları etkinleştirin." }, @@ -1558,6 +2111,7 @@ "CustomColorPalettes": "Özel Renk Paletleri", "DevMode": "Geliştirici Modu", "EditTokenWeight": "Jeton Ağırlığını Düzenle", + "Execution": "Yürütme", "Extension": "Uzantı", "General": "Genel", "Graph": "Grafik", @@ -1572,12 +2126,14 @@ "Mask Editor": "Maske Düzenleyici", "Menu": "Menü", "ModelLibrary": "Model Kütüphanesi", - "NewEditor": "Yeni Düzenleyici", "Node": "Düğüm", "Node Search Box": "Düğüm Arama Kutusu", "Node Widget": "Düğüm Widget'ı", "NodeLibrary": "Düğüm Kütüphanesi", + "Nodes 2_0": "Nodes 2.0", "Notification Preferences": "Bildirim Tercihleri", + "Other": "Diğer", + "PLY": "PLY", "PlanCredits": "Plan & Krediler", "Pointer": "İşaretçi", "Queue": "Kuyruk", @@ -1596,7 +2152,8 @@ "Vue Nodes": "Vue Düğümleri", "VueNodes": "Vue Düğümleri", "Window": "Pencere", - "Workflow": "İş Akışı" + "Workflow": "İş Akışı", + "Workspace": "Çalışma Alanı" }, "shape": { "CARD": "Kart", @@ -1622,11 +2179,14 @@ "viewControls": "Görünüm Kontrolleri" }, "sideToolbar": { + "activeJobStatus": "Aktif iş: {status}", "assets": "Varlıklar", "backToAssets": "Tüm varlıklara dön", "browseTemplates": "Örnek şablonlara göz atın", "downloads": "İndirmeler", + "generatedAssetsHeader": "Oluşturulan varlıklar", "helpCenter": "Yardım Merkezi", + "importedAssetsHeader": "İçe aktarılan varlıklar", "labels": { "assets": "Varlıklar", "console": "Konsol", @@ -1669,6 +2229,47 @@ }, "openWorkflow": "Yerel dosya sisteminde iş akışını aç", "queue": "Kuyruk", + "queueProgressOverlay": { + "activeJobs": "{count} aktif iş | {count} aktif iş", + "activeJobsShort": "{count} aktif | {count} aktif", + "activeJobsSuffix": "aktif iş", + "cancelJobTooltip": "İşi iptal et", + "clearHistory": "İş kuyruğu geçmişini temizle", + "clearHistoryDialogAssetsNote": "Bu işler tarafından oluşturulan varlıklar silinmeyecek ve her zaman varlık panelinden görüntülenebilir.", + "clearHistoryDialogDescription": "Aşağıdaki tamamlanan veya başarısız olan tüm işler bu İş kuyruğu panelinden kaldırılacaktır.", + "clearHistoryDialogTitle": "İş kuyruğu geçmişinizi temizlemek istiyor musunuz?", + "clearQueueTooltip": "Kuyruğu temizle", + "clearQueued": "Kuyruktakileri temizle", + "colonPercent": ": {percent}", + "currentNode": "Mevcut düğüm:", + "expandCollapsedQueue": "İş kuyruğunu genişlet", + "filterAllWorkflows": "Tüm iş akışları", + "filterBy": "Filtrele", + "filterCurrentWorkflow": "Mevcut iş akışı", + "filterJobs": "İşleri filtrele", + "interruptAll": "Tüm çalışan işleri durdur", + "jobQueue": "İş Kuyruğu", + "jobsCompleted": "{count} iş tamamlandı | {count} iş tamamlandı", + "jobsFailed": "{count} iş başarısız oldu | {count} iş başarısız oldu", + "moreOptions": "Daha fazla seçenek", + "noActiveJobs": "Aktif iş yok", + "preview": "Önizleme", + "queuedSuffix": "kuyrukta", + "running": "çalışıyor", + "showAssets": "Varlıkları göster", + "showAssetsPanel": "Varlık panelini göster", + "sortBy": "Sırala", + "sortJobs": "İşleri sırala", + "stubClipTextEncode": "CLIP Metin Kodlama:", + "title": "Kuyruk İlerlemesi", + "total": "Toplam: {percent}", + "viewAllJobs": "Tüm işleri görüntüle", + "viewGrid": "Izgara görünümü", + "viewJobHistory": "İş geçmişini görüntüle", + "viewList": "Liste görünümü" + }, + "searchAssets": "Varlıkları Ara", + "sidebar": "Kenar Çubuğu", "templates": "Şablonlar", "themeToggle": "Temayı Değiştir", "workflowTab": { @@ -1711,24 +2312,58 @@ "subscription": { "addApiCredits": "API kredisi ekle", "addCredits": "Kredi ekle", + "addCreditsLabel": "İstediğiniz zaman kredi ekleyin", "benefits": { "benefit1": "Partner Düğümleri için aylık krediler — ihtiyaç duyulduğunda yükleyin", "benefit2": "İş başına en fazla 30 dakika çalışma süresi" }, "beta": "BETA", + "billedMonthly": "Aylık faturalandırılır", + "billedYearly": "{total} Yıllık faturalandırılır", + "billingComingSoon": { + "message": "Takım faturalandırması yakında geliyor. Çalışma alanınız için koltuk başına fiyatlandırma ile bir plana abone olabileceksiniz. Güncellemeler için bizi takip edin.", + "title": "Yakında" + }, + "cancelSubscription": "Aboneliği İptal Et", + "changeTo": "{plan} planına geç", "comfyCloud": "Comfy Cloud", + "comfyCloudLogo": "Comfy Cloud Logosu", + "contactOwnerToSubscribe": "Abone olmak için çalışma alanı sahibiyle iletişime geçin", + "contactUs": "Bize ulaşın", + "creditsRemainingThisMonth": "Bu ay kalan krediler", + "creditsRemainingThisYear": "Bu yıl kalan krediler", + "creditsYouveAdded": "Eklediğiniz krediler", + "currentPlan": "Mevcut Plan", + "customLoRAsLabel": "Kendi LoRA'larınızı içe aktarın", + "description": "Sizin için en iyi planı seçin", "expiresDate": "{date} tarihinde sona erer", + "gpuLabel": "RTX 6000 Pro (96GB VRAM)", + "haveQuestions": "Sorularınız mı var veya kurumsal çözüm mü arıyorsunuz?", "invoiceHistory": "Fatura geçmişi", "learnMore": "Daha fazla bilgi edinin", + "managePayment": "Ödemeyi Yönet", + "managePlan": "Planı yönet", "manageSubscription": "Aboneliği yönet", + "maxDuration": { + "creator": "30 dk", + "founder": "30 dk", + "pro": "1 sa", + "standard": "30 dk" + }, + "maxDurationLabel": "Her iş akışı çalıştırma için maksimum süre", "messageSupport": "Destek ekibine mesaj gönder", + "monthly": "Aylık", "monthlyBonusDescription": "Aylık kredi bonusu", + "monthlyCreditsInfo": "Bu krediler her ay yenilenir ve devretmez", + "monthlyCreditsLabel": "Aylık krediler", "monthlyCreditsRollover": "Bu krediler sonraki aya devredilecek", + "mostPopular": "En popüler", "nextBillingCycle": "sonraki fatura döngüsü", "partnerNodesBalance": "\"Partner Düğümleri\" Kredi Bakiyesi", "partnerNodesCredits": "Partner Düğümleri kredileri", "partnerNodesDescription": "Ticari/özel modelleri çalıştırmak için", "perMonth": "USD / ay", + "plansAndPricing": "Planlar ve fiyatlandırma", "prepaidCreditsInfo": "Ayrıca satın alınan ve son kullanma tarihi olmayan krediler", "prepaidDescription": "Ön ödemeli krediler", "renewsDate": "{date} tarihinde yenilenir", @@ -1738,14 +2373,46 @@ "waitingForSubscription": "Aboneliğinizi yeni sekmede tamamlayın. İşleminiz bittiğinde otomatik olarak algılayacağız!" }, "subscribeNow": "Hemen Abone Ol", + "subscribeTo": "{plan} abonesi ol", "subscribeToComfyCloud": "Comfy Cloud'a Abone Ol", "subscribeToRun": "Abone Ol", "subscribeToRunFull": "Çalıştırmaya Abone Ol", + "subscriptionRequiredMessage": "Üyelerin Bulut'ta iş akışlarını çalıştırabilmesi için abonelik gereklidir", + "tierNameYearly": "{name} Yıllık", + "tiers": { + "creator": { + "name": "Yaratıcı" + }, + "founder": { + "name": "Kurucu Sürümü" + }, + "pro": { + "name": "Pro" + }, + "standard": { + "name": "Standart" + } + }, "title": "Abonelik", "titleUnsubscribed": "Comfy Cloud'a Abone Olun", "totalCredits": "Toplam kredi", + "upgrade": "YÜKSELT", + "upgradePlan": "Planı Yükselt", + "upgradeTo": "{plan} planına yükselt", + "usdPerMonth": "USD / ay", + "videoEstimateExplanation": "Bu tahminler, varsayılan ayarlarla (5 saniye, 640x640, 16fps, 4 adım örnekleme) Wan 2.2 Görselden Videoya şablonuna dayanmaktadır.", + "videoEstimateHelp": "Bu şablon hakkında daha fazla bilgi", + "videoEstimateLabel": "Wan 2.2 Görselden Videoya şablonu ile yaklaşık 5 sn'lik video sayısı", + "videoEstimateTryTemplate": "Bu şablonu dene", + "videoTemplateBasedCredits": "Wan 2.2 Görselden Videoya ile oluşturulan videolar", + "viewEnterprise": "Kurumsal çözümü görüntüle", "viewMoreDetails": "Daha fazla detay görüntüle", + "viewMoreDetailsPlans": "Planlar ve fiyatlandırma hakkında daha fazla detay", "viewUsageHistory": "Kullanım geçmişini görüntüle", + "workspaceNotSubscribed": "Bu çalışma alanı bir aboneliğe sahip değil", + "yearly": "Yıllık", + "yearlyCreditsLabel": "Toplam yıllık krediler", + "yearlyDiscount": "%20 İNDİRİM", "yourPlanIncludes": "Planınız şunları içerir:" }, "tabMenu": { @@ -1757,8 +2424,14 @@ "duplicateTab": "Sekmeyi Çoğalt", "removeFromBookmarks": "Yer İmlerinden Kaldır" }, + "templateWidgets": { + "sort": { + "searchPlaceholder": "Ara..." + } + }, "templateWorkflows": { "activeFilters": "Filtreler:", + "allTemplates": "Tüm Şablonlar", "categories": "Kategoriler", "category": { "3D": "3D", @@ -1785,6 +2458,7 @@ "error": { "templateNotFound": "\"{templateName}\" şablonu bulunamadı" }, + "licenseFilter": "Lisans", "loading": "Şablonlar yükleniyor...", "loadingMore": "Daha fazla şablon yükleniyor...", "modelFilter": "Model Filtresi", @@ -1801,12 +2475,14 @@ "default": "Varsayılan", "modelSizeLowToHigh": "Model Boyutu (Düşükten Yükseğe)", "newest": "En Yeni", + "popular": "Popüler", "recommended": "Önerilen", "searchPlaceholder": "Ara...", "vramLowToHigh": "VRAM Kullanımı (Düşükten Yükseğe)" }, "sorting": "Sıralama ölçütü", "title": "Bir Şablonla Başlayın", + "useCaseFilter": "Görevler", "useCasesSelected": "{count} Kullanım Senaryosu" }, "toastMessages": { @@ -1835,9 +2511,22 @@ "failedToLoadModel": "3B model yüklenemedi", "failedToPurchaseCredits": "Kredi satın alınamadı: {error}", "failedToQueue": "Kuyruğa alınamadı", + "failedToToggleCamera": "Kamera açılıp kapatılamadı", + "failedToToggleGrid": "Izgara açılıp kapatılamadı", + "failedToUpdateBackgroundColor": "Arka plan rengi güncellenemedi", + "failedToUpdateBackgroundImage": "Arka plan görseli güncellenemedi", + "failedToUpdateBackgroundRenderMode": "Arka plan render modu {mode} olarak güncellenemedi", + "failedToUpdateEdgeThreshold": "Kenar eşiği güncellenemedi", + "failedToUpdateFOV": "Görüş alanı güncellenemedi", + "failedToUpdateLightIntensity": "Işık şiddeti güncellenemedi", + "failedToUpdateMaterialMode": "Malzeme modu güncellenemedi", + "failedToUpdateUpDirection": "Yukarı yönü güncellenemedi", + "failedToUploadBackgroundImage": "Arka plan görseli yüklenemedi", "fileLoadError": "{fileName} içinde iş akışı bulunamıyor", + "fileTooLarge": "Dosya çok büyük ({size} MB). Desteklenen maksimum boyut {maxSize} MB", "fileUploadFailed": "Dosya yükleme başarısız oldu", "interrupted": "Yürütme kesintiye uğradı", + "legacyMaskEditorDeprecated": "Klasik maske düzenleyici kullanımdan kaldırıldı ve yakında kaldırılacak.", "migrateToLitegraphReroute": "Yeniden yönlendirme düğümleri gelecekteki sürümlerde kaldırılacaktır. Litegraph yerel yeniden yönlendirmeye geçmek için tıklayın.", "modelLoadedSuccessfully": "3B model başarıyla yüklendi", "no3dScene": "Doku uygulanacak 3D sahne yok", @@ -1864,12 +2553,14 @@ "selectUser": "Bir kullanıcı seçin" }, "userSettings": { + "accountSettings": "Hesap ayarları", "email": "E-posta", "name": "İsim", "notSet": "Ayarlanmadı", "provider": "Giriş Sağlayıcı", "title": "Kullanıcı Ayarları", - "updatePassword": "Şifreyi Güncelle" + "updatePassword": "Şifreyi Güncelle", + "workspaceSettings": "Çalışma alanı ayarları" }, "validation": { "descriptionRequired": "Açıklama gerekli", @@ -1898,22 +2589,32 @@ "updateFrontend": "Ön Ucu Güncelle" }, "vueNodesBanner": { - "message": "Düğümler yeni bir görünüm ve his kazandı", + "desc": "– Daha esnek iş akışları, güçlü yeni bileşenler, genişletilebilirlik için tasarlandı", + "title": "Nodes 2.0 Tanıtıldı", "tryItOut": "Deneyin" }, "vueNodesMigration": { "button": "Ayarları Aç", "message": "Klasik düğüm tasarımını mı tercih ediyorsunuz?" }, + "vueNodesMigrationMainMenu": { + "message": "Ana menüden istediğiniz zaman Nodes 2.0'a geri dönebilirsiniz." + }, "welcome": { "getStarted": "Başlayın", "title": "ComfyUI'ye Hoş Geldiniz" }, "whatsNewPopup": { + "later": "Daha Sonra", "learnMore": "Daha fazla bilgi edinin", "noReleaseNotes": "Sürüm notu yok." }, + "widgetFileUpload": { + "browseFiles": "Dosyalara Göz At", + "dropPrompt": "Dosyanızı bırakın veya" + }, "widgets": { + "node2only": "Yalnızca Node 2.0", "selectModel": "Model seç", "uploadSelect": { "placeholder": "Seç...", @@ -1922,6 +2623,26 @@ "placeholderModel": "Model seç...", "placeholderUnknown": "Medya seç...", "placeholderVideo": "Video seç..." + }, + "valueControl": { + "decrement": "Değeri Azalt", + "decrementDesc": "Değerden 1 çıkarır veya önceki seçeneği seçer", + "editSettings": "Kontrol ayarlarını düzenle", + "fixed": "Sabit Değer", + "fixedDesc": "Değeri değiştirmez", + "header": { + "after": "SONRA", + "before": "ÖNCE", + "postfix": "iş akışını çalıştırırken:", + "prefix": "Değeri otomatik olarak güncelle" + }, + "increment": "Değeri Artır", + "incrementDesc": "Değere 1 ekler veya bir sonraki seçeneği seçer", + "linkToGlobal": "Bağla", + "linkToGlobalDesc": "Global Değer'in kontrol ayarına bağlı benzersiz değer", + "linkToGlobalSeed": "Global Değer", + "randomize": "Değeri Rastgele Yap", + "randomizeDesc": "Her üretimden sonra değeri rastgele değiştirir" } }, "workflowService": { @@ -1929,6 +2650,146 @@ "exportWorkflow": "İş Akışını Dışa Aktar", "saveWorkflow": "İş akışını kaydet" }, + "workspace": { + "addedToWorkspace": "{workspaceName} çalışma alanına eklendiniz", + "inviteAccepted": "Davet kabul edildi", + "inviteFailed": "Davet kabul edilemedi", + "unsavedChanges": { + "message": "Kaydedilmemiş değişiklikleriniz var. Bunları iptal edip çalışma alanlarını değiştirmek istiyor musunuz?", + "title": "Kaydedilmemiş Değişiklikler" + } + }, + "workspaceAuth": { + "errors": { + "accessDenied": "Bu çalışma alanına erişiminiz yok", + "invalidFirebaseToken": "Kimlik doğrulama başarısız oldu. Lütfen tekrar giriş yapmayı deneyin.", + "notAuthenticated": "Çalışma alanlarına erişmek için giriş yapmalısınız", + "tokenExchangeFailed": "Çalışma alanı ile kimlik doğrulama başarısız oldu: {error}", + "workspaceNotFound": "Çalışma alanı bulunamadı" + } + }, + "workspacePanel": { + "createWorkspaceDialog": { + "create": "Oluştur", + "message": "Çalışma alanları, üyelerin tek bir kredi havuzunu paylaşmasını sağlar. Oluşturduktan sonra sahibi olacaksınız.", + "nameLabel": "Çalışma alanı adı*", + "namePlaceholder": "Çalışma alanı adını girin", + "title": "Yeni bir çalışma alanı oluştur" + }, + "dashboard": { + "placeholder": "Çalışma alanı kontrol paneli ayarları" + }, + "deleteDialog": { + "message": "Kullanılmamış krediler veya kaydedilmemiş varlıklar kaybolacak. Bu işlem geri alınamaz.", + "messageWithName": "\"{name}\" silinsin mi? Kullanılmamış krediler veya kaydedilmemiş varlıklar kaybolacak. Bu işlem geri alınamaz.", + "title": "Bu çalışma alanı silinsin mi?" + }, + "editWorkspaceDialog": { + "nameLabel": "Çalışma alanı adı", + "save": "Kaydet", + "title": "Çalışma alanı detaylarını düzenle" + }, + "invite": "Davet Et", + "inviteLimitReached": "Maksimum 50 üye sınırına ulaştınız", + "inviteMember": "Üye Davet Et", + "inviteMemberDialog": { + "createLink": "Bağlantı oluştur", + "linkCopied": "Kopyalandı", + "linkCopyFailed": "Bağlantı kopyalanamadı", + "linkStep": { + "copyLink": "Bağlantıyı Kopyala", + "done": "Tamamlandı", + "message": "Hesaplarının bu e-posta adresini kullandığından emin olun.", + "title": "Bu bağlantıyı kişiye gönderin" + }, + "message": "Birine göndermek için paylaşılabilir bir davet bağlantısı oluştur", + "placeholder": "Kişinin e-posta adresini girin", + "title": "Bu çalışma alanına birini davet et" + }, + "leaveDialog": { + "leave": "Ayrıl", + "message": "Çalışma alanı sahibiyle iletişime geçmedikçe tekrar katılamazsınız.", + "title": "Bu çalışma alanından ayrılsın mı?" + }, + "members": { + "actions": { + "copyLink": "Davet bağlantısını kopyala", + "removeMember": "Üyeyi kaldır", + "revokeInvite": "Daveti geri al" + }, + "columns": { + "expiryDate": "Son kullanma tarihi", + "inviteDate": "Davet tarihi", + "joinDate": "Katılma tarihi" + }, + "createNewWorkspace": "yeni bir tane oluşturun.", + "membersCount": "{count}/50 Üye", + "noInvites": "Bekleyen davet yok", + "noMembers": "Üye yok", + "pendingInvitesCount": "{count} bekleyen davet | {count} bekleyen davet", + "personalWorkspaceMessage": "Şu anda kişisel çalışma alanınıza başka üyeler davet edemezsiniz. Bir çalışma alanına üye eklemek için,", + "tabs": { + "active": "Aktif", + "pendingCount": "Bekliyor ({count})" + } + }, + "menu": { + "deleteWorkspace": "Çalışma Alanını Sil", + "deleteWorkspaceDisabledTooltip": "Önce çalışma alanınızın aktif aboneliğini iptal edin", + "editWorkspace": "Çalışma alanı detaylarını düzenle", + "leaveWorkspace": "Çalışma Alanından Ayrıl" + }, + "removeMemberDialog": { + "error": "Üye kaldırılamadı", + "message": "Bu üye çalışma alanınızdan kaldırılacak. Kullandıkları krediler iade edilmeyecek.", + "remove": "Üyeyi kaldır", + "success": "Üye kaldırıldı", + "title": "Bu üye kaldırılsın mı?" + }, + "revokeInviteDialog": { + "message": "Bu üye artık çalışma alanınıza katılamayacak. Davet bağlantısı geçersiz olacak.", + "revoke": "Davet Etme", + "title": "Bu kişiyi davet etme?" + }, + "tabs": { + "dashboard": "Kontrol Paneli", + "membersCount": "Üyeler ({count})", + "planCredits": "Plan & Kredi" + }, + "toast": { + "failedToCreateWorkspace": "Çalışma alanı oluşturulamadı", + "failedToDeleteWorkspace": "Çalışma alanı silinemedi", + "failedToFetchWorkspaces": "Çalışma alanları yüklenemedi", + "failedToLeaveWorkspace": "Çalışma alanından ayrılamadı", + "failedToUpdateWorkspace": "Çalışma alanı güncellenemedi", + "workspaceCreated": { + "message": "Bir plana abone olun, ekip arkadaşlarınızı davet edin ve iş birliğine başlayın.", + "subscribe": "Abone Ol", + "title": "Çalışma alanı oluşturuldu" + }, + "workspaceDeleted": { + "message": "Çalışma alanı kalıcı olarak silindi.", + "title": "Çalışma alanı silindi" + }, + "workspaceLeft": { + "message": "Çalışma alanından ayrıldınız.", + "title": "Çalışma alanından ayrıldınız" + }, + "workspaceUpdated": { + "message": "Çalışma alanı detayları kaydedildi.", + "title": "Çalışma alanı güncellendi" + } + } + }, + "workspaceSwitcher": { + "createWorkspace": "Yeni çalışma alanı oluştur", + "maxWorkspacesReached": "Yalnızca 10 çalışma alanına sahip olabilirsiniz. Yeni bir tane oluşturmak için birini silin.", + "personal": "Kişisel", + "roleMember": "Üye", + "roleOwner": "Sahip", + "subscribe": "Abone Ol", + "switchWorkspace": "Çalışma alanı değiştir" + }, "zoomControls": { "hideMinimap": "Mini Haritayı Gizle", "label": "Yakınlaştırma Kontrolleri", diff --git a/src/locales/tr/nodeDefs.json b/src/locales/tr/nodeDefs.json index f592a4bc5..7d573ddf8 100644 --- a/src/locales/tr/nodeDefs.json +++ b/src/locales/tr/nodeDefs.json @@ -39,6 +39,87 @@ "sigmas": { "name": "sigmalar" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AddTextPrefix": { + "display_name": "Metin Ön Eki Ekle", + "inputs": { + "prefix": { + "name": "ön ek", + "tooltip": "Eklenecek ön ek." + }, + "texts": { + "name": "metinler", + "tooltip": "İşlenecek metin." + } + }, + "outputs": { + "0": { + "name": "metinler", + "tooltip": "İşlenmiş metinler" + } + } + }, + "AddTextSuffix": { + "display_name": "Metin Son Eki Ekle", + "inputs": { + "suffix": { + "name": "son ek", + "tooltip": "Eklenecek son ek." + }, + "texts": { + "name": "metinler", + "tooltip": "İşlenecek metin." + } + }, + "outputs": { + "0": { + "name": "metinler", + "tooltip": "İşlenmiş metinler" + } + } + }, + "AdjustBrightness": { + "display_name": "Parlaklığı Ayarla", + "inputs": { + "factor": { + "name": "faktör", + "tooltip": "Parlaklık faktörü. 1.0 = değişiklik yok, <1.0 = daha koyu, >1.0 = daha parlak." + }, + "images": { + "name": "görseller", + "tooltip": "İşlenecek görsel." + } + }, + "outputs": { + "0": { + "name": "görseller", + "tooltip": "İşlenmiş görseller" + } + } + }, + "AdjustContrast": { + "display_name": "Kontrastı Ayarla", + "inputs": { + "factor": { + "name": "faktör", + "tooltip": "Kontrast faktörü. 1.0 = değişiklik yok, <1.0 = daha az kontrast, >1.0 = daha fazla kontrast." + }, + "images": { + "name": "görseller", + "tooltip": "İşlenecek görsel." + } + }, + "outputs": { + "0": { + "name": "görseller", + "tooltip": "İşlenmiş görseller" + } } }, "AlignYourStepsScheduler": { @@ -70,6 +151,11 @@ "name": "volume", "tooltip": "Desibel (dB) cinsinden ses seviyesi ayarı. 0 = değişiklik yok, +6 = iki katı, -6 = yarısı, vb." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "AudioConcat": { @@ -86,6 +172,11 @@ "name": "direction", "tooltip": "Ses2'nin ses1'den sonra mı yoksa önce mi ekleneceği." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "AudioEncoderEncode": { @@ -131,6 +222,11 @@ "name": "merge_method", "tooltip": "Ses dalga formlarını birleştirmek için kullanılan yöntem." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BasicGuider": { @@ -142,6 +238,11 @@ "model": { "name": "model" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BasicScheduler": { @@ -159,6 +260,50 @@ "steps": { "name": "adımlar" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchImagesNode": { + "display_name": "Toplu Görseller", + "inputs": { + "images": { + "name": "görseller" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchLatentsNode": { + "display_name": "Toplu Latentler", + "inputs": { + "latents": { + "name": "latentler" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchMasksNode": { + "display_name": "Toplu Maskeler", + "inputs": { + "masks": { + "name": "maskeler" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BetaSamplingScheduler": { @@ -176,6 +321,73 @@ "steps": { "name": "adımlar" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BriaImageEditNode": { + "description": "Bria'nın en son modeliyle görüntüleri düzenleyin", + "display_name": "Bria Görüntü Düzenleme", + "inputs": { + "control_after_generate": { + "name": "oluşturduktan sonra kontrol" + }, + "guidance_scale": { + "name": "yönlendirme_ölçeği", + "tooltip": "Daha yüksek değer, görüntünün isteme daha yakın olmasını sağlar." + }, + "image": { + "name": "görüntü" + }, + "mask": { + "name": "maske", + "tooltip": "Atlanırsa, düzenleme tüm görüntüye uygulanır." + }, + "model": { + "name": "model" + }, + "moderation": { + "name": "denetleme", + "tooltip": "Denetleme ayarları" + }, + "moderation_prompt_content_moderation": { + "name": "istem_içeriği_denetleme" + }, + "moderation_visual_input_moderation": { + "name": "görsel_girdi_denetleme" + }, + "moderation_visual_output_moderation": { + "name": "görsel_çıktı_denetleme" + }, + "negative_prompt": { + "name": "negatif_istem" + }, + "prompt": { + "name": "istem", + "tooltip": "Görüntüyü düzenlemek için talimat" + }, + "seed": { + "name": "tohum" + }, + "steps": { + "name": "adımlar" + }, + "structured_prompt": { + "name": "yapılandırılmış_istem", + "tooltip": "JSON formatında yapılandırılmış düzenleme istemi içeren bir dize. Hassas ve programatik kontrol için normal istem yerine bunu kullanın." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "yapılandırılmış_istem", + "tooltip": null + } } }, "ByteDanceFirstLastFrameNode": { @@ -201,13 +413,16 @@ "name": "ilk_kare", "tooltip": "Video için kullanılacak ilk kare." }, + "generate_audio": { + "name": "ses_oluştur", + "tooltip": "Bu parametre, seedance-1-5-pro modeli dışında tüm modeller için yok sayılır." + }, "last_frame": { "name": "son_kare", "tooltip": "Video için kullanılacak son kare." }, "model": { - "name": "model", - "tooltip": "Model adı" + "name": "model" }, "prompt": { "name": "prompt", @@ -248,8 +463,7 @@ "tooltip": "Düzenlenecek temel görüntü" }, "model": { - "name": "model", - "tooltip": "Model adı" + "name": "model" }, "prompt": { "name": "istek", @@ -286,8 +500,7 @@ "tooltip": "Görsel için özel yükseklik. Bu değer yalnızca `size_preset` `Custom` olarak ayarlandığında çalışır" }, "model": { - "name": "model", - "tooltip": "Model adı" + "name": "model" }, "prompt": { "name": "prompt", @@ -336,8 +549,7 @@ "tooltip": "Bir ila dört görsel." }, "model": { - "name": "model", - "tooltip": "Model adı" + "name": "model" }, "prompt": { "name": "prompt", @@ -381,13 +593,16 @@ "name": "süre", "tooltip": "Oluşturulan videonun saniye cinsinden süresi." }, + "generate_audio": { + "name": "ses_oluştur", + "tooltip": "Bu parametre, seedance-1-5-pro modeli dışında tüm modeller için yok sayılır." + }, "image": { "name": "görüntü", "tooltip": "Video için kullanılacak ilk kare." }, "model": { - "name": "model", - "tooltip": "Model adı" + "name": "model" }, "prompt": { "name": "prompt", @@ -489,9 +704,12 @@ "name": "süre", "tooltip": "Çıktı videosunun saniye cinsinden süresi." }, + "generate_audio": { + "name": "ses_oluştur", + "tooltip": "Bu parametre, seedance-1-5-pro modeli dışında tüm modeller için yok sayılır." + }, "model": { - "name": "model", - "tooltip": "Model adı" + "name": "model" }, "prompt": { "name": "prompt", @@ -531,6 +749,11 @@ "positive": { "name": "pozitif" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "CFGNorm": { @@ -769,6 +992,25 @@ } } }, + "CLIPTextEncodeKandinsky5": { + "display_name": "CLIPTextEncodeKandinsky5", + "inputs": { + "clip": { + "name": "clip" + }, + "clip_l": { + "name": "clip_l" + }, + "qwen25_7b": { + "name": "qwen25_7b" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "CLIPTextEncodeLumina2": { "description": "Bir sistem istemini ve bir kullanıcı istemini bir CLIP modeli kullanarak, belirli görüntülerin oluşturulmasına yönelik difüzyon modelini yönlendirmek için kullanılabilecek bir gömme içine kodlar.", "display_name": "Lumina2 için CLIP Metin Kodlama", @@ -959,6 +1201,29 @@ } } }, + "CenterCropImages": { + "display_name": "Ortadan Kırp Görseller", + "inputs": { + "height": { + "name": "yükseklik", + "tooltip": "Kırpma yüksekliği." + }, + "images": { + "name": "görseller", + "tooltip": "İşlenecek görsel." + }, + "width": { + "name": "genişlik", + "tooltip": "Kırpma genişliği." + } + }, + "outputs": { + "0": { + "name": "görseller", + "tooltip": "İşlenmiş görseller" + } + } + }, "CheckpointLoader": { "display_name": "Yapılandırmayla Kontrol Noktası Yükle (ESKİ)", "inputs": { @@ -1095,6 +1360,26 @@ } } }, + "ComfySwitchNode": { + "display_name": "Anahtar", + "inputs": { + "on_false": { + "name": "yanlışta" + }, + "on_true": { + "name": "doğruda" + }, + "switch": { + "name": "anahtar" + } + }, + "outputs": { + "0": { + "name": "çıktı", + "tooltip": null + } + } + }, "ConditioningAverage": { "display_name": "KoşullandırmaOrtalaması", "inputs": { @@ -1327,14 +1612,14 @@ "name": "saniye_toplam" } }, - "outputs": { - "0": { - "name": "pozitif" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "negatif" + { + "tooltip": null } - } + ] }, "ConditioningTimestepsRange": { "display_name": "Zaman Adımları Aralığı", @@ -1391,6 +1676,10 @@ "name": "boyut", "tooltip": "Bağlam pencerelerinin uygulanacağı boyut." }, + "freenoise": { + "name": "serbest_gürültü", + "tooltip": "FreeNoise gürültü karıştırmasını uygulayıp uygulamayacağını belirtir, pencere birleştirmesini iyileştirir." + }, "fuse_method": { "name": "birleştirme yöntemi", "tooltip": "Bağlam pencerelerini birleştirmek için kullanılacak yöntem." @@ -1791,8 +2080,32 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, + "CustomCombo": { + "display_name": "Özel Kombinasyon", + "inputs": { + "choice": { + "name": "seçim" + }, + "index": { + }, + "option1": { + } + }, + "outputs": [ + null, + { + "name": "INDEX", + "tooltip": null + } + ] + }, "DiffControlNetLoader": { "display_name": "ControlNet Modelini Yükle (fark)", "inputs": { @@ -1829,7 +2142,12 @@ } }, "DisableNoise": { - "display_name": "Gürültüyü Devre Dışı Bırak" + "display_name": "Gürültüyü Devre Dışı Bırak", + "outputs": { + "0": { + "tooltip": null + } + } }, "DualCFGGuider": { "display_name": "İkili CFG Rehberi", @@ -1855,6 +2173,11 @@ "style": { "name": "stil" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "DualCLIPLoader": { @@ -1938,6 +2261,11 @@ "name": "örnekleme_oranı", "tooltip": "Boş ses klibinin örnekleme oranı." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyChromaRadianceLatentImage": { @@ -1981,6 +2309,25 @@ } } }, + "EmptyFlux2LatentImage": { + "display_name": "Boş Flux 2 Latent", + "inputs": { + "batch_size": { + "name": "toplu_boyut" + }, + "height": { + "name": "yükseklik" + }, + "width": { + "name": "genişlik" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptyHunyuanImageLatent": { "display_name": "Boş Hunyuan Görüntü Gizli", "inputs": { @@ -2022,6 +2369,28 @@ } } }, + "EmptyHunyuanVideo15Latent": { + "display_name": "Boş HunyuanVideo 1.5 Latent", + "inputs": { + "batch_size": { + "name": "toplu_boyut" + }, + "height": { + "name": "yükseklik" + }, + "length": { + "name": "uzunluk" + }, + "width": { + "name": "genişlik" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptyImage": { "display_name": "Boş Görüntü", "inputs": { @@ -2071,6 +2440,11 @@ "seconds": { "name": "saniye" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyLatentHunyuan3Dv2": { @@ -2083,6 +2457,11 @@ "resolution": { "name": "çözünürlük" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyLatentImage": { @@ -2130,6 +2509,28 @@ } } }, + "EmptyQwenImageLayeredLatentImage": { + "display_name": "Boş Qwen Görsel Katmanlı Latent", + "inputs": { + "batch_size": { + "name": "toplu_boyut" + }, + "height": { + "name": "yükseklik" + }, + "layers": { + "name": "katmanlar" + }, + "width": { + "name": "genişlik" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptySD3LatentImage": { "display_name": "BoşSD3GizliGörüntü", "inputs": { @@ -2177,6 +2578,11 @@ "steps": { "name": "adımlar" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ExtendIntermediateSigmas": { @@ -2197,6 +2603,11 @@ "steps": { "name": "adımlar" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FeatherMask": { @@ -2217,6 +2628,11 @@ "top": { "name": "üst" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FlipSigmas": { @@ -2225,6 +2641,102 @@ "sigmas": { "name": "sigmalar" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2MaxImageNode": { + "description": "İstem ve çözünürlüğe göre senkron bir şekilde görseller üretir.", + "display_name": "Flux.2 [max] Görsel", + "inputs": { + "control_after_generate": { + "name": "oluşturduktan sonra kontrol" + }, + "height": { + "name": "yükseklik" + }, + "images": { + "name": "görseller", + "tooltip": "Referans olarak kullanılacak en fazla 9 görsel." + }, + "prompt": { + "name": "istem", + "tooltip": "Görsel oluşturma veya düzenleme için istem" + }, + "prompt_upsampling": { + "name": "istem üst örnekleme", + "tooltip": "İstem üzerinde üst örnekleme yapılıp yapılmayacağı. Aktifse, daha yaratıcı üretim için istemi otomatik olarak değiştirir." + }, + "seed": { + "name": "tohum", + "tooltip": "Gürültü oluşturmak için kullanılan rastgele tohum." + }, + "width": { + "name": "genişlik" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2ProImageNode": { + "description": "İstem ve çözünürlüğe göre senkron bir şekilde görseller üretir.", + "display_name": "Flux.2 [pro] Görsel", + "inputs": { + "control_after_generate": { + "name": "oluşturduktan sonra kontrol" + }, + "height": { + "name": "yükseklik" + }, + "images": { + "name": "görseller", + "tooltip": "Referans olarak kullanılacak en fazla 9 görsel." + }, + "prompt": { + "name": "istem", + "tooltip": "Görsel oluşturma veya düzenleme için istem" + }, + "prompt_upsampling": { + "name": "istem üst örnekleme", + "tooltip": "İstem üzerinde üst örnekleme yapılıp yapılmayacağı. Aktifse, daha yaratıcı üretim için istemi otomatik olarak değiştirir." + }, + "seed": { + "name": "tohum", + "tooltip": "Gürültü oluşturmak için kullanılan rastgele tohum." + }, + "width": { + "name": "genişlik" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2Scheduler": { + "display_name": "Flux2Scheduler", + "inputs": { + "height": { + "name": "yükseklik" + }, + "steps": { + "name": "adım" + }, + "width": { + "name": "genişlik" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FluxDisableGuidance": { @@ -2547,6 +3059,11 @@ "s2": { "name": "s2" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FreeU_V2": { @@ -2567,6 +3084,11 @@ "s2": { "name": "s2" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "GITSScheduler": { @@ -2625,6 +3147,58 @@ } } }, + "GeminiImage2Node": { + "description": "Google Vertex API üzerinden senkron olarak görsel oluşturun veya düzenleyin.", + "display_name": "Nano Banana Pro (Google Gemini Image)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "'auto' olarak ayarlanırsa, giriş görselinizin en-boy oranı kullanılır; görsel yoksa genellikle 16:9 kare oluşturulur." + }, + "control_after_generate": { + "name": "control after generate" + }, + "files": { + "name": "files", + "tooltip": "Model için bağlam olarak kullanılacak isteğe bağlı dosya(lar). Gemini İçerik Üretimi Girdi Dosyaları düğümünden gelen girdileri kabul eder." + }, + "images": { + "name": "images", + "tooltip": "İsteğe bağlı referans görsel(ler)i. Birden fazla görsel eklemek için Toplu Görseller (Batch Images) düğümünü kullanın (en fazla 14)." + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "Oluşturulacak görseli veya uygulanacak düzenlemeleri tanımlayan metin istemi. Modelin uyması gereken kısıtlamaları, stilleri veya detayları ekleyin." + }, + "resolution": { + "name": "resolution", + "tooltip": "Hedef çıktı çözünürlüğü. 2K/4K için yerel Gemini yükselticisi kullanılır." + }, + "response_modalities": { + "name": "response_modalities", + "tooltip": "'IMAGE' seçilirse yalnızca görsel çıktısı alınır, 'IMAGE+TEXT' seçilirse hem oluşturulan görsel hem de metin yanıtı döner." + }, + "seed": { + "name": "seed", + "tooltip": "Seed belirli bir değere sabitlendiğinde, model tekrarlanan isteklerde aynı yanıtı vermeye çalışır. Deterministik çıktı garanti edilmez. Ayrıca, model veya parametre ayarlarını (ör. sıcaklık) değiştirmek, aynı seed değeriyle bile yanıtın farklı olmasına neden olabilir. Varsayılan olarak rastgele bir seed değeri kullanılır." + }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "Bir yapay zekanın davranışını belirleyen temel talimatlar." + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "tooltip": null + } + } + }, "GeminiImageNode": { "description": "Google API üzerinden görüntüleri eşzamanlı olarak düzenleyin.", "display_name": "Google Gemini Görsel", @@ -2652,9 +3226,17 @@ "name": "istek", "tooltip": "Oluşturma için metin isteği" }, + "response_modalities": { + "name": "response_modalities", + "tooltip": "'IMAGE' seçilirse yalnızca görsel çıktısı alınır, 'IMAGE+TEXT' seçilirse hem oluşturulan görsel hem de metin yanıtı döner." + }, "seed": { "name": "tohum", "tooltip": "Tohum belirli bir değere sabitlendiğinde, model tekrarlanan istekler için aynı yanıtı sağlamak için elinden geleni yapar. Belirleyici çıktı garanti edilmez. Ayrıca, modeli veya sıcaklık gibi parametre ayarlarını değiştirmek, aynı tohum değerini kullansanız bile yanıtta değişikliklere neden olabilir. Varsayılan olarak rastgele bir tohum değeri kullanılır." + }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "Bir yapay zekanın davranışını belirleyen temel talimatlar." } }, "outputs": { @@ -2716,6 +3298,10 @@ "name": "seed", "tooltip": "Seed belirli bir değere sabitlendiğinde, model tekrarlanan istekler için aynı yanıtı sağlamak için elinden geleni yapar. Deterministik çıktı garanti edilmez. Ayrıca, modeli veya sıcaklık gibi parametre ayarlarını değiştirmek, aynı seed değerini kullansanız bile yanıtta değişikliklere neden olabilir. Varsayılan olarak rastgele bir seed değeri kullanılır." }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "Bir yapay zekanın davranışını belirleyen temel talimatlar." + }, "video": { "name": "video", "tooltip": "Model için bağlam olarak kullanılacak isteğe bağlı video." @@ -2727,6 +3313,72 @@ } } }, + "GenerateTracks": { + "display_name": "GenerateTracks", + "inputs": { + "bezier": { + "name": "bezier", + "tooltip": "Orta noktayı kontrol noktası olarak kullanarak Bezier eğrisi yolunu etkinleştir." + }, + "end_x": { + "name": "bitiş_x", + "tooltip": "Bitiş pozisyonu için normalize edilmiş X koordinatı (0-1)." + }, + "end_y": { + "name": "bitiş_y", + "tooltip": "Bitiş pozisyonu için normalize edilmiş Y koordinatı (0-1)." + }, + "height": { + "name": "yükseklik" + }, + "interpolation": { + "name": "enterpolasyon", + "tooltip": "Yol boyunca hareketin zamanlamasını/hızını kontrol eder." + }, + "mid_x": { + "name": "orta_x", + "tooltip": "Bezier eğrisi için normalize edilmiş X kontrol noktası. Sadece 'bezier' etkinleştirildiğinde kullanılır." + }, + "mid_y": { + "name": "orta_y", + "tooltip": "Bezier eğrisi için normalize edilmiş Y kontrol noktası. Sadece 'bezier' etkinleştirildiğinde kullanılır." + }, + "num_frames": { + "name": "kare_sayısı" + }, + "num_tracks": { + "name": "iz_sayısı" + }, + "start_x": { + "name": "başlangıç_x", + "tooltip": "Başlangıç pozisyonu için normalize edilmiş X koordinatı (0-1)." + }, + "start_y": { + "name": "başlangıç_y", + "tooltip": "Başlangıç pozisyonu için normalize edilmiş Y koordinatı (0-1)." + }, + "track_mask": { + "name": "iz_maskesi", + "tooltip": "Görünür kareleri belirtmek için isteğe bağlı maske." + }, + "track_spread": { + "name": "iz_aralığı", + "tooltip": "İzler arasındaki normalize mesafe. İzler hareket yönüne dik olarak yayılır." + }, + "width": { + "name": "genişlik" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "iz_uzunluğu", + "tooltip": null + } + } + }, "GetImageSize": { "description": "Resmin genişlik ve yüksekliğini döndürür ve değiştirmeden iletir.", "display_name": "Resim Boyutunu Al", @@ -2735,17 +3387,17 @@ "name": "image" } }, - "outputs": { - "0": { - "name": "width" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "height" + { + "tooltip": null }, - "2": { - "name": "batch_size" + { + "tooltip": null } - } + ] }, "GetVideoComponents": { "description": "Bir videodan tüm bileşenleri çıkarır: kareler, ses ve kare hızı.", @@ -2783,6 +3435,11 @@ "tapered_corners": { "name": "sivri_köşeler" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Hunyuan3Dv2Conditioning": { @@ -2792,14 +3449,14 @@ "name": "clip_görü_çıktısı" } }, - "outputs": { - "0": { - "name": "pozitif" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "negatif" + { + "tooltip": null } - } + ] }, "Hunyuan3Dv2ConditioningMultiView": { "display_name": "Hunyuan3Dv2ÇokluGörünümKoşullandırma", @@ -2817,14 +3474,14 @@ "name": "sağ" } }, - "outputs": { - "0": { - "name": "pozitif" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "negatif" + { + "tooltip": null } - } + ] }, "HunyuanImageToVideo": { "display_name": "HunyuanGörüntüdenVideoya", @@ -2896,6 +3553,120 @@ } } }, + "HunyuanVideo15ImageToVideo": { + "display_name": "HunyuanVideo15ImageToVideo", + "inputs": { + "batch_size": { + "name": "toplu_boyutu" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "height": { + "name": "yükseklik" + }, + "length": { + "name": "uzunluk" + }, + "negative": { + "name": "negatif" + }, + "positive": { + "name": "pozitif" + }, + "start_image": { + "name": "başlangıç_görseli" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "genişlik" + } + }, + "outputs": { + "0": { + "name": "pozitif", + "tooltip": null + }, + "1": { + "name": "negatif", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "HunyuanVideo15LatentUpscaleWithModel": { + "display_name": "Hunyuan Video 15 Latent Upscale With Model", + "inputs": { + "crop": { + "name": "kırp" + }, + "height": { + "name": "yükseklik" + }, + "model": { + "name": "model" + }, + "samples": { + "name": "örnekler" + }, + "upscale_method": { + "name": "büyütme_yöntemi" + }, + "width": { + "name": "genişlik" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "HunyuanVideo15SuperResolution": { + "display_name": "HunyuanVideo15SuperResolution", + "inputs": { + "clip_vision_output": { + "name": "clip_vision_output" + }, + "latent": { + "name": "latent" + }, + "negative": { + "name": "negatif" + }, + "noise_augmentation": { + "name": "gürültü_artırımı" + }, + "positive": { + "name": "pozitif" + }, + "start_image": { + "name": "başlangıç_görseli" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "pozitif", + "tooltip": null + }, + "1": { + "name": "negatif", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, "HyperTile": { "display_name": "HiperDöşeme", "inputs": { @@ -3100,6 +3871,11 @@ "strength": { "name": "güç" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageBatch": { @@ -3163,6 +3939,26 @@ "image": { "name": "görüntü" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageCompare": { + "description": "İki görüntüyü yan yana bir kaydırıcı ile karşılaştırır.", + "display_name": "Görüntü Karşılaştırma", + "inputs": { + "compare_view": { + "name": "karşılaştırma_görünümü" + }, + "image_a": { + "name": "görüntü_a" + }, + "image_b": { + "name": "görüntü_b" + } } }, "ImageCompositeMasked": { @@ -3186,6 +3982,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageCrop": { @@ -3206,6 +4007,30 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageDeduplication": { + "display_name": "Görsel Çoğaltma Kaldırma", + "inputs": { + "images": { + "name": "görseller", + "tooltip": "İşlenecek görsellerin listesi." + }, + "similarity_threshold": { + "name": "benzerlik_eşiği", + "tooltip": "Benzerlik eşiği (0-1). Daha yüksek değer daha benzer anlamına gelir. Bu eşiğin üzerindeki görseller çoğaltılmış kabul edilir." + } + }, + "outputs": { + "0": { + "name": "görseller", + "tooltip": "İşlenmiş görseller" + } } }, "ImageFlip": { @@ -3217,6 +4042,11 @@ "image": { "name": "görüntü" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageFromBatch": { @@ -3231,6 +4061,42 @@ "length": { "name": "uzunluk" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageGrid": { + "display_name": "Görsel Izgarası", + "inputs": { + "cell_height": { + "name": "hücre_yüksekliği", + "tooltip": "Izgaradaki her hücrenin yüksekliği." + }, + "cell_width": { + "name": "hücre_genişliği", + "tooltip": "Izgaradaki her hücrenin genişliği." + }, + "columns": { + "name": "sütunlar", + "tooltip": "Izgaradaki sütun sayısı." + }, + "images": { + "name": "görseller", + "tooltip": "İşlenecek görsellerin listesi." + }, + "padding": { + "name": "boşluk", + "tooltip": "Görseller arasındaki boşluk." + } + }, + "outputs": { + "0": { + "name": "görseller", + "tooltip": "İşlenmiş görseller" + } } }, "ImageInvert": { @@ -3339,6 +4205,11 @@ "rotation": { "name": "döndürme" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageScale": { @@ -3387,6 +4258,11 @@ "upscale_method": { "name": "ölçeklendirme yöntemi" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageScaleToTotalPixels": { @@ -3398,6 +4274,9 @@ "megapixels": { "name": "megapiksel" }, + "resolution_steps": { + "name": "çözünürlük_adımları" + }, "upscale_method": { "name": "büyütme_yöntemi" } @@ -3452,6 +4331,11 @@ "spacing_width": { "name": "boşluk genişliği" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageToMask": { @@ -3463,6 +4347,11 @@ "image": { "name": "görüntü" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageUpscaleWithModel": { @@ -3572,6 +4461,29 @@ "mask": { "name": "maske" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "JoinAudioChannels": { + "description": "Sol ve sağ mono ses kanallarını stereo sese birleştirir.", + "display_name": "Ses Kanallarını Birleştir", + "inputs": { + "audio_left": { + "name": "ses_sol" + }, + "audio_right": { + "name": "ses_sağ" + } + }, + "outputs": { + "0": { + "name": "ses", + "tooltip": null + } } }, "JoinImageWithAlpha": { @@ -3697,6 +4609,58 @@ "sampler_name": { "name": "örnekleyici_adı" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Kandinsky5ImageToVideo": { + "display_name": "Kandinsky5ImageToVideo", + "inputs": { + "batch_size": { + "name": "toplu_boyutu" + }, + "height": { + "name": "yükseklik" + }, + "length": { + "name": "uzunluk" + }, + "negative": { + "name": "negatif" + }, + "positive": { + "name": "pozitif" + }, + "start_image": { + "name": "başlangıç_görseli" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "genişlik" + } + }, + "outputs": { + "0": { + "name": "pozitif", + "tooltip": null + }, + "1": { + "name": "negatif", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": "Boş video latent" + }, + "3": { + "name": "cond_latent", + "tooltip": "Temiz kodlanmış başlangıç görselleri, model çıktısı latentlerinin gürültülü başlangıcını değiştirmek için kullanılır" + } } }, "KarrasScheduler": { @@ -3714,6 +4678,11 @@ "steps": { "name": "adımlar" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "KlingCameraControlI2VNode": { @@ -3869,7 +4838,6 @@ } }, "KlingImage2VideoNode": { - "description": "Kling Görüntüden Videoya Düğümü", "display_name": "Kling Görüntüden Videoya", "inputs": { "aspect_ratio": { @@ -3957,6 +4925,35 @@ } } }, + "KlingImageToVideoWithAudio": { + "display_name": "Kling Görsel (İlk Kare) ile Videoya ve Sesli", + "inputs": { + "duration": { + "name": "süre" + }, + "generate_audio": { + "name": "ses_oluştur" + }, + "mode": { + "name": "mod" + }, + "model_name": { + "name": "model_adı" + }, + "prompt": { + "name": "istem", + "tooltip": "Pozitif metin istemi." + }, + "start_frame": { + "name": "başlangıç_kare" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingLipSyncAudioToVideoNode": { "description": "Kling Dudak Senkronizasyonu Sesten Videoya Düğümü. Bir video dosyasındaki ağız hareketlerini bir ses dosyasının ses içeriğiyle senkronize eder.", "display_name": "Kling Dudak Senkronizasyonu Video ile Ses", @@ -4018,6 +5015,227 @@ } } }, + "KlingMotionControl": { + "display_name": "Kling Hareket Kontrolü", + "inputs": { + "character_orientation": { + "name": "karakter_yönelimi", + "tooltip": "Karakterin bakış/yöneliminin nereden geldiğini kontrol eder.\nvideo: hareketler, ifadeler, kamera hareketleri ve yönelim hareket referans videosunu takip eder (diğer detaylar istem ile).\ngörsel: hareketler ve ifadeler yine hareket referans videosunu takip eder, ancak karakter yönelimi referans görsel ile eşleşir (kamera/diğer detaylar istem ile)." + }, + "keep_original_sound": { + "name": "orijinal_sesi_koru" + }, + "mode": { + "name": "mod" + }, + "prompt": { + "name": "istem" + }, + "reference_image": { + "name": "referans_görsel" + }, + "reference_video": { + "name": "referans_video", + "tooltip": "Hareket/ifade için kullanılan hareket referans videosu.\nSüre sınırları karakter_yönelimine bağlıdır:\n - görsel: 3–10sn (maks 10sn)\n - video: 3–30sn (maks 30sn)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProEditVideoNode": { + "description": "Mevcut bir videoyu Kling'in en son modeliyle düzenleyin.", + "display_name": "Kling Omni Video Düzenle (Pro)", + "inputs": { + "keep_original_sound": { + "name": "orijinal_sesi_koru" + }, + "model_name": { + "name": "model_adı" + }, + "prompt": { + "name": "istem", + "tooltip": "Video içeriğini tanımlayan bir metin istemi. Hem pozitif hem de negatif açıklamalar içerebilir." + }, + "reference_images": { + "name": "referans_görseller", + "tooltip": "En fazla 4 ek referans görsel." + }, + "resolution": { + "name": "çözünürlük" + }, + "video": { + "name": "video", + "tooltip": "Düzenlenecek video. Çıktı video uzunluğu aynı olacaktır." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProFirstLastFrameNode": { + "description": "Başlangıç karesi, isteğe bağlı bir bitiş karesi veya referans görselleri ile en yeni Kling modeli kullanılır.", + "display_name": "Kling Omni İlk-Son-Kare'den Videoya (Pro)", + "inputs": { + "duration": { + "name": "duration" + }, + "end_frame": { + "name": "end_frame", + "tooltip": "Video için isteğe bağlı bir bitiş karesi. Bu, 'reference_images' ile aynı anda kullanılamaz." + }, + "first_frame": { + "name": "first_frame" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Video içeriğini tanımlayan bir metin istemi. Hem olumlu hem de olumsuz açıklamalar içerebilir." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "En fazla 6 ek referans görseli." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageNode": { + "description": "Kling'in en yeni modeliyle görseller oluşturun veya düzenleyin.", + "display_name": "Kling Omni Görsel (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Görsel içeriğini tanımlayan bir metin istemi. Hem olumlu hem de olumsuz açıklamalar içerebilir." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "En fazla 10 ek referans görseli." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageToVideoNode": { + "description": "En yeni Kling modeliyle video oluşturmak için en fazla 7 referans görseli kullanın.", + "display_name": "Kling Omni Görselden Videoya (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Video içeriğini tanımlayan bir metin istemi. Hem olumlu hem de olumsuz açıklamalar içerebilir." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "En fazla 7 referans görseli." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProTextToVideoNode": { + "description": "En yeni Kling modeliyle metin istemleri kullanarak videolar oluşturun.", + "display_name": "Kling Omni Metinden Videoya (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Video içeriğini tanımlayan bir metin istemi. Hem olumlu hem de olumsuz açıklamalar içerebilir." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProVideoToVideoNode": { + "description": "Bir video ve en fazla 4 referans görseli kullanarak en yeni Kling modeliyle bir video oluşturun.", + "display_name": "Kling Omni Video'dan Video'ya (Pro)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "keep_original_sound": { + "name": "keep_original_sound" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Video içeriğini tanımlayan bir metin istemi. Hem olumlu hem de olumsuz açıklamalar içerebilir." + }, + "reference_images": { + "name": "reference_images", + "tooltip": "En fazla 4 ek referans görseli." + }, + "reference_video": { + "name": "reference_video", + "tooltip": "Referans olarak kullanılacak video." + }, + "resolution": { + "name": "resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingSingleImageVideoEffectNode": { "description": "Efekt_sahnesine dayalı bir video oluştururken farklı özel efektler elde edin.", "display_name": "Kling Video Efektleri", @@ -4132,6 +5350,35 @@ } } }, + "KlingTextToVideoWithAudio": { + "display_name": "Kling Metinden Videoya (Sesli)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "duration": { + "name": "duration" + }, + "generate_audio": { + "name": "generate_audio" + }, + "mode": { + "name": "mode" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "prompt", + "tooltip": "Olumlu metin istemi." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingVideoExtendNode": { "description": "Kling Video Uzatma Düğümü. Diğer Kling düğümleri tarafından yapılan videoları uzatın. video_id, diğer Kling Düğümleri kullanılarak oluşturulur.", "display_name": "Kling Video Uzatma", @@ -4186,6 +5433,26 @@ } } }, + "LTXAVTextEncoderLoader": { + "description": "[Tarifler]\n\nltxav: gemma 3 12B", + "display_name": "LTXV Sesli Metin Kodlayıcı Yükleyici", + "inputs": { + "ckpt_name": { + "name": "ckpt_name" + }, + "device": { + "name": "device" + }, + "text_encoder": { + "name": "text_encoder" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LTXVAddGuide": { "display_name": "LTXVRehberEkle", "inputs": { @@ -4228,6 +5495,76 @@ } } }, + "LTXVAudioVAEDecode": { + "display_name": "LTXV Sesli VAE Çöz", + "inputs": { + "audio_vae": { + "name": "audio_vae", + "tooltip": "Latent'i çözmek için kullanılan Sesli VAE modeli." + }, + "samples": { + "name": "samples", + "tooltip": "Çözülecek latent." + } + }, + "outputs": { + "0": { + "name": "Ses", + "tooltip": null + } + } + }, + "LTXVAudioVAEEncode": { + "display_name": "LTXV Sesli VAE Kodla", + "inputs": { + "audio": { + "name": "audio", + "tooltip": "Kodlanacak ses." + }, + "audio_vae": { + "name": "audio_vae", + "tooltip": "Kodlama için kullanılacak Sesli VAE modeli." + } + }, + "outputs": { + "0": { + "name": "Ses Latenti", + "tooltip": null + } + } + }, + "LTXVAudioVAELoader": { + "display_name": "LTXV Sesli VAE Yükleyici", + "inputs": { + "ckpt_name": { + "name": "ckpt_name", + "tooltip": "Yüklenecek Sesli VAE kontrol noktası." + } + }, + "outputs": { + "0": { + "name": "Sesli VAE", + "tooltip": null + } + } + }, + "LTXVConcatAVLatent": { + "display_name": "LTXVConcatAVLatent", + "inputs": { + "audio_latent": { + "name": "audio_latent" + }, + "video_latent": { + "name": "video_latent" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, "LTXVConditioning": { "display_name": "LTXVKoşullandırma", "inputs": { @@ -4280,6 +5617,33 @@ } } }, + "LTXVEmptyLatentAudio": { + "display_name": "LTXV Boş Latent Ses", + "inputs": { + "audio_vae": { + "name": "audio_vae", + "tooltip": "Yapılandırma almak için kullanılacak Sesli VAE modeli." + }, + "batch_size": { + "name": "batch_size", + "tooltip": "Toplu işteki latent ses örneklerinin sayısı." + }, + "frame_rate": { + "name": "frame_rate", + "tooltip": "Saniyedeki kare sayısı." + }, + "frames_number": { + "name": "frames_number", + "tooltip": "Kare sayısı." + } + }, + "outputs": { + "0": { + "name": "Latent", + "tooltip": null + } + } + }, "LTXVImgToVideo": { "display_name": "LTXVGörüntüdenVideoya", "inputs": { @@ -4326,6 +5690,47 @@ } } }, + "LTXVImgToVideoInplace": { + "display_name": "LTXVImgToVideoInplace", + "inputs": { + "bypass": { + "name": "atla", + "tooltip": "Koşullandırmayı atla." + }, + "image": { + "name": "görüntü" + }, + "latent": { + "name": "latent" + }, + "strength": { + "name": "güç" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, + "LTXVLatentUpsampler": { + "display_name": "LTXVLatentUpsampler", + "inputs": { + "samples": { + "name": "örnekler" + }, + "upscale_model": { + "name": "büyütme_modeli" + }, + "vae": { + "name": "vae" + } + } + }, "LTXVPreprocess": { "display_name": "LTXVÖnİşleme", "inputs": { @@ -4374,6 +5779,25 @@ } } }, + "LTXVSeparateAVLatent": { + "description": "LTXV Ayrık AV Latent", + "display_name": "LTXVSeparateAVLatent", + "inputs": { + "av_latent": { + "name": "av_latent" + } + }, + "outputs": { + "0": { + "name": "video_latent", + "tooltip": null + }, + "1": { + "name": "audio_latent", + "tooltip": null + } + } + }, "LaplaceScheduler": { "display_name": "LaplaceZamanlayıcı", "inputs": { @@ -4392,6 +5816,11 @@ "steps": { "name": "adımlar" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LatentAdd": { @@ -4529,6 +5958,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LatentConcat": { @@ -4592,6 +6026,25 @@ } } }, + "LatentCutToBatch": { + "display_name": "LatentCutToBatch", + "inputs": { + "dim": { + "name": "dim" + }, + "samples": { + "name": "samples" + }, + "slice_size": { + "name": "slice_size" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LatentFlip": { "display_name": "Gizli Değişkeni Çevir", "inputs": { @@ -4745,6 +6198,19 @@ } } }, + "LatentUpscaleModelLoader": { + "display_name": "Latent Büyütme Modelini Yükle", + "inputs": { + "model_name": { + "name": "model_name" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LazyCache": { "description": "EasyCache'in ev yapımı bir versiyonu - uygulaması daha 'kolay' bir EasyCache versiyonu. Genel olarak EasyCache'den daha kötü çalışır, ancak bazı nadir durumlarda daha iyidir VE ComfyUI'deki her şeyle evrensel uyumluluğa sahiptir.", "display_name": "Tembel Önbellek", @@ -4792,70 +6258,32 @@ }, "upload 3d model": { }, - "width": { - "name": "genişlik" - } - }, - "outputs": { - "0": { - "name": "görüntü" - }, - "1": { - "name": "maske" - }, - "2": { - "name": "ağ_yolu" - }, - "3": { - "name": "normal" - }, - "4": { - "name": "çizgi_sanatı" - }, - "5": { - "name": "kamera_bilgisi" - }, - "6": { - "name": "video_kaydı" - } - } - }, - "Load3DAnimation": { - "display_name": "3D Yükle - Animasyon", - "inputs": { - "height": { - "name": "yükseklik" - }, - "image": { - "name": "görüntü" - }, - "model_file": { - "name": "model_dosyası" + "upload extra resources": { }, "width": { "name": "genişlik" } }, - "outputs": { - "0": { - "name": "görüntü" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "maske" + { + "tooltip": null }, - "2": { - "name": "ağ_yolu" + { + "tooltip": null }, - "3": { - "name": "normal" + { + "tooltip": null }, - "4": { - "name": "kamera_bilgisi" + { + "tooltip": null }, - "5": { - "name": "video_kaydı" + { + "tooltip": null } - } + ] }, "LoadAudio": { "display_name": "Ses Yükle", @@ -4869,6 +6297,11 @@ "upload": { "name": "yüklenecek dosyayı seçin" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LoadImage": { @@ -4882,6 +6315,21 @@ } } }, + "LoadImageDataSetFromFolder": { + "display_name": "Klasörden Görsel Veri Kümesi Yükle", + "inputs": { + "folder": { + "name": "folder", + "tooltip": "Görsellerin yükleneceği klasör." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Yüklenen görsellerin listesi" + } + } + }, "LoadImageMask": { "display_name": "Görüntü Yükle (Maske olarak)", "inputs": { @@ -4900,6 +6348,8 @@ "description": "Çıktı klasöründen bir görüntü yükleyin. Yenile düğmesine tıklandığında, düğüm görüntü listesini güncelleyecek ve otomatik olarak ilk görüntüyü seçecek, bu da kolay yinelemeye olanak tanıyacaktır.", "display_name": "Görüntü Yükle (Çıktılardan)", "inputs": { + "Auto-refresh after generation": { + }, "image": { "name": "görüntü" }, @@ -4910,41 +6360,22 @@ } } }, - "LoadImageSetFromFolderNode": { - "description": "Eğitim için bir dizinden bir grup görüntü yükler.", - "display_name": "Klasörden Görüntü Veri Kümesi Yükle", + "LoadImageTextDataSetFromFolder": { + "display_name": "Klasörden Görsel ve Metin Veri Kümesi Yükle", "inputs": { "folder": { - "name": "klasör", - "tooltip": "Görüntülerin yükleneceği klasör." - }, - "resize_method": { - "name": "yeniden_boyutlandırma_yöntemi" + "name": "folder", + "tooltip": "Görsellerin yükleneceği klasör." } - } - }, - "LoadImageTextSetFromFolderNode": { - "description": "Eğitim için bir dizinden bir grup görüntü ve açıklama yükler.", - "display_name": "Klasörden Görüntü ve Metin Veri Kümesi Yükle", - "inputs": { - "clip": { - "name": "clip", - "tooltip": "Metni kodlamak için kullanılan CLIP modeli." + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Yüklenen görsellerin listesi" }, - "folder": { - "name": "klasör", - "tooltip": "Görüntülerin yükleneceği klasör." - }, - "height": { - "name": "yükseklik", - "tooltip": "Görüntülerin yeniden boyutlandırılacağı yükseklik. -1 orijinal yüksekliği kullan anlamına gelir." - }, - "resize_method": { - "name": "yeniden_boyutlandırma_yöntemi" - }, - "width": { - "name": "genişlik", - "tooltip": "Görüntülerin yeniden boyutlandırılacağı genişlik. -1 orijinal genişliği kullan anlamına gelir." + "1": { + "name": "texts", + "tooltip": "Metin başlıklarının listesi" } } }, @@ -4956,6 +6387,25 @@ } } }, + "LoadTrainingDataset": { + "display_name": "Eğitim Verisetini Yükle", + "inputs": { + "folder_name": { + "name": "folder_name", + "tooltip": "Kaydedilmiş verisetini içeren klasörün adı (çıkış dizini içinde)." + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "Latent sözlüklerinin listesi" + }, + "1": { + "name": "conditioning", + "tooltip": "Koşullandırma listelerinin listesi" + } + } + }, "LoadVideo": { "display_name": "Video Yükle", "inputs": { @@ -5027,7 +6477,6 @@ } }, "LoraModelLoader": { - "description": "LoRA Eğit düğümünden eğitilmiş LoRA ağırlıklarını yükler.", "display_name": "LoRA Modeli Yükle", "inputs": { "lora": { @@ -5043,11 +6492,11 @@ "tooltip": "Difüzyon modelinin ne kadar güçlü bir şekilde değiştirileceği. Bu değer negatif olabilir." } }, - "outputs": { - "0": { - "tooltip": "Değiştirilmiş difüzyon modeli." + "outputs": [ + { + "name": "model" } - } + ] }, "LoraSave": { "display_name": "Lora'yı Çıkar ve Kaydet", @@ -5075,14 +6524,15 @@ } }, "LossGraphNode": { - "description": "Kayıp grafiğini çizer ve çıktı dizinine kaydeder.", "display_name": "Kayıp Grafiği Çiz", "inputs": { "filename_prefix": { - "name": "dosya_adı_ön_eki" + "name": "dosya_adı_ön_eki", + "tooltip": "Kaydedilen kayıp grafik görseli için önek." }, "loss": { - "name": "kayıp" + "name": "kayıp", + "tooltip": "Eğitim düğümünden kayıp haritası." } } }, @@ -5388,6 +6838,50 @@ } } }, + "MakeTrainingDataset": { + "display_name": "Eğitim Veriseti Oluştur", + "inputs": { + "clip": { + "name": "clip", + "tooltip": "Metni koşullandırmaya kodlamak için CLIP modeli." + }, + "images": { + "name": "görüntüler", + "tooltip": "Kodlanacak görüntülerin listesi." + }, + "texts": { + "name": "metinler", + "tooltip": "Metin başlıklarının listesi. Uzunluk n (görüntülerle eşleşen), 1 (hepsi için tekrarlanan) veya boş bırakılabilir (boş dize kullanılır)." + }, + "vae": { + "name": "vae", + "tooltip": "Görüntüleri latentlere kodlamak için VAE modeli." + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "Latent sözlüklerinin listesi" + }, + "1": { + "name": "koşullandırma", + "tooltip": "Koşullandırma listelerinin listesi" + } + } + }, + "ManualSigmas": { + "display_name": "Manuel Sigmalar", + "inputs": { + "sigmas": { + "name": "sigmalar" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "MaskComposite": { "display_name": "MaskeBirleştirme", "inputs": { @@ -5406,6 +6900,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "MaskPreview": { @@ -5423,6 +6922,315 @@ "mask": { "name": "maske" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MergeImageLists": { + "display_name": "Görüntü Listelerini Birleştir", + "inputs": { + "images": { + "name": "görüntüler", + "tooltip": "İşlenecek görüntülerin listesi." + } + }, + "outputs": { + "0": { + "name": "görüntüler", + "tooltip": "İşlenmiş görüntüler" + } + } + }, + "MergeTextLists": { + "display_name": "Metin Listelerini Birleştir", + "inputs": { + "texts": { + "name": "metinler", + "tooltip": "İşlenecek metinlerin listesi." + } + }, + "outputs": { + "0": { + "name": "metinler", + "tooltip": "İşlenmiş metinler" + } + } + }, + "MeshyAnimateModelNode": { + "description": "Daha önce rig işlemi uygulanmış bir karaktere belirli bir animasyon hareketi uygula.", + "display_name": "Meshy: Modeli Canlandır", + "inputs": { + "action_id": { + "name": "action_id", + "tooltip": "Mevcut değerlerin listesi için https://docs.meshy.ai/en/api/animation-library adresini ziyaret edin." + }, + "rig_task_id": { + "name": "rig_task_id" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + } + } + }, + "MeshyImageToModelNode": { + "display_name": "Meshy: Görüntüden Modele", + "inputs": { + "control_after_generate": { + "name": "oluşturduktan sonra kontrol et" + }, + "image": { + "name": "image" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "Oluşturulan model için poz modunu belirtin." + }, + "seed": { + "name": "seed", + "tooltip": "Seed, düğümün tekrar çalıştırılıp çalıştırılmayacağını kontrol eder; seed ne olursa olsun sonuçlar deterministik değildir." + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "Yanlış olarak ayarlanırsa, işlenmemiş üçgen ağ döndürülür." + }, + "should_remesh_target_polycount": { + "name": "hedef_poligon_sayısı" + }, + "should_remesh_topology": { + "name": "topoloji" + }, + "should_texture": { + "name": "should_texture", + "tooltip": "Doku oluşturulup oluşturulmayacağını belirler. Yanlış olarak ayarlanırsa doku aşaması atlanır ve dokusuz bir ağ döndürülür." + }, + "should_texture_enable_pbr": { + "name": "pbr_aktif_et" + }, + "should_texture_texture_prompt": { + "name": "doku_isteği" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyMultiImageToModelNode": { + "display_name": "Meshy: Çoklu Görüntüden Modele", + "inputs": { + "control_after_generate": { + "name": "oluşturduktan sonra kontrol et" + }, + "images": { + "name": "images" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "Oluşturulan model için poz modunu belirtin." + }, + "seed": { + "name": "seed", + "tooltip": "Seed, düğümün tekrar çalıştırılıp çalıştırılmayacağını kontrol eder; seed ne olursa olsun sonuçlar deterministik değildir." + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "Yanlış olarak ayarlanırsa, işlenmemiş üçgen ağ döndürülür." + }, + "should_remesh_target_polycount": { + "name": "hedef_poligon_sayısı" + }, + "should_remesh_topology": { + "name": "topoloji" + }, + "should_texture": { + "name": "should_texture", + "tooltip": "Doku oluşturulup oluşturulmayacağını belirler. Yanlış olarak ayarlanırsa doku aşaması atlanır ve dokusuz bir ağ döndürülür." + }, + "should_texture_enable_pbr": { + "name": "pbr_aktif_et" + }, + "should_texture_texture_prompt": { + "name": "doku_isteği" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRefineNode": { + "description": "Önceden oluşturulmuş bir taslak modeli iyileştirir.", + "display_name": "Meshy: Taslak Modeli İyileştir", + "inputs": { + "enable_pbr": { + "name": "enable_pbr", + "tooltip": "Temel renge ek olarak PBR Haritaları (metalik, pürüzlülük, normal) oluşturur. Not: Heykel tarzı kullanıldığında bu seçenek kapalı olmalıdır, çünkü Heykel tarzı kendi PBR haritalarını üretir." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "model": { + "name": "model" + }, + "texture_image": { + "name": "texture_image", + "tooltip": "'texture_image' veya 'texture_prompt' seçeneklerinden yalnızca biri aynı anda kullanılabilir." + }, + "texture_prompt": { + "name": "texture_prompt", + "tooltip": "Kaplama sürecini yönlendirmek için bir metin istemi girin. Maksimum 600 karakter. 'texture_image' ile aynı anda kullanılamaz." + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRigModelNode": { + "description": "Standart formatlarda riglenmiş bir karakter sağlar. Otomatik rigleme, şu anda dokusuz meshler, insan olmayan varlıklar veya uzuv ve vücut yapısı belirsiz olan insan benzeri varlıklar için uygun değildir.", + "display_name": "Meshy: Modeli Rigle", + "inputs": { + "height_meters": { + "name": "height_meters", + "tooltip": "Karakter modelinin yaklaşık yüksekliği (metre cinsinden). Ölçekleme ve rigleme doğruluğu için yardımcı olur." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "texture_image": { + "name": "texture_image", + "tooltip": "Modelin UV-unwrap edilmiş temel renk doku görseli." + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "rig_task_id", + "tooltip": null + } + } + }, + "MeshyTextToModelNode": { + "display_name": "Meshy: Metinden Modele", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "Oluşturulan model için poz modu belirtin." + }, + "prompt": { + "name": "prompt" + }, + "seed": { + "name": "seed", + "tooltip": "Seed, nodun tekrar çalıştırılıp çalıştırılmayacağını kontrol eder; seed ne olursa olsun sonuçlar deterministik değildir." + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "Kapalı olarak ayarlanırsa, işlenmemiş üçgen mesh döndürülür." + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "style": { + "name": "style" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyTextureNode": { + "display_name": "Meshy: Doku Modeli", + "inputs": { + "enable_original_uv": { + "name": "orijinal UV'yi etkinleştir", + "tooltip": "Modelin yeni UV'leri oluşturmak yerine orijinal UV'sini kullan. Etkinleştirildiğinde, Meshy yüklenen modeldeki mevcut dokuları korur. Modelin orijinal UV'si yoksa, çıktı kalitesi o kadar iyi olmayabilir." + }, + "image_style": { + "name": "görsel stil", + "tooltip": "Dokulandırma sürecine rehberlik edecek 2D bir görsel. 'text_style_prompt' ile aynı anda kullanılamaz." + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "model": { + "name": "model" + }, + "pbr": { + "name": "pbr" + }, + "text_style_prompt": { + "name": "metin stil istemi", + "tooltip": "Nesnenin istediğiniz doku stilini metinle tanımlayın. En fazla 600 karakter. 'image_style' ile aynı anda kullanılamaz." + } + }, + "outputs": { + "0": { + "name": "model_dosyası", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } } }, "MinimaxHailuoVideoNode": { @@ -7866,6 +9674,52 @@ } } }, + "NormalizeImages": { + "display_name": "Görüntüleri Normalleştir", + "inputs": { + "images": { + "name": "görüntüler", + "tooltip": "İşlenecek görüntü." + }, + "mean": { + "name": "ortalama", + "tooltip": "Normalleştirme için ortalama değer." + }, + "std": { + "name": "std", + "tooltip": "Normalleştirme için standart sapma." + } + }, + "outputs": { + "0": { + "name": "görüntüler", + "tooltip": "İşlenmiş görüntüler" + } + } + }, + "NormalizeVideoLatentStart": { + "description": "Bir video latentinin ilk karelerini, sonraki referans karelerin ortalaması ve standart sapmasına uyacak şekilde normalleştirir. Başlangıç kareleri ile videonun geri kalanı arasındaki farkların azaltılmasına yardımcı olur.", + "display_name": "NormalizeVideoLatentStart", + "inputs": { + "latent": { + "name": "latent" + }, + "reference_frame_count": { + "name": "reference_frame_count", + "tooltip": "Referans olarak kullanılacak, başlangıç karelerinden sonraki latent kare sayısı" + }, + "start_frame_count": { + "name": "start_frame_count", + "tooltip": "Başlangıçtan itibaren normalleştirilecek latent kare sayısı" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, "OpenAIChatConfig": { "description": "OpenAI Sohbet Düğümleri için gelişmiş yapılandırma seçeneklerini belirlemeye olanak tanır.", "display_name": "OpenAI ChatGPT Gelişmiş Seçenekler", @@ -8015,6 +9869,9 @@ "name": "maske", "tooltip": "İç boyama için isteğe bağlı maske (beyaz alanlar değiştirilecektir)" }, + "model": { + "name": "model" + }, "n": { "name": "n", "tooltip": "Kaç tane görüntü oluşturulacağı" @@ -8373,265 +10230,6 @@ } } }, - "PikaImageToVideoNode2_2": { - "description": "Bir video oluşturmak için Pika API v2.2'ye bir görüntü ve istem gönderir.", - "display_name": "Pika Görüntüden Videoya", - "inputs": { - "control_after_generate": { - "name": "oluşturduktan sonra kontrol et" - }, - "duration": { - "name": "süre" - }, - "image": { - "name": "görüntü", - "tooltip": "Videoya dönüştürülecek görüntü" - }, - "negative_prompt": { - "name": "negatif_istem" - }, - "prompt_text": { - "name": "istem_metni" - }, - "resolution": { - "name": "çözünürlük" - }, - "seed": { - "name": "tohum" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaScenesV2_2": { - "description": "İçlerindeki nesnelerle bir video oluşturmak için görüntülerinizi birleştirin. Malzeme olarak birden fazla görüntü yükleyin ve hepsini içeren yüksek kaliteli bir video oluşturun.", - "display_name": "Pika Sahneleri (Video Görüntü Kompozisyonu)", - "inputs": { - "aspect_ratio": { - "name": "en_boy_oranı", - "tooltip": "En boy oranı (genişlik / yükseklik)" - }, - "control_after_generate": { - "name": "oluşturduktan sonra kontrol et" - }, - "duration": { - "name": "süre" - }, - "image_ingredient_1": { - "name": "görüntü_malzemesi_1", - "tooltip": "Video oluşturmak için malzeme olarak kullanılacak görüntü." - }, - "image_ingredient_2": { - "name": "görüntü_malzemesi_2", - "tooltip": "Video oluşturmak için malzeme olarak kullanılacak görüntü." - }, - "image_ingredient_3": { - "name": "görüntü_malzemesi_3", - "tooltip": "Video oluşturmak için malzeme olarak kullanılacak görüntü." - }, - "image_ingredient_4": { - "name": "görüntü_malzemesi_4", - "tooltip": "Video oluşturmak için malzeme olarak kullanılacak görüntü." - }, - "image_ingredient_5": { - "name": "görüntü_malzemesi_5", - "tooltip": "Video oluşturmak için malzeme olarak kullanılacak görüntü." - }, - "ingredients_mode": { - "name": "malzemeler_modu" - }, - "negative_prompt": { - "name": "negatif_istem" - }, - "prompt_text": { - "name": "istem_metni" - }, - "resolution": { - "name": "çözünürlük" - }, - "seed": { - "name": "tohum" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaStartEndFrameNode2_2": { - "description": "İlk ve son karenizi birleştirerek bir video oluşturun. Başlangıç ve bitiş noktalarını tanımlamak için iki görüntü yükleyin ve yapay zekanın aralarında pürüzsüz bir geçiş oluşturmasına izin verin.", - "display_name": "Pika Başlangıç ve Bitiş Karesinden Videoya", - "inputs": { - "control_after_generate": { - "name": "oluşturduktan sonra kontrol et" - }, - "duration": { - "name": "süre" - }, - "image_end": { - "name": "bitiş_görüntüsü", - "tooltip": "Birleştirilecek son görüntü." - }, - "image_start": { - "name": "başlangıç_görüntüsü", - "tooltip": "Birleştirilecek ilk görüntü." - }, - "negative_prompt": { - "name": "negatif_istem" - }, - "prompt_text": { - "name": "istem_metni" - }, - "resolution": { - "name": "çözünürlük" - }, - "seed": { - "name": "tohum" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaTextToVideoNode2_2": { - "description": "Bir video oluşturmak için Pika API v2.2'ye bir metin istemi gönderir.", - "display_name": "Pika Metinden Videoya", - "inputs": { - "aspect_ratio": { - "name": "en_boy_oranı", - "tooltip": "En boy oranı (genişlik / yükseklik)" - }, - "control_after_generate": { - "name": "oluşturduktan sonra kontrol et" - }, - "duration": { - "name": "süre" - }, - "negative_prompt": { - "name": "negatif_istem" - }, - "prompt_text": { - "name": "istem_metni" - }, - "resolution": { - "name": "çözünürlük" - }, - "seed": { - "name": "tohum" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikadditions": { - "description": "Videonuzun içine herhangi bir nesne veya görüntü ekleyin. Bir video yükleyin ve sorunsuz bir şekilde entegre edilmiş bir sonuç oluşturmak için ne eklemek istediğinizi belirtin.", - "display_name": "Pikadditions (Video Nesne Ekleme)", - "inputs": { - "control_after_generate": { - "name": "oluşturduktan sonra kontrol et" - }, - "image": { - "name": "görüntü", - "tooltip": "Videoya eklenecek görüntü." - }, - "negative_prompt": { - "name": "negatif_istem" - }, - "prompt_text": { - "name": "istem_metni" - }, - "seed": { - "name": "tohum" - }, - "video": { - "name": "video", - "tooltip": "Görüntü eklenecek video." - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikaffects": { - "description": "Belirli bir Pikaffect ile bir video oluşturun. Desteklenen Pikaffect'ler: Cake-ify, Crumble, Crush, Decapitate, Deflate, Dissolve, Explode, Eye-pop, Inflate, Levitate, Melt, Peel, Poke, Squish, Ta-da, Tear", - "display_name": "Pikaffects (Video Efektleri)", - "inputs": { - "control_after_generate": { - "name": "oluşturduktan sonra kontrol et" - }, - "image": { - "name": "görüntü", - "tooltip": "Pikaffect'in uygulanacağı referans görüntü." - }, - "negative_prompt": { - "name": "negatif_istem" - }, - "pikaffect": { - "name": "pikaffect" - }, - "prompt_text": { - "name": "istem_metni" - }, - "seed": { - "name": "tohum" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikaswaps": { - "description": "Videonuzdaki herhangi bir nesneyi veya bölgeyi yeni bir görüntü veya nesneyle değiştirin. Değiştirilecek alanları bir maske veya koordinatlarla tanımlayın.", - "display_name": "Pika Değişimleri (Video Nesne Değiştirme)", - "inputs": { - "control_after_generate": { - "name": "oluşturduktan sonra kontrol et" - }, - "image": { - "name": "görüntü", - "tooltip": "Videodaki maskelenmiş nesneyi değiştirmek için kullanılan görüntü." - }, - "mask": { - "name": "maske", - "tooltip": "Videoda değiştirilecek alanları tanımlamak için maskeyi kullanın" - }, - "negative_prompt": { - "name": "negatif_istem" - }, - "prompt_text": { - "name": "istem_metni" - }, - "region_to_modify": { - "name": "değiştirilecek_bölge", - "tooltip": "Değiştirilecek nesnenin/bölgenin düz metin açıklaması." - }, - "seed": { - "name": "tohum" - }, - "video": { - "name": "video", - "tooltip": "İçinde bir nesne değiştirilecek video." - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, "PixverseImageToVideoNode": { "description": "İstem ve çıktı_boyutuna göre videoları eşzamanlı olarak oluşturur.", "display_name": "PixVerse Görüntüden Videoya", @@ -8786,6 +10384,11 @@ "steps": { "name": "adımlar" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "PorterDuffImageComposite": { @@ -8819,6 +10422,9 @@ "Preview3D": { "display_name": "3D Önizleme", "inputs": { + "bg_image": { + "name": "bg_image" + }, "camera_info": { "name": "kamera_bilgisi" }, @@ -8830,22 +10436,13 @@ } } }, - "Preview3DAnimation": { - "display_name": "3D Önizleme - Animasyon", - "inputs": { - "camera_info": { - "name": "kamera_bilgisi" - }, - "model_file": { - "name": "model_dosyası" - } - } - }, "PreviewAny": { "display_name": "Herhangi Bir Şeyi Önizle", "inputs": { "preview": { }, + "previewMode": { + }, "source": { "name": "kaynak" } @@ -8985,6 +10582,36 @@ } } }, + "RandomCropImages": { + "display_name": "Rastgele Kırpılmış Görseller", + "inputs": { + "control_after_generate": { + "name": "oluşturduktan sonra kontrol et" + }, + "height": { + "name": "height", + "tooltip": "Kırpma yüksekliği." + }, + "images": { + "name": "images", + "tooltip": "İşlenecek görsel." + }, + "seed": { + "name": "seed", + "tooltip": "Rastgelelik tohumu." + }, + "width": { + "name": "width", + "tooltip": "Kırpma genişliği." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "İşlenmiş görseller" + } + } + }, "RandomNoise": { "display_name": "RastgeleGürültü", "inputs": { @@ -8994,6 +10621,11 @@ "noise_seed": { "name": "gürültü_tohumu" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RebatchImages": { @@ -9034,6 +10666,11 @@ "audio": { "name": "ses" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RecraftColorRGB": { @@ -9538,6 +11175,11 @@ "image": { "name": "görüntü" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RepeatLatentBatch": { @@ -9551,6 +11193,51 @@ } } }, + "ReplaceText": { + "display_name": "Metni Değiştir", + "inputs": { + "find": { + "name": "find", + "tooltip": "Bulunacak metin." + }, + "replace": { + "name": "replace", + "tooltip": "Yerine konacak metin." + }, + "texts": { + "name": "texts", + "tooltip": "İşlenecek metin." + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "İşlenmiş metinler" + } + } + }, + "ReplaceVideoLatentFrames": { + "display_name": "ReplaceVideoLatentFrames", + "inputs": { + "destination": { + "name": "destination", + "tooltip": "Karelerin değiştirileceği hedef latent." + }, + "index": { + "name": "index", + "tooltip": "Kaynak latent karelerinin hedef latente yerleştirileceği başlangıç latent kare indeksi. Negatif değerler sondan sayılır." + }, + "source": { + "name": "source", + "tooltip": "Hedef latente eklenecek kareleri sağlayan kaynak latent. Sağlanmazsa, hedef latent değişmeden döndürülür." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "RescaleCFG": { "display_name": "CFG'yiYenidenÖlçekle", "inputs": { @@ -9580,6 +11267,104 @@ "target_width": { "name": "hedef_genişlik" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ResizeImageMaskNode": { + "description": "Bir görüntüyü veya maskeyi çeşitli ölçeklendirme yöntemleriyle yeniden boyutlandırın.", + "display_name": "Görsel/Maske Yeniden Boyutlandır", + "inputs": { + "input": { + "name": "input" + }, + "resize_type": { + "name": "resize_type", + "tooltip": "Yeniden boyutlandırma yöntemini seçin: tam boyutlar, ölçek faktörü, başka bir görüntüyle eşleştirme vb." + }, + "resize_type_crop": { + "name": "kırp" + }, + "resize_type_height": { + "name": "yükseklik" + }, + "resize_type_width": { + "name": "genişlik" + }, + "scale_method": { + "name": "scale_method", + "tooltip": "Enterpolasyon algoritması. 'area' küçültme için en iyisidir, 'lanczos' büyütme için uygundur, 'nearest-exact' piksel sanatı için idealdir." + } + }, + "outputs": { + "0": { + "name": "resized", + "tooltip": null + } + } + }, + "ResizeImagesByLongerEdge": { + "display_name": "Görüntüleri Uzun Kenara Göre Yeniden Boyutlandır", + "inputs": { + "images": { + "name": "görüntüler", + "tooltip": "İşlenecek görüntü." + }, + "longer_edge": { + "name": "uzun_kenar", + "tooltip": "Uzun kenar için hedef uzunluk." + } + }, + "outputs": { + "0": { + "name": "görüntüler", + "tooltip": "İşlenmiş görüntüler" + } + } + }, + "ResizeImagesByShorterEdge": { + "display_name": "Görüntüleri Kısa Kenara Göre Yeniden Boyutlandır", + "inputs": { + "images": { + "name": "görüntüler", + "tooltip": "İşlenecek görüntü." + }, + "shorter_edge": { + "name": "kısa_kenar", + "tooltip": "Kısa kenar için hedef uzunluk." + } + }, + "outputs": { + "0": { + "name": "görüntüler", + "tooltip": "İşlenmiş görüntüler" + } + } + }, + "ResolutionBucket": { + "display_name": "Çözünürlük Kovası", + "inputs": { + "conditioning": { + "name": "koşullandırma", + "tooltip": "Koşullandırma listelerinin listesi (latentler ile aynı uzunlukta olmalı)." + }, + "latents": { + "name": "latentler", + "tooltip": "Çözünürlüğe göre kovalanacak latent sözlüklerinin listesi." + } + }, + "outputs": { + "0": { + "name": "latentler", + "tooltip": "Her çözünürlük kovası için birer toplu latent sözlükleri listesi." + }, + "1": { + "name": "koşullandırma", + "tooltip": "Her çözünürlük kovası için birer koşul listesi." + } } }, "Rodin3D_Detail": { @@ -9833,6 +11618,11 @@ "steps": { "name": "adımlar" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SD_4XUpscale_Conditioning": { @@ -9986,14 +11776,14 @@ "name": "sigmalar" } }, - "outputs": { - "0": { - "name": "çıktı" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "gürültüsü_alınmış_çıktı" + { + "tooltip": null } - } + ] }, "SamplerCustomAdvanced": { "display_name": "GelişmişÖzelÖrnekleyici", @@ -10014,14 +11804,14 @@ "name": "sigmalar" } }, - "outputs": { - "0": { - "name": "çıktı" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "gürültüsü_alınmış_çıktı" + { + "tooltip": null } - } + ] }, "SamplerDPMAdaptative": { "display_name": "UyarlanabilirDPMÖrnekleyici", @@ -10056,6 +11846,11 @@ "s_noise": { "name": "s_gürültü" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_2M_SDE": { @@ -10073,6 +11868,11 @@ "solver_type": { "name": "çözücü_türü" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_2S_Ancestral": { @@ -10084,6 +11884,11 @@ "s_noise": { "name": "s_gürültü" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_3M_SDE": { @@ -10098,6 +11903,11 @@ "s_noise": { "name": "s_gürültü" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_SDE": { @@ -10115,6 +11925,11 @@ "s_noise": { "name": "s_gürültü" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerER_SDE": { @@ -10133,6 +11948,11 @@ "solver_type": { "name": "çözücü_tipi" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerAncestral": { @@ -10144,6 +11964,11 @@ "s_noise": { "name": "s_gürültü" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerAncestralCFGPP": { @@ -10155,6 +11980,11 @@ "s_noise": { "name": "s_gürültü" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerCFGpp": { @@ -10195,6 +12025,11 @@ "order": { "name": "sıra" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerSASolver": { @@ -10227,6 +12062,37 @@ "use_pece": { "name": "pece_kullan" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerSEEDS2": { + "description": "Bu örnekleyici düğümü birden fazla örnekleyiciyi temsil edebilir:\n\nseeds_2\n- varsayılan ayar\n\nexp_heun_2_x0\n- solver_type=phi_2, r=1.0, eta=0.0\n\nexp_heun_2_x0_sde\n- solver_type=phi_2, r=1.0, eta=1.0, s_noise=1.0", + "display_name": "SamplerSEEDS2", + "inputs": { + "eta": { + "name": "eta", + "tooltip": "Stokastik güç" + }, + "r": { + "name": "r", + "tooltip": "Ara aşama için göreli adım boyutu (c2 düğümü)" + }, + "s_noise": { + "name": "s_noise", + "tooltip": "SDE gürültü çarpanı" + }, + "solver_type": { + "name": "solver_type" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplingPercentToSigma": { @@ -10243,11 +12109,11 @@ "name": "örnekleme_yüzdesi" } }, - "outputs": { - "0": { - "name": "sigma_değeri" + "outputs": [ + { + "tooltip": null } - } + ] }, "SaveAnimatedPNG": { "display_name": "Animasyonlu PNG Kaydet", @@ -10365,6 +12231,44 @@ } } }, + "SaveImageDataSetToFolder": { + "display_name": "Görüntü Veri Setini Klasöre Kaydet", + "inputs": { + "filename_prefix": { + "name": "filename_prefix", + "tooltip": "Kaydedilen görüntü dosya adları için ön ek." + }, + "folder_name": { + "name": "folder_name", + "tooltip": "Görüntülerin kaydedileceği klasörün adı (çıktı dizini içinde)." + }, + "images": { + "name": "images", + "tooltip": "Kaydedilecek görüntülerin listesi." + } + } + }, + "SaveImageTextDataSetToFolder": { + "display_name": "Görüntü ve Metin Veri Setini Klasöre Kaydet", + "inputs": { + "filename_prefix": { + "name": "filename_prefix", + "tooltip": "Kaydedilen görüntü dosya adları için ön ek." + }, + "folder_name": { + "name": "folder_name", + "tooltip": "Görüntülerin kaydedileceği klasörün adı (çıktı dizini içinde)." + }, + "images": { + "name": "images", + "tooltip": "Kaydedilecek görüntülerin listesi." + }, + "texts": { + "name": "texts", + "tooltip": "Kaydedilecek metin başlıklarının listesi." + } + } + }, "SaveImageWebsocket": { "display_name": "GörüntüyüWebsocketKaydet", "inputs": { @@ -10384,20 +12288,20 @@ } } }, - "SaveLoRANode": { + "SaveLoRA": { "display_name": "LoRA Ağırlıklarını Kaydet", "inputs": { "lora": { "name": "lora", - "tooltip": "Kaydedilecek LoRA modeli. LoRA katmanlarına sahip modeli kullanmayın." + "tooltip": "Kaydedilecek LoRA modeli. LoRA katmanlarıyla modeli kullanmayın." }, "prefix": { - "name": "ön_ek", - "tooltip": "Kaydedilecek LoRA dosyası için kullanılacak ön ek." + "name": "prefix", + "tooltip": "Kaydedilen LoRA dosyası için kullanılacak ön ek." }, "steps": { - "name": "adımlar", - "tooltip": "İsteğe bağlı: LoRA'nın eğitildiği adım sayısı, kaydedilen dosyanın adlandırılmasında kullanılır." + "name": "steps", + "tooltip": "İsteğe bağlı: LoRA'nın eğitildiği adım sayısı, kaydedilen dosyanın adında kullanılır." } } }, @@ -10414,6 +12318,27 @@ } } }, + "SaveTrainingDataset": { + "display_name": "Eğitim Veri Setini Kaydet", + "inputs": { + "conditioning": { + "name": "conditioning", + "tooltip": "MakeTrainingDataset'ten alınan koşullandırma listelerinin listesi." + }, + "folder_name": { + "name": "folder_name", + "tooltip": "Veri setinin kaydedileceği klasörün adı (çıktı dizini içinde)." + }, + "latents": { + "name": "latents", + "tooltip": "MakeTrainingDataset'ten alınan latent sözlüklerinin listesi." + }, + "shard_size": { + "name": "shard_size", + "tooltip": "Her bir parça dosyası başına örnek sayısı." + } + } + }, "SaveVideo": { "description": "Giriş görüntülerini ComfyUI çıktı dizininize kaydeder.", "display_name": "Videoyu Kaydet", @@ -10534,6 +12459,11 @@ "sigmas": { "name": "sigmalar" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SetHookKeyframes": { @@ -10574,6 +12504,58 @@ } } }, + "ShuffleDataset": { + "display_name": "Görüntü Veri Setini Karıştır", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images", + "tooltip": "İşlenecek görüntülerin listesi." + }, + "seed": { + "name": "seed", + "tooltip": "Rastgele tohum." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "İşlenmiş görüntüler" + } + } + }, + "ShuffleImageTextDataset": { + "display_name": "Görüntü-Metin Veri Setini Karıştır", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images", + "tooltip": "Karıştırılacak görüntülerin listesi." + }, + "seed": { + "name": "seed", + "tooltip": "Rastgele tohum." + }, + "texts": { + "name": "texts", + "tooltip": "Karıştırılacak metinlerin listesi." + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "Karıştırılmış görüntüler" + }, + "1": { + "name": "texts", + "tooltip": "Karıştırılmış metinler" + } + } + }, "SkipLayerGuidanceDiT": { "description": "Her DiT modelinde kullanılabilecek SkipLayerGuidance düğümünün genel bir sürümü.", "display_name": "KatmanAtlamaRehberliğiDiT", @@ -10670,6 +12652,11 @@ "width": { "name": "genişlik" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SplitAudioChannels": { @@ -10680,14 +12667,14 @@ "name": "ses" } }, - "outputs": { - "0": { - "name": "sol" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "sağ" + { + "tooltip": null } - } + ] }, "SplitImageWithAlpha": { "display_name": "Görüntüyü Alfa ile Böl", @@ -10715,14 +12702,14 @@ "name": "adım" } }, - "outputs": { - "0": { - "name": "yüksek_sigmalar" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "düşük_sigmalar" + { + "tooltip": null } - } + ] }, "SplitSigmasDenoise": { "display_name": "SigmalarıGürültüAzaltmaBöl", @@ -10734,14 +12721,14 @@ "name": "sigmalar" } }, - "outputs": { - "0": { - "name": "yüksek_sigmalar" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "düşük_sigmalar" + { + "tooltip": null } - } + ] }, "StabilityAudioInpaint": { "description": "Mevcut ses örneğinin bir bölümünü metin talimatları kullanarak dönüştürür.", @@ -11343,6 +13330,21 @@ } } }, + "StripWhitespace": { + "display_name": "Boşlukları Kaldır", + "inputs": { + "texts": { + "name": "metinler", + "tooltip": "İşlenecek metin." + } + }, + "outputs": { + "0": { + "name": "metinler", + "tooltip": "İşlenmiş metinler" + } + } + }, "StyleModelApply": { "display_name": "Stil Modeli Uygula", "inputs": { @@ -11428,6 +13430,84 @@ } } }, + "TencentImageToModelNode": { + "display_name": "Hunyuan3D: Görsel(ler)den Modele (Pro)", + "inputs": { + "control_after_generate": { + "name": "oluşturma sonrası kontrol" + }, + "face_count": { + "name": "yüz sayısı" + }, + "generate_type": { + "name": "oluşturma türü" + }, + "generate_type_pbr": { + "name": "pbr" + }, + "image": { + "name": "görsel" + }, + "image_back": { + "name": "arka görsel" + }, + "image_left": { + "name": "sol görsel" + }, + "image_right": { + "name": "sağ görsel" + }, + "model": { + "name": "model", + "tooltip": "`3.1` modeli için LowPoly seçeneği kullanılamaz." + }, + "seed": { + "name": "tohum", + "tooltip": "Tohum, düğümün tekrar çalıştırılıp çalıştırılmayacağını kontrol eder; sonuçlar tohumdan bağımsız olarak deterministik değildir." + } + }, + "outputs": { + "0": { + "name": "model_dosyası", + "tooltip": null + } + } + }, + "TencentTextToModelNode": { + "display_name": "Hunyuan3D: Metinden Modele (Pro)", + "inputs": { + "control_after_generate": { + "name": "oluşturma sonrası kontrol" + }, + "face_count": { + "name": "yüz sayısı" + }, + "generate_type": { + "name": "oluşturma türü" + }, + "generate_type_pbr": { + "name": "pbr" + }, + "model": { + "name": "model", + "tooltip": "`3.1` modeli için LowPoly seçeneği kullanılamaz." + }, + "prompt": { + "name": "istem", + "tooltip": "En fazla 1024 karakter desteklenir." + }, + "seed": { + "name": "tohum", + "tooltip": "Tohum, düğümün tekrar çalıştırılıp çalıştırılmayacağını kontrol eder; sonuçlar tohumdan bağımsız olarak deterministik değildir." + } + }, + "outputs": { + "0": { + "name": "model_dosyası", + "tooltip": null + } + } + }, "TextEncodeAceStepAudio": { "display_name": "TextEncodeAceStepAudio", "inputs": { @@ -11523,6 +13603,70 @@ } } }, + "TextEncodeZImageOmni": { + "display_name": "TextEncodeZImageOmni", + "inputs": { + "auto_resize_images": { + "name": "görüntüleri_otomatik_yeniden_boyutlandır" + }, + "clip": { + "name": "clip" + }, + "image1": { + "name": "görüntü1" + }, + "image2": { + "name": "görüntü2" + }, + "image3": { + "name": "görüntü3" + }, + "image_encoder": { + "name": "görüntü_kodlayıcı" + }, + "prompt": { + "name": "istem" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TextToLowercase": { + "display_name": "Metni Küçük Harfe Çevir", + "inputs": { + "texts": { + "name": "metinler", + "tooltip": "İşlenecek metin." + } + }, + "outputs": { + "0": { + "name": "metinler", + "tooltip": "İşlenmiş metinler" + } + } + }, + "TextToUppercase": { + "display_name": "Metni Büyük Harfe Çevir", + "inputs": { + "texts": { + "name": "metinler", + "tooltip": "İşlenecek metin." + } + }, + "outputs": { + "0": { + "name": "metinler", + "tooltip": "İşlenmiş metinler" + } + } + }, "ThresholdMask": { "display_name": "EşikMaskesi", "inputs": { @@ -11532,6 +13676,11 @@ "value": { "name": "değer" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "TomePatchModel": { @@ -11550,6 +13699,118 @@ } } }, + "TopazImageEnhance": { + "description": "Endüstri standardı büyütme ve görüntü iyileştirme.", + "display_name": "Topaz Görüntü İyileştirme", + "inputs": { + "color_preservation": { + "name": "renk_koruma", + "tooltip": "Orijinal renkleri koru." + }, + "creativity": { + "name": "yaratıcılık" + }, + "crop_to_fill": { + "name": "doldurmak_için_kırp", + "tooltip": "Varsayılan olarak, çıktı en-boy oranı farklıysa görüntü letterbox yapılır. Çıktı boyutlarını doldurmak için görüntüyü kırpmak için etkinleştirin." + }, + "face_enhancement": { + "name": "yüz_iyileştirme", + "tooltip": "İşleme sırasında (varsa) yüzleri iyileştir." + }, + "face_enhancement_creativity": { + "name": "yüz_iyileştirme_yaratıcılığı", + "tooltip": "Yüz iyileştirme için yaratıcılık seviyesini ayarla." + }, + "face_enhancement_strength": { + "name": "yüz_iyileştirme_gücü", + "tooltip": "İyileştirilen yüzlerin arka plana göre ne kadar keskin olacağını kontrol eder." + }, + "face_preservation": { + "name": "yüz_koruma", + "tooltip": "Konu yüzlerinin kimliğini koru." + }, + "image": { + "name": "görüntü" + }, + "model": { + "name": "model" + }, + "output_height": { + "name": "çıktı_yüksekliği", + "tooltip": "Sıfır değeri, orijinal ile aynı yükseklikte veya çıktı genişliğinde çıktı alınacağı anlamına gelir." + }, + "output_width": { + "name": "çıktı_genişliği", + "tooltip": "Sıfır değeri otomatik olarak hesaplanacağı anlamına gelir (genellikle orijinal boyut veya belirtilmişse çıktı_yüksekliği olur)." + }, + "prompt": { + "name": "istem", + "tooltip": "Yaratıcı büyütme rehberliği için isteğe bağlı metin istemi." + }, + "subject_detection": { + "name": "konu_tespiti" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TopazVideoEnhance": { + "description": "Güçlü ölçek büyütme ve iyileştirme teknolojisiyle videolara yeniden hayat verin.", + "display_name": "Topaz Video Enhance", + "inputs": { + "dynamic_compression_level": { + "name": "dynamic_compression_level", + "tooltip": "CQP seviyesi." + }, + "interpolation_duplicate": { + "name": "interpolation_duplicate", + "tooltip": "Girdideki yinelenen kareleri analiz edip kaldırır." + }, + "interpolation_duplicate_threshold": { + "name": "interpolation_duplicate_threshold", + "tooltip": "Yinelenen kareler için algılama hassasiyeti." + }, + "interpolation_enabled": { + "name": "interpolation_enabled" + }, + "interpolation_frame_rate": { + "name": "interpolation_frame_rate", + "tooltip": "Çıktı kare hızı." + }, + "interpolation_model": { + "name": "interpolation_model" + }, + "interpolation_slowmo": { + "name": "interpolation_slowmo", + "tooltip": "Girdi videoya uygulanan ağır çekim faktörü. Örneğin, 2 çıktıyı iki kat yavaşlatır ve süresini iki katına çıkarır." + }, + "upscaler_creativity": { + "name": "upscaler_creativity", + "tooltip": "Yaratıcılık seviyesi (yalnızca Starlight (Astra) Creative için geçerlidir)." + }, + "upscaler_enabled": { + "name": "upscaler_enabled" + }, + "upscaler_model": { + "name": "upscaler_model" + }, + "upscaler_resolution": { + "name": "upscaler_resolution" + }, + "video": { + "name": "video" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "TorchCompileModel": { "display_name": "Torch Model Derleme", "inputs": { @@ -11577,6 +13838,10 @@ "name": "toplu_iş_boyutu", "tooltip": "Eğitim için kullanılacak toplu iş boyutu." }, + "bucket_mode": { + "name": "bucket_mode", + "tooltip": "Çözünürlük kova modunu etkinleştir. Etkinleştirildiğinde, ResolutionBucket düğümünden önceden kovalanmış latentler beklenir." + }, "control_after_generate": { "name": "oluşturduktan sonra kontrol et" }, @@ -11637,20 +13902,20 @@ "tooltip": "Eğitim için kullanılacak veri tipi." } }, - "outputs": { - "0": { - "name": "lora_ile_model" + "outputs": [ + { + "tooltip": "LoRA uygulanmış model" }, - "1": { - "name": "lora" + { + "tooltip": "LoRA ağırlıkları" }, - "2": { - "name": "kayıp" + { + "tooltip": "Kayıp geçmişi" }, - "3": { - "name": "adımlar" + { + "tooltip": "Toplam eğitim adımı" } - } + ] }, "TrimAudioDuration": { "description": "Ses tensörünü seçilen zaman aralığına kırp.", @@ -11667,6 +13932,11 @@ "name": "başlangıç_indeksi", "tooltip": "Saniye cinsinden başlangıç zamanı, sondan saymak için negatif olabilir (saniyenin alt birimlerini destekler)." } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "TrimVideoLatent": { @@ -11708,23 +13978,62 @@ "TripoConversionNode": { "display_name": "Tripo: Modeli Dönüştür", "inputs": { + "animate_in_place": { + "name": "animate_in_place" + }, + "bake": { + "name": "bake" + }, + "export_orientation": { + "name": "export_orientation" + }, + "export_vertex_colors": { + "name": "export_vertex_colors" + }, "face_limit": { "name": "yüz_sınırı" }, + "fbx_preset": { + "name": "fbx_preset" + }, + "flatten_bottom": { + "name": "flatten_bottom" + }, + "flatten_bottom_threshold": { + "name": "flatten_bottom_threshold" + }, + "force_symmetry": { + "name": "force_symmetry" + }, "format": { "name": "biçim" }, "original_model_task_id": { "name": "orijinal_model_görev_id" }, + "pack_uv": { + "name": "pack_uv" + }, + "part_names": { + "name": "part_names" + }, + "pivot_to_center_bottom": { + "name": "pivot_to_center_bottom" + }, "quad": { "name": "dörtlü" }, + "scale_factor": { + "name": "scale_factor" + }, "texture_format": { "name": "doku_biçimi" }, "texture_size": { "name": "doku_boyutu" + }, + "with_animation": { + "name": "with_animation" } } }, @@ -11734,6 +14043,9 @@ "face_limit": { "name": "yüz_sınırı" }, + "geometry_quality": { + "name": "geometry_quality" + }, "image": { "name": "görüntü" }, @@ -11786,6 +14098,9 @@ "face_limit": { "name": "yüz_sınırı" }, + "geometry_quality": { + "name": "geometry_quality" + }, "image": { "name": "görüntü" }, @@ -11903,6 +14218,9 @@ "face_limit": { "name": "yüz_sınırı" }, + "geometry_quality": { + "name": "geometry_quality" + }, "image_seed": { "name": "görüntü_tohumu" }, @@ -11981,6 +14299,25 @@ } } }, + "TruncateText": { + "display_name": "Metni Kısalt", + "inputs": { + "max_length": { + "name": "max_length", + "tooltip": "Maksimum metin uzunluğu." + }, + "texts": { + "name": "texts", + "tooltip": "İşlenecek metin." + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "İşlenmiş metinler" + } + } + }, "UNETLoader": { "display_name": "Difüzyon Modeli Yükle", "inputs": { @@ -12122,6 +14459,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEDecodeHunyuan3D": { @@ -12139,6 +14481,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEDecodeTiled": { @@ -12186,6 +14533,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEEncodeForInpaint": { @@ -12264,6 +14616,63 @@ "steps": { "name": "adımlar" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Veo3FirstLastFrameNode": { + "description": "Açıklama ve ilk ile son kareleri kullanarak video oluştur.", + "display_name": "Google Veo 3 İlk-Son-Kare'den Videoya", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "Çıktı videonun en-boy oranı" + }, + "control_after_generate": { + "name": "control after generate" + }, + "duration": { + "name": "duration", + "tooltip": "Çıktı videonun süresi (saniye cinsinden)" + }, + "first_frame": { + "name": "first_frame", + "tooltip": "Başlangıç karesi" + }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "Video için ses oluştur." + }, + "last_frame": { + "name": "last_frame", + "tooltip": "Bitiş karesi" + }, + "model": { + "name": "model" + }, + "negative_prompt": { + "name": "negative_prompt", + "tooltip": "Videoda kaçınılması gerekenleri yönlendiren negatif metin açıklaması" + }, + "prompt": { + "name": "prompt", + "tooltip": "Videonun metin açıklaması" + }, + "resolution": { + "name": "resolution" + }, + "seed": { + "name": "seed", + "tooltip": "Video üretimi için tohum" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Veo3VideoGenerationNode": { @@ -12392,6 +14801,166 @@ } } }, + "Vidu2ImageToVideoNode": { + "description": "Bir görüntüden ve isteğe bağlı bir istemden video oluşturun.", + "display_name": "Vidu2 Görüntüden Videoya Üretim", + "inputs": { + "control_after_generate": { + "name": "oluşturduktan sonra kontrol et" + }, + "duration": { + "name": "süre" + }, + "image": { + "name": "görüntü", + "tooltip": "Oluşturulan videonun başlangıç karesi olarak kullanılacak bir görüntü." + }, + "model": { + "name": "model" + }, + "movement_amplitude": { + "name": "hareket_genliği", + "tooltip": "Karedeki nesnelerin hareket genliği." + }, + "prompt": { + "name": "istem", + "tooltip": "Video üretimi için isteğe bağlı metin istemi (en fazla 2000 karakter)." + }, + "resolution": { + "name": "çözünürlük" + }, + "seed": { + "name": "tohum" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2ReferenceVideoNode": { + "description": "Birden fazla referans görüntü ve bir istemden video oluşturun.", + "display_name": "Vidu2 Referans ile Videoya Üretim", + "inputs": { + "aspect_ratio": { + "name": "en-boy_oranı" + }, + "audio": { + "name": "ses", + "tooltip": "Etkinleştirildiğinde, video isteme göre oluşturulan konuşma ve arka plan müziği içerecektir." + }, + "control_after_generate": { + "name": "oluşturduktan sonra kontrol et" + }, + "duration": { + "name": "süre" + }, + "model": { + "name": "model" + }, + "movement_amplitude": { + "name": "hareket_genliği", + "tooltip": "Karedeki nesnelerin hareket genliği." + }, + "prompt": { + "name": "istem", + "tooltip": "Etkinleştirildiğinde, video isteme göre oluşturulan konuşma ve arka plan müziği içerecektir." + }, + "resolution": { + "name": "çözünürlük" + }, + "seed": { + "name": "tohum" + }, + "subjects": { + "name": "konular", + "tooltip": "Her konu için en fazla 3 referans görüntü sağlayın (tüm konular için toplam 7 görüntü). İstemlerde @subject{subject_id} ile referans verin." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2StartEndToVideoNode": { + "description": "Bir başlangıç karesi, bir bitiş karesi ve bir komut istemiyle video oluşturun.", + "display_name": "Vidu2 Başlangıç/Bitiş Kareden Videoya Üretim", + "inputs": { + "control_after_generate": { + "name": "oluşturduktan sonra kontrol et" + }, + "duration": { + "name": "süre" + }, + "end_frame": { + "name": "bitiş_kare" + }, + "first_frame": { + "name": "ilk_kare" + }, + "model": { + "name": "model" + }, + "movement_amplitude": { + "name": "hareket_genliği", + "tooltip": "Karedeki nesnelerin hareket genliği." + }, + "prompt": { + "name": "komut_istemi", + "tooltip": "Komut istemi açıklaması (en fazla 2000 karakter)." + }, + "resolution": { + "name": "çözünürlük" + }, + "seed": { + "name": "tohum" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2TextToVideoNode": { + "description": "Bir metin komut isteminden video oluşturun", + "display_name": "Vidu2 Metinden Videoya Üretim", + "inputs": { + "aspect_ratio": { + "name": "en-boy_oranı" + }, + "background_music": { + "name": "arka_plan_müziği", + "tooltip": "Oluşturulan videoya arka plan müziği eklenip eklenmeyeceği." + }, + "control_after_generate": { + "name": "oluşturduktan sonra kontrol et" + }, + "duration": { + "name": "süre" + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "komut_istemi", + "tooltip": "Video üretimi için metinsel açıklama, maksimum uzunluk 2000 karakter." + }, + "resolution": { + "name": "çözünürlük" + }, + "seed": { + "name": "tohum" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "ViduImageToVideoNode": { "description": "Görsel ve isteğe bağlı prompt'tan video oluştur", "display_name": "Vidu Görselden Video Oluşturma", @@ -12580,6 +15149,11 @@ "voxel": { "name": "voksel" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VoxelToMeshBasic": { @@ -12591,6 +15165,11 @@ "voxel": { "name": "voksel" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Wan22FunControlToVideo": { @@ -12870,6 +15449,10 @@ "name": "bağlam_adımı", "tooltip": "Bağlam penceresinin adım aralığı; sadece düzenli çizelgeler için geçerlidir." }, + "freenoise": { + "name": "freenoise", + "tooltip": "FreeNoise gürültü karıştırmayı uygulayıp uygulamama, pencere birleştirmesini iyileştirir." + }, "fuse_method": { "name": "birleştirme_yöntemi", "tooltip": "Bağlam pencerelerini birleştirmek için kullanılacak yöntem." @@ -13210,6 +15793,10 @@ "name": "tohum", "tooltip": "Oluşturma için kullanılacak tohum değeri." }, + "shot_type": { + "name": "shot_type", + "tooltip": "Oluşturulan video için çekim türünü belirtir; yani, videonun tek bir sürekli çekim mi yoksa kesmeli birden fazla çekim mi olacağını belirler. Bu parametre yalnızca prompt_extend True olduğunda geçerlidir." + }, "watermark": { "name": "filigran", "tooltip": "Sonuca \"AI tarafından oluşturulmuştur\" filigranı eklenip eklenmeyeceği." @@ -13221,6 +15808,196 @@ } } }, + "WanInfiniteTalkToVideo": { + "display_name": "WanInfiniteTalkToVideo", + "inputs": { + "audio_encoder_output_1": { + "name": "audio_encoder_output_1" + }, + "audio_scale": { + "name": "audio_scale" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "height": { + "name": "yükseklik" + }, + "length": { + "name": "uzunluk" + }, + "mode": { + "name": "mod" + }, + "model": { + "name": "model" + }, + "model_patch": { + "name": "model_patch" + }, + "motion_frame_count": { + "name": "hareket_kare_sayısı", + "tooltip": "Hareket bağlamı olarak kullanılacak önceki karelerin sayısı." + }, + "negative": { + "name": "negatif" + }, + "positive": { + "name": "pozitif" + }, + "previous_frames": { + "name": "önceki_kareler" + }, + "start_image": { + "name": "başlangıç_görseli" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "genişlik" + } + }, + "outputs": { + "0": { + "name": "model", + "tooltip": null + }, + "1": { + "name": "pozitif", + "tooltip": null + }, + "2": { + "name": "negatif", + "tooltip": null + }, + "3": { + "name": "latent", + "tooltip": null + }, + "4": { + "name": "kırpılmış_görsel", + "tooltip": null + } + } + }, + "WanMoveConcatTrack": { + "display_name": "WanMoveConcatTrack", + "inputs": { + "tracks_1": { + "name": "tracks_1" + }, + "tracks_2": { + "name": "tracks_2" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanMoveTrackToVideo": { + "display_name": "WanMoveTrackToVideo", + "inputs": { + "batch_size": { + "name": "toplu_boyutu" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "height": { + "name": "yükseklik" + }, + "length": { + "name": "uzunluk" + }, + "negative": { + "name": "negatif" + }, + "positive": { + "name": "pozitif" + }, + "start_image": { + "name": "başlangıç_görseli" + }, + "strength": { + "name": "güç", + "tooltip": "Parça koşullandırmasının gücü." + }, + "tracks": { + "name": "izler" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "genişlik" + } + }, + "outputs": { + "0": { + "name": "pozitif", + "tooltip": null + }, + "1": { + "name": "negatif", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "WanMoveTracksFromCoords": { + "display_name": "WanMoveTracksFromCoords", + "inputs": { + "track_coords": { + "name": "track_coords" + }, + "track_mask": { + "name": "track_mask" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "track_length", + "tooltip": null + } + } + }, + "WanMoveVisualizeTracks": { + "display_name": "WanMoveVisualizeTracks", + "inputs": { + "circle_size": { + "name": "daire_boyutu" + }, + "images": { + "name": "görseller" + }, + "line_resolution": { + "name": "çizgi_çözünürlüğü" + }, + "line_width": { + "name": "çizgi_kalınlığı" + }, + "opacity": { + "name": "opaklık" + }, + "tracks": { + "name": "izler" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WanPhantomSubjectToVideo": { "display_name": "WanPhantomSubjectToVideo", "inputs": { @@ -13268,6 +16045,51 @@ } } }, + "WanReferenceVideoApi": { + "description": "Girdi videolarındaki karakter ve sesi, bir istemle birleştirerek karakter tutarlılığını koruyan yeni bir video oluşturun.", + "display_name": "Wan Reference to Video", + "inputs": { + "control_after_generate": { + "name": "oluşturduktan sonra kontrol" + }, + "duration": { + "name": "süre" + }, + "model": { + "name": "model" + }, + "negative_prompt": { + "name": "negatif_istem", + "tooltip": "Kaçınılması gerekenleri tanımlayan negatif istem." + }, + "prompt": { + "name": "istem", + "tooltip": "Öğeleri ve görsel özellikleri tanımlayan istem. İngilizce ve Çince desteklenir. Referans karakterlere atıfta bulunmak için `character1` ve `character2` gibi tanımlayıcılar kullanın." + }, + "reference_videos": { + "name": "referans_videolar" + }, + "seed": { + "name": "tohum" + }, + "shot_type": { + "name": "çekim_türü", + "tooltip": "Oluşturulan video için çekim türünü belirtir; yani, videonun tek bir sürekli çekim mi yoksa kesmeli birden fazla çekim mi olacağını belirtir." + }, + "size": { + "name": "boyut" + }, + "watermark": { + "name": "filigran", + "tooltip": "Sonuca yapay zeka tarafından oluşturulan bir filigran eklenip eklenmeyeceği." + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WanSoundImageToVideo": { "display_name": "WanSoundImageToVideo", "inputs": { @@ -13446,6 +16268,10 @@ "name": "seed", "tooltip": "Oluşturma için kullanılacak seed değeri." }, + "shot_type": { + "name": "çekim_türü", + "tooltip": "Oluşturulan video için çekim türünü belirtir; yani, videonun tek bir sürekli çekim mi yoksa kesmeli birden fazla çekim mi olacağını belirtir. Bu parametre yalnızca prompt_extend True olduğunda geçerlidir." + }, "size": { "name": "size" }, @@ -13571,6 +16397,43 @@ } } }, + "WavespeedFlashVSRNode": { + "description": "Düşük çözünürlüklü veya bulanık videolar için hızlı, yüksek kaliteli video yükseltici; çözünürlüğü artırır ve netliği geri kazandırır.", + "display_name": "FlashVSR Video Yükseltme", + "inputs": { + "target_resolution": { + "name": "hedef_çözünürlük" + }, + "video": { + "name": "video" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WavespeedImageUpscaleNode": { + "description": "Görüntü çözünürlüğünü ve kalitesini artırın, fotoğrafları keskin ve ayrıntılı sonuçlar için 4K veya 8K'ya yükseltin.", + "display_name": "WaveSpeed Görüntü Yükseltme", + "inputs": { + "image": { + "name": "görüntü" + }, + "model": { + "name": "model" + }, + "target_resolution": { + "name": "hedef_çözünürlük" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WebcamCapture": { "display_name": "Webcam Yakalama", "inputs": { @@ -13590,6 +16453,32 @@ } } }, + "ZImageFunControlnet": { + "display_name": "ZImageFunControlnet", + "inputs": { + "image": { + "name": "görsel" + }, + "inpaint_image": { + "name": "boyanacak_görsel" + }, + "mask": { + "name": "mask" + }, + "model": { + "name": "model" + }, + "model_patch": { + "name": "model_patch" + }, + "strength": { + "name": "güç" + }, + "vae": { + "name": "vae" + } + } + }, "unCLIPCheckpointLoader": { "display_name": "unCLIPKontrolNoktasıYükleyici", "inputs": { @@ -13614,5 +16503,19 @@ "name": "güç" } } + }, + "wanBlockSwap": { + "description": "NOP", + "display_name": "wanBlockSwap", + "inputs": { + "model": { + "name": "model" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } } } diff --git a/src/locales/tr/settings.json b/src/locales/tr/settings.json index b76a87ef2..19bef1bad 100644 --- a/src/locales/tr/settings.json +++ b/src/locales/tr/settings.json @@ -29,12 +29,26 @@ "name": "Tuval arka plan resmi", "tooltip": "Tuval arka planı için resim URL'si. Çıktılar panelindeki bir resme sağ tıklayıp \"Arka Plan Olarak Ayarla\"yı seçerek kullanabilir veya yükleme düğmesini kullanarak kendi resminizi yükleyebilirsiniz." }, + "Comfy_Canvas_LeftMouseClickBehavior": { + "name": "Sol Fare Tıklama Davranışı", + "options": { + "Panning": "Kaydırma", + "Select": "Seç" + } + }, + "Comfy_Canvas_MouseWheelScroll": { + "name": "Fare Tekerleği Kaydırma", + "options": { + "Panning": "Kaydırma", + "Zoom in/out": "Yakınlaştır/Uzaklaştır" + } + }, "Comfy_Canvas_NavigationMode": { "name": "Tuval Gezinme Modu", "options": { + "Custom": "Özel", "Drag Navigation": "Sürükleyerek Gezinme", - "Standard (New)": "Standart (Yeni)", - "Custom": "Özel" + "Standard (New)": "Standart (Yeni)" } }, "Comfy_Canvas_SelectionToolbox": { @@ -65,6 +79,17 @@ "Comfy_EnableWorkflowViewRestore": { "name": "İş akışlarında tuval konumunu ve yakınlaştırma seviyesini kaydet ve geri yükle" }, + "Comfy_Execution_PreviewMethod": { + "name": "Canlı önizleme yöntemi", + "options": { + "auto": "otomatik", + "default": "default", + "latent2rgb": "latent2rgb", + "none": "hiçbiri", + "taesd": "taesd" + }, + "tooltip": "Görüntü oluşturma sırasında canlı önizleme yöntemi. \"default\" sunucu CLI ayarını kullanır." + }, "Comfy_FloatRoundingPrecision": { "name": "Ondalık sayı widget yuvarlama ondalık basamakları [0 = otomatik].", "tooltip": "(sayfanın yeniden yüklenmesini gerektirir)" @@ -86,6 +111,10 @@ "None": "Yok" } }, + "Comfy_Graph_LiveSelection": { + "name": "Canlı seçim", + "tooltip": "Etkinleştirildiğinde, seçim dikdörtgenini sürüklerken düğümler gerçek zamanlı olarak seçilir/seçimi kaldırılır; diğer tasarım araçlarına benzer şekilde çalışır." + }, "Comfy_Graph_ZoomSpeed": { "name": "Tuval yakınlaştırma hızı" }, @@ -152,6 +181,15 @@ "name": "Minimum Işık Yoğunluğu", "tooltip": "3D sahneler için izin verilen minimum ışık yoğunluğu değerini ayarlar. Bu, herhangi bir 3D widget'ta aydınlatma ayarlanırken ayarlanabilecek alt parlaklık sınırını tanımlar." }, + "Comfy_Load3D_PLYEngine": { + "name": "PLY Motoru", + "options": { + "fastply": "fastply", + "sparkjs": "sparkjs", + "threejs": "threejs" + }, + "tooltip": "PLY dosyalarını yüklemek için motoru seçin. \"threejs\" yerel Three.js PLYLoader'ı kullanır (mesh PLY dosyaları için en iyisi). \"fastply\" ASCII nokta bulutu PLY dosyaları için optimize edilmiş bir yükleyici kullanır. \"sparkjs\" ise 3D Gaussian Splatting PLY dosyaları için Spark.js kullanır." + }, "Comfy_Load3D_ShowGrid": { "name": "Başlangıç Izgara Görünürlüğü", "tooltip": "Yeni bir 3D widget oluşturulduğunda ızgaranın varsayılan olarak görünür olup olmadığını kontrol eder. Bu varsayılan, oluşturulduktan sonra her widget için ayrı ayrı değiştirilebilir." @@ -167,10 +205,6 @@ "name": "Fırça ayarını baskın eksene kilitle", "tooltip": "Etkinleştirildiğinde, fırça ayarları yalnızca daha fazla hareket ettiğiniz yöne bağlı olarak boyutu VEYA sertliği etkileyecektir" }, - "Comfy_MaskEditor_UseNewEditor": { - "name": "Yeni maske düzenleyiciyi kullan", - "tooltip": "Yeni maske düzenleyici arayüzüne geç" - }, "Comfy_ModelLibrary_AutoLoadAll": { "name": "Tüm model klasörlerini otomatik olarak yükle", "tooltip": "Doğruysa, model kütüphanesini açar açmaz tüm klasörler yüklenecektir (bu, yüklenirken gecikmelere neden olabilir). Yanlışsa, kök düzeyindeki model klasörleri yalnızca üzerlerine tıkladığınızda yüklenecektir." @@ -238,6 +272,10 @@ "Comfy_Node_AllowImageSizeDraw": { "name": "Görüntü önizlemesinin altında genişlik × yüksekliği göster" }, + "Comfy_Node_AlwaysShowAdvancedWidgets": { + "name": "Tüm düğümlerde gelişmiş araçları her zaman göster", + "tooltip": "Etkinleştirildiğinde, gelişmiş araçlar tüm düğümlerde ayrı ayrı genişletmeye gerek kalmadan her zaman görünür olur." + }, "Comfy_Node_AutoSnapLinkToSlot": { "name": "Bağlantıyı otomatik olarak düğüm yuvasına yapıştır", "tooltip": "Bir bağlantıyı bir düğümün üzerine sürüklerken, bağlantı otomatik olarak düğüm üzerindeki uygun bir giriş yuvasına yapışır" @@ -298,6 +336,10 @@ "name": "Kuyruk geçmişi boyutu", "tooltip": "Kuyruk geçmişinde gösterilen maksimum görev sayısı." }, + "Comfy_Queue_QPOV2": { + "name": "Varlıklar yan panelinde birleşik iş kuyruğunu kullan", + "tooltip": "Kayan iş kuyruğu panelini, Varlıklar yan paneline gömülü eşdeğer bir iş kuyruğu ile değiştirir. Kayan panel düzenine dönmek için bunu devre dışı bırakabilirsiniz." + }, "Comfy_Sidebar_Location": { "name": "Kenar çubuğu konumu", "options": { @@ -312,6 +354,13 @@ "small": "küçük" } }, + "Comfy_Sidebar_Style": { + "name": "Kenar Çubuğu Stili", + "options": { + "connected": "Bağlı", + "floating": "Yüzer" + } + }, "Comfy_Sidebar_UnifiedWidth": { "name": "Birleşik kenar çubuğu genişliği" }, @@ -328,6 +377,14 @@ "Comfy_TreeExplorer_ItemPadding": { "name": "Ağaç gezgini öğe dolgusu" }, + "Comfy_UI_TabBarLayout": { + "name": "Sekme Çubuğu Düzeni", + "options": { + "Default": "Varsayılan", + "Integrated": "Entegre" + }, + "tooltip": "Sekme çubuğu düzenini kontrol eder. \"Entegre\" seçeneği, Yardım ve Kullanıcı kontrollerini sekme çubuğu alanına taşır." + }, "Comfy_UseNewMenu": { "name": "Yeni menüyü kullan", "options": { @@ -339,6 +396,14 @@ "Comfy_Validation_Workflows": { "name": "İş akışlarını doğrula" }, + "Comfy_VueNodes_AutoScaleLayout": { + "name": "Otomatik ölçeklendirme düzeni (Vue düğümleri)", + "tooltip": "Vue işlemeye geçerken düğüm konumlarını örtüşmeyi önlemek için otomatik olarak ölçeklendir" + }, + "Comfy_VueNodes_Enabled": { + "name": "Modern Düğüm Tasarımı (Vue Düğümleri)", + "tooltip": "Modern: Gelişmiş etkileşim, yerel tarayıcı özellikleri ve güncellenmiş görsel tasarıma sahip DOM tabanlı işleme. Klasik: Geleneksel tuval işleme." + }, "Comfy_WidgetControlMode": { "name": "Widget kontrol modu", "options": { @@ -376,6 +441,9 @@ "Comfy_Workflow_SortNodeIdOnSave": { "name": "İş akışını kaydederken düğüm kimliklerini sırala" }, + "Comfy_Workflow_WarnBlueprintOverwrite": { + "name": "Mevcut bir alt grafik şablonunun üzerine yazmak için onay iste" + }, "Comfy_Workflow_WorkflowTabsPosition": { "name": "Açılan iş akışları konumu", "options": { @@ -407,37 +475,5 @@ }, "pysssss_SnapToGrid": { "name": "Her zaman ızgaraya yapıştır" - }, - "Comfy_Canvas_LeftMouseClickBehavior": { - "name": "Sol Fare Tıklama Davranışı", - "options": { - "Panning": "Kaydırma", - "Select": "Seç" - } - }, - "Comfy_Canvas_MouseWheelScroll": { - "name": "Fare Tekerleği Kaydırma", - "options": { - "Panning": "Kaydırma", - "Zoom in/out": "Yakınlaştır/Uzaklaştır" - } - }, - "Comfy_Sidebar_Style": { - "name": "Kenar Çubuğu Stili", - "options": { - "floating": "Yüzer", - "connected": "Bağlı" - } - }, - "Comfy_VueNodes_AutoScaleLayout": { - "name": "Otomatik ölçeklendirme düzeni (Vue düğümleri)", - "tooltip": "Vue işlemeye geçerken düğüm konumlarını örtüşmeyi önlemek için otomatik olarak ölçeklendir" - }, - "Comfy_VueNodes_Enabled": { - "name": "Modern Düğüm Tasarımı (Vue Düğümleri)", - "tooltip": "Modern: Gelişmiş etkileşim, yerel tarayıcı özellikleri ve güncellenmiş görsel tasarıma sahip DOM tabanlı işleme. Klasik: Geleneksel tuval işleme." - }, - "Comfy_Workflow_WarnBlueprintOverwrite": { - "name": "Mevcut bir alt grafik şablonunun üzerine yazmak için onay iste" } } diff --git a/src/locales/zh-TW/commands.json b/src/locales/zh-TW/commands.json index 0a52ed2f8..677269ac3 100644 --- a/src/locales/zh-TW/commands.json +++ b/src/locales/zh-TW/commands.json @@ -1,4 +1,40 @@ { + "Comfy-Desktop_CheckForUpdates": { + "label": "檢查更新" + }, + "Comfy-Desktop_Folders_OpenCustomNodesFolder": { + "label": "開啟自訂節點資料夾" + }, + "Comfy-Desktop_Folders_OpenInputsFolder": { + "label": "開啟輸入資料夾" + }, + "Comfy-Desktop_Folders_OpenLogsFolder": { + "label": "開啟日誌資料夾" + }, + "Comfy-Desktop_Folders_OpenModelConfig": { + "label": "開啟 extra_model_paths.yaml" + }, + "Comfy-Desktop_Folders_OpenModelsFolder": { + "label": "開啟模型資料夾" + }, + "Comfy-Desktop_Folders_OpenOutputsFolder": { + "label": "開啟輸出資料夾" + }, + "Comfy-Desktop_OpenDevTools": { + "label": "開啟開發者工具" + }, + "Comfy-Desktop_OpenUserGuide": { + "label": "桌面版使用指南" + }, + "Comfy-Desktop_Quit": { + "label": "結束" + }, + "Comfy-Desktop_Reinstall": { + "label": "重新安裝" + }, + "Comfy-Desktop_Restart": { + "label": "重新啟動" + }, "Comfy_3DViewer_Open3DViewer": { "label": "為選取的節點開啟 3D 檢視器(Beta)" }, @@ -155,18 +191,30 @@ "Comfy_Manager_ShowUpdateAvailablePacks": { "label": "檢查自訂節點更新" }, - "Comfy_Manager_ToggleManagerProgressDialog": { - "label": "切換自訂節點管理器進度條" - }, "Comfy_MaskEditor_BrushSize_Decrease": { "label": "減少 MaskEditor 畫筆大小" }, "Comfy_MaskEditor_BrushSize_Increase": { "label": "增加 MaskEditor 畫筆大小" }, + "Comfy_MaskEditor_ColorPicker": { + "label": "在 MaskEditor 中開啟顏色選擇器" + }, + "Comfy_MaskEditor_Mirror_Horizontal": { + "label": "在 MaskEditor 中水平鏡像" + }, + "Comfy_MaskEditor_Mirror_Vertical": { + "label": "在 MaskEditor 中垂直鏡像" + }, "Comfy_MaskEditor_OpenMaskEditor": { "label": "為選取的節點開啟 Mask 編輯器" }, + "Comfy_MaskEditor_Rotate_Left": { + "label": "在 MaskEditor 中向左旋轉" + }, + "Comfy_MaskEditor_Rotate_Right": { + "label": "在 MaskEditor 中向右旋轉" + }, "Comfy_Memory_UnloadModels": { "label": "卸載模型" }, @@ -197,12 +245,18 @@ "Comfy_QueueSelectedOutputNodes": { "label": "佇列所選的輸出節點" }, + "Comfy_Queue_ToggleOverlay": { + "label": "切換任務歷史" + }, "Comfy_Redo": { "label": "重做" }, "Comfy_RefreshNodeDefinitions": { "label": "重新整理節點定義" }, + "Comfy_RenameWorkflow": { + "label": "重新命名工作流程" + }, "Comfy_SaveWorkflow": { "label": "儲存工作流程" }, @@ -221,6 +275,12 @@ "Comfy_ToggleHelpCenter": { "label": "說明中心" }, + "Comfy_ToggleLinear": { + "label": "切換線性模式" + }, + "Comfy_ToggleQPOV2": { + "label": "切換佇列面板 V2" + }, "Comfy_ToggleTheme": { "label": "切換主題(深色/淺色)" }, diff --git a/src/locales/zh-TW/main.json b/src/locales/zh-TW/main.json index 6eb41f42e..827a9b73b 100644 --- a/src/locales/zh-TW/main.json +++ b/src/locales/zh-TW/main.json @@ -1,6 +1,8 @@ { "actionbar": { - "dockToTop": "停靠到頂部" + "dockToTop": "停靠到頂部", + "feedback": "意見回饋", + "feedbackTooltip": "意見回饋" }, "apiNodesCostBreakdown": { "costPerRun": "每次執行成本", @@ -18,23 +20,141 @@ "assetCard": "{name} - {type} 資源", "loadingAsset": "載入資源中" }, + "assetCollection": "資產收藏", "assets": "資產", "baseModels": "基礎模型", "browseAssets": "瀏覽資產", + "byType": "依類型", + "checkpoints": "Checkpoints", + "civitaiLinkExample": "{example} {link}", + "civitaiLinkExampleStrong": "範例:", + "civitaiLinkExampleUrl": "https://civitai.com/models/10706/luisap-z-image-and-qwen-pixel-art-refiner?modelVersionId=2225295", + "civitaiLinkLabel": "Civitai 模型{download}連結", + "civitaiLinkLabelDownload": "下載", + "civitaiLinkPlaceholder": "請在此貼上連結", + "confirmModelDetails": "確認模型細節", "connectionError": "請檢查您的連線並重試", + "deletion": { + "body": "此模型將會從您的資料庫中永久移除。", + "complete": "{assetName} 已被刪除。", + "failed": "無法刪除 {assetName}。", + "header": "要刪除此模型嗎?", + "inProgress": "正在刪除 {assetName}..." + }, + "download": { + "complete": "下載完成", + "failed": "下載失敗", + "inProgress": "正在下載 {assetName}..." + }, + "emptyImported": { + "canImport": "尚未匯入模型。點擊「匯入模型」以新增您的模型。", + "restricted": "個人模型僅限 Creator 方案及以上等級使用。" + }, + "errorFileTooLarge": "檔案超過允許的最大大小限制", + "errorFormatNotAllowed": "僅允許 SafeTensor 格式", + "errorModelTypeNotSupported": "不支援此模型類型", + "errorUnknown": "發生未預期的錯誤", + "errorUnsafePickleScan": "CivitAI 偵測到此檔案中可能存在不安全的程式碼", + "errorUnsafeVirusScan": "CivitAI 偵測到此檔案中有惡意軟體或可疑內容", + "errorUploadFailed": "匯入資產失敗,請再試一次。", "failedToCreateNode": "無法建立節點。請重試或查看主控台以取得詳細資訊。", "fileFormats": "檔案格式", + "fileName": "檔案名稱", + "fileSize": "檔案大小", + "filterBy": "篩選條件", + "findInLibrary": "可在模型庫的 {type} 區段找到。", + "finish": "完成", + "genericLinkPlaceholder": "請在此貼上連結", + "importAnother": "匯入其他", + "imported": "已匯入", + "jobId": "工作 ID", "loadingModels": "正在載入 {type}...", + "maxFileSize": "最大檔案大小:{size}", + "maxFileSizeValue": "1 GB", + "media": { + "audioPlaceholder": "音訊", + "threeDModelPlaceholder": "3D 模型" + }, + "modelAssociatedWithLink": "您提供的連結所對應的模型:", + "modelInfo": { + "addBaseModel": "新增基礎模型...", + "addTag": "新增標籤...", + "additionalTags": "其他標籤", + "baseModelUnknown": "基礎模型未知", + "basicInfo": "基本資訊", + "compatibleBaseModels": "相容基礎模型", + "description": "描述", + "descriptionNotSet": "尚未設定描述", + "descriptionPlaceholder": "為此模型新增描述...", + "displayName": "顯示名稱", + "editDisplayName": "編輯顯示名稱", + "fileName": "檔案名稱", + "modelDescription": "模型描述", + "modelTagging": "模型標籤", + "modelType": "模型類型", + "noAdditionalTags": "沒有其他標籤", + "selectModelPrompt": "選擇模型以查看其資訊", + "selectModelType": "選擇模型類型...", + "source": "來源", + "title": "模型資訊", + "triggerPhrases": "觸發詞", + "viewOnSource": "在 {source} 上檢視" + }, + "modelName": "模型名稱", + "modelNamePlaceholder": "請輸入此模型的名稱", + "modelTypeSelectorLabel": "這是什麼類型的模型?", + "modelTypeSelectorPlaceholder": "選擇模型類型", + "modelUploaded": "模型匯入成功。", "noAssetsFound": "找不到資產", "noModelsInFolder": "此資料夾中沒有可用的 {type}", - "searchAssetsPlaceholder": "搜尋資產...", + "noValidSourceDetected": "未偵測到有效的匯入來源", + "notSureLeaveAsIs": "不確定?請保持原樣", + "onlyCivitaiUrlsSupported": "僅支援 Civitai 的網址", + "ownership": "擁有權", + "ownershipAll": "全部", + "ownershipMyModels": "我的模型", + "ownershipPublicModels": "公開模型", + "processingModel": "已開始下載", + "processingModelDescription": "您可以關閉此對話框,下載將在背景繼續進行。", + "providerCivitai": "Civitai", + "providerHuggingFace": "Hugging Face", + "rename": { + "failed": "無法重新命名資產。" + }, + "selectFrameworks": "選擇框架", + "selectModelType": "選擇模型類型", + "selectProjects": "選擇專案", "sortAZ": "A-Z", "sortBy": "排序依據", "sortPopular": "熱門", "sortRecent": "最近", "sortZA": "Z-A", + "sortingType": "排序方式", + "tags": "標籤", + "tagsHelp": "以逗號分隔標籤", + "tagsPlaceholder": "例如:models, checkpoint", "tryAdjustingFilters": "請嘗試調整您的搜尋或篩選條件", - "unknown": "未知" + "unknown": "未知", + "unsupportedUrlSource": "僅支援來自 {sources} 的網址", + "upgradeFeatureDescription": "此功能僅限 Creator 或 Pro 方案使用。", + "upgradeToUnlockFeature": "升級以解鎖此功能", + "upload": "匯入", + "uploadFailed": "匯入失敗", + "uploadModel": "匯入", + "uploadModelDescription1": "貼上 Civitai 模型下載連結以新增至您的資料庫。", + "uploadModelDescription1Generic": "貼上模型下載連結以新增至您的資料庫。", + "uploadModelDescription2": "目前僅支援來自 {link} 的連結", + "uploadModelDescription2Generic": "目前僅支援以下提供者的網址:", + "uploadModelDescription2Link": "https://civitai.com/models", + "uploadModelDescription3": "最大檔案大小:{size}", + "uploadModelFailedToRetrieveMetadata": "無法取得中繼資料。請檢查連結並再試一次。", + "uploadModelFromCivitai": "從 Civitai 匯入模型", + "uploadModelGeneric": "匯入模型", + "uploadModelHelpFooterText": "需要協助尋找網址嗎?點擊下方提供者觀看教學影片。", + "uploadModelHelpVideo": "模型匯入教學影片", + "uploadModelHowDoIFindThis": "要如何找到這個?", + "uploadSuccess": "模型匯入成功!", + "uploadingModel": "正在匯入模型..." }, "auth": { "apiKey": { @@ -148,12 +268,19 @@ "title": "建立帳戶" } }, + "boundingBox": { + "height": "高度", + "width": "寬度", + "x": "X", + "y": "Y" + }, "breadcrumbsMenu": { "clearWorkflow": "清除工作流程", "deleteBlueprint": "刪除藍圖", "deleteWorkflow": "刪除工作流程", "duplicate": "複製", - "enterNewName": "輸入新名稱" + "enterNewName": "輸入新名稱", + "missingNodesWarning": "工作流程包含不支援的節點(以紅色標示)。" }, "clipboard": { "errorMessage": "複製到剪貼簿失敗", @@ -207,6 +334,7 @@ }, "retry": "再試一次", "retrying": "正在重試...", + "skipToCloudApp": "跳至雲端應用程式", "start": { "desc": "無需任何設定。可在任何裝置上使用。", "download": "下載 ComfyUI", @@ -340,6 +468,8 @@ "Edit Subgraph Widgets": "編輯子圖小工具", "Expand": "展開", "Expand Node": "展開節點", + "Extensions": "擴充功能", + "FavoriteWidget": "收藏元件", "Horizontal": "水平", "Inputs": "輸入", "Left": "左側", @@ -359,6 +489,7 @@ "Remove": "移除", "Remove Bypass": "移除繞過", "Rename": "重新命名", + "RenameWidget": "重新命名元件", "Resize": "調整大小", "Right": "右側", "Run Branch": "執行分支", @@ -369,6 +500,7 @@ "Shapes": "形狀", "Title": "標題", "Top": "頂部", + "UnfavoriteWidget": "取消收藏元件", "Unpack Subgraph": "解包子圖", "Unpin": "取消釘選", "Vertical": "垂直", @@ -382,6 +514,7 @@ "additionalInfo": "其他資訊", "apiPricing": "API 價格", "credits": "點數", + "creditsAvailable": "可用點數", "details": "詳細資料", "eventType": "事件類型", "faqs": "常見問題", @@ -390,15 +523,46 @@ "messageSupport": "聯絡客服", "model": "模型", "purchaseCredits": "購買點數", + "refreshes": "將於 {date} 重置", "time": "時間", "topUp": { + "addMoreCredits": "儲值點數", + "addMoreCreditsToRun": "儲值點數以執行", + "amountToPayLabel": "需支付的美元金額", + "buy": "購買", + "buyCredits": "繼續付款", "buyNow": "立即購買", + "contactUs": "聯絡我們", + "creditsDescription": "點數用於執行工作流程或合作夥伴節點。", + "creditsPerDollar": "每美元點數", + "creditsToReceiveLabel": "可獲得的點數", + "howManyCredits": "您想要儲值多少點數?", "insufficientMessage": "您的點數不足,無法執行此工作流程。", "insufficientTitle": "點數不足", + "insufficientWorkflowMessage": "您的點數不足,無法執行此工作流程。", + "maxAllowed": "最多可購買 {credits} 點。", "maxAmount": "(最高 $1,000 美元)", + "maximumAmount": "最高 ${amount}", + "minRequired": "最低需購買 {credits} 點", + "minimumPurchase": "最低購買 ${amount}(${credits} 點)", + "needMore": "需要更多?", + "purchaseError": "購買失敗", + "purchaseErrorDetail": "購買點數失敗:{error}", "quickPurchase": "快速購買", "seeDetails": "查看詳情", - "topUp": "儲值" + "selectAmount": "選擇金額", + "templateNote": "*以 Wan Fun Control 範本產生", + "topUp": "儲值", + "unknownError": "發生未知錯誤", + "usdAmount": "${amount}", + "videosEstimate": "約 {count} 部影片", + "viewPricing": "查看價格詳情", + "youGet": "點數", + "youPay": "金額(美元)" + }, + "unified": { + "message": "點數已統一", + "tooltip": "我們已統一 Comfy 的付款系統。現在一切都以 Comfy 點數運作:\n- 合作夥伴節點(原 API 節點)\n- 雲端工作流程\n\n您原有的合作夥伴節點餘額已轉換為點數。" }, "yourCreditBalance": "您的點數餘額" }, @@ -414,6 +578,9 @@ "CLIP_VISION": "CLIP 視覺", "CLIP_VISION_OUTPUT": "CLIP 視覺輸出", "COMBO": "組合", + "COMFY_AUTOGROW_V3": "COMFY_AUTOGROW_V3", + "COMFY_DYNAMICCOMBO_V3": "COMFY_DYNAMICCOMBO_V3", + "COMFY_MATCHTYPE_V3": "COMFY_MATCHTYPE_V3", "CONDITIONING": "條件設定", "CONTROL_NET": "ControlNet", "FLOAT": "浮點數", @@ -424,18 +591,21 @@ "HOOKS": "掛鉤", "HOOK_KEYFRAMES": "關鍵影格掛鉤", "IMAGE": "影像", + "IMAGECOMPARE": "圖像比較", "INT": "整數", "LATENT": "latent (潛空間)", "LATENT_OPERATION": "latent 操作", + "LATENT_UPSCALE_MODEL": "LATENT_UPSCALE_MODEL", "LOAD3D_CAMERA": "載入 3D 攝影機", "LOAD_3D": "載入 3D", - "LOAD_3D_ANIMATION": "載入 3D 動畫", "LORA_MODEL": "LoRA模型", "LOSS_MAP": "損失圖", "LUMA_CONCEPTS": "LUMA 概念", "LUMA_REF": "LUMA 參考", "MASK": "遮罩", "MESH": "網格", + "MESHY_RIGGED_TASK_ID": "MESHY_RIGGED_TASK_ID", + "MESHY_TASK_ID": "MESHY_TASK_ID", "MODEL": "模型", "MODEL_PATCH": "模型修補", "MODEL_TASK_ID": "模型任務ID", @@ -455,6 +625,7 @@ "STYLE_MODEL": "風格模型", "SVG": "SVG", "TIMESTEPS_RANGE": "時間步範圍", + "TRACKS": "TRACKS", "UPSCALE_MODEL": "升頻模型", "VAE": "VAE", "VIDEO": "影片", @@ -523,14 +694,17 @@ "amount": "數量", "apply": "套用", "architecture": "架構", + "asset": "{count} 筆資產", "audioFailedToLoad": "無法載入音訊", "audioProgress": "音訊進度", "author": "作者", "back": "返回", + "batchRename": "批次重新命名", "beta": "測試版", "bookmark": "儲存至程式庫", "calculatingDimensions": "計算尺寸中", "cancel": "取消", + "cancelled": "已取消", "capture": "擷取", "category": "分類", "chart": "圖表", @@ -540,6 +714,7 @@ "clearAll": "全部清除", "clearFilters": "清除篩選", "close": "關閉", + "closeDialog": "關閉對話框", "color": "顏色", "comfy": "Comfy", "comfyOrgLogoAlt": "ComfyOrg 標誌", @@ -556,13 +731,17 @@ "control_before_generate": "生成前控制", "copied": "已複製", "copy": "複製", + "copyAll": "全部複製", "copyJobId": "複製工作 ID", "copyToClipboard": "複製到剪貼簿", "copyURL": "複製網址", + "core": "核心", "currentUser": "目前使用者", + "custom": "自訂", "customBackground": "自訂背景", "customize": "自訂", "customizeFolder": "自訂資料夾", + "decrement": "減少", "defaultBanner": "預設橫幅", "delete": "刪除", "deleteAudioFile": "刪除音訊檔案", @@ -571,27 +750,35 @@ "description": "描述", "devices": "裝置", "disableAll": "全部停用", + "disableSelected": "停用所選", + "disableThirdParty": "停用第三方", "disabling": "停用中", "dismiss": "關閉", "download": "下載", "downloadImage": "下載圖片", "downloadVideo": "下載影片", + "downloading": "下載中", "dropYourFileOr": "拖放您的檔案或", "duplicate": "複製", "edit": "編輯", "editImage": "編輯圖片", "editOrMaskImage": "編輯或遮罩圖片", + "emDash": "—", "empty": "空", "enableAll": "全部啟用", "enableOrDisablePack": "啟用或停用套件", + "enableSelected": "啟用所選", "enabled": "已啟用", "enabling": "啟用中", + "enterBaseName": "輸入基礎名稱", + "enterNewName": "輸入新名稱", "error": "錯誤", "errorLoadingImage": "載入圖片時發生錯誤", "errorLoadingVideo": "載入影片時發生錯誤", "experimental": "實驗性", "export": "匯出", "extensionName": "擴充套件名稱", + "failed": "失敗", "failedToCopyJobId": "複製工作 ID 失敗", "failedToDownloadImage": "下載圖片失敗", "failedToDownloadVideo": "下載影片失敗", @@ -607,12 +794,15 @@ "goToNode": "前往節點", "graphNavigation": "圖形導覽", "halfSpeed": "0.5倍速", + "hideLeftPanel": "隱藏左側面板", + "hideRightPanel": "隱藏右側面板", "icon": "圖示", "imageFailedToLoad": "無法載入圖片", "imagePreview": "圖片預覽 - 使用方向鍵在圖片間導航", "imageUrl": "圖片網址", "import": "匯入", "inProgress": "進行中", + "increment": "增加", "info": "節點資訊", "insert": "插入", "install": "安裝", @@ -620,7 +810,9 @@ "installing": "安裝中", "interrupted": "已中斷", "itemSelected": "已選取 {selectedCount} 項", + "itemsCopiedToClipboard": "已複製項目到剪貼簿", "itemsSelected": "已選取 {selectedCount} 項", + "job": "工作", "jobIdCopied": "工作 ID 已複製到剪貼簿", "keybinding": "快捷鍵", "keybindingAlreadyExists": "快捷鍵已存在於", @@ -638,14 +830,18 @@ "micPermissionDenied": "麥克風權限被拒絕", "migrate": "遷移", "missing": "缺少", + "more": "更多", "moreOptions": "更多選項", "moreWorkflows": "更多工作流程", "multiSelectDropdown": "多選下拉式選單", "name": "名稱", "newFolder": "新資料夾", "next": "下一步", + "nightly": "NIGHTLY", "no": "否", "noAudioRecorded": "沒有錄製到音訊", + "noItems": "沒有項目", + "noResults": "沒有結果", "noResultsFound": "找不到結果", "noTasksFound": "找不到任務", "noTasksFoundMessage": "佇列中沒有任務。", @@ -656,26 +852,45 @@ "nodeSlotsError": "節點插槽錯誤", "nodeWidgetsError": "節點小工具錯誤", "nodes": "節點", + "nodesCount": "{count} 個節點 | {count} 個節點 | {count} 個節點", "nodesRunning": "節點執行中", "none": "無", + "nothingToCopy": "沒有可複製的項目", + "nothingToDelete": "沒有可刪除的項目", + "nothingToDuplicate": "沒有可複製的項目", + "nothingToRename": "沒有可重新命名的項目", "ok": "確定", "openManager": "開啟管理器", "openNewIssue": "開啟新問題", + "or": "或", "overwrite": "覆蓋", + "playPause": "播放/暫停", "playRecording": "播放錄製", "playbackSpeed": "播放速度", "playing": "播放中", "pressKeysForNewBinding": "按下按鍵設定新綁定", "preview": "預覽", + "profile": "個人檔案", "progressCountOf": "共", + "queued": "已排隊", "ready": "就緒", "reconnected": "已重新連線", "reconnecting": "重新連線中", "refresh": "重新整理", "refreshNode": "重新整理節點", + "relativeTime": { + "daysAgo": "{count}天前", + "hoursAgo": "{count}小時前", + "minutesAgo": "{count}分鐘前", + "monthsAgo": "{count}個月前", + "now": "現在", + "weeksAgo": "{count}週前", + "yearsAgo": "{count}年前" + }, "releaseTitle": "{package} {version} 版本發佈", "reloadToApplyChanges": "重新載入以套用變更", "removeImage": "移除圖片", + "removeTag": "移除標籤", "removeVideo": "移除影片", "rename": "重新命名", "reportIssue": "送出回報", @@ -690,21 +905,31 @@ "resizeFromTopRight": "從右上角調整大小", "restart": "重新啟動", "resultsCount": "找到 {count} 筆結果", + "running": "執行中", "save": "儲存", "saving": "儲存中", + "scrollLeft": "向左捲動", + "scrollRight": "向右捲動", "search": "搜尋", "searchExtensions": "搜尋擴充套件", "searchFailedMessage": "找不到符合您搜尋的設定。請嘗試調整搜尋條件。", "searchKeybindings": "搜尋快捷鍵", "searchModels": "搜尋模型", "searchNodes": "搜尋節點", + "searchPlaceholder": "搜尋...", "searchSettings": "搜尋設定", "searchWorkflows": "搜尋工作流程", "seeTutorial": "查看教學", + "selectItemsToCopy": "請選擇要複製的項目", + "selectItemsToDelete": "請選擇要刪除的項目", + "selectItemsToDuplicate": "請選擇要複製的項目", + "selectItemsToRename": "請選擇要重新命名的項目", "selectedFile": "已選取的檔案", "setAsBackground": "設為背景", "settings": "設定", + "showLeftPanel": "顯示左側面板", "showReport": "顯示報告", + "showRightPanel": "顯示右側面板", "singleSelectDropdown": "單選下拉式選單", "sort": "排序", "source": "來源", @@ -712,12 +937,14 @@ "status": "狀態", "stopPlayback": "停止播放", "stopRecording": "停止錄音", + "submit": "提交", "success": "成功", "systemInfo": "系統資訊", "terminal": "終端機", "title": "標題", "triggerPhrase": "觸發詞", "unknownError": "未知錯誤", + "untitled": "未命名", "update": "更新", "updateAvailable": "有可用更新", "updateFrontend": "更新前端", @@ -725,6 +952,7 @@ "updating": "更新中", "upload": "上傳", "usageHint": "使用提示", + "use": "使用", "user": "使用者", "versionMismatchWarning": "版本相容性警告", "versionMismatchWarningMessage": "{warning}:{detail} 請參閱 https://docs.comfy.org/installation/update_comfyui#common-update-issues 以取得更新說明。", @@ -732,11 +960,10 @@ "videoPreview": "影片預覽 - 使用方向鍵在影片間導航", "viewImageOfTotal": "檢視第 {index} 張圖片(共 {total} 張)", "viewVideoOfTotal": "檢視第 {index} 個影片(共 {total} 個)", - "vitePreloadErrorMessage": "應用程式的新版本已發佈。您要重新載入嗎?\n如果不重新載入,應用程式的某些部分可能無法正常運作。\n您可以拒絕並先儲存進度,稍後再重新載入。", - "vitePreloadErrorTitle": "新版本可用", "volume": "音量", "warning": "警告", - "workflow": "工作流程" + "workflow": "工作流程", + "you": "你" }, "graphCanvasMenu": { "fitView": "適合視窗", @@ -758,12 +985,17 @@ "create": "建立群組節點", "enterName": "輸入名稱" }, + "help": { + "helpCenterMenu": "說明中心選單", + "recentReleases": "近期版本" + }, "helpCenter": { "clickToLearnMore": "點擊了解更多 →", "desktopUserGuide": "桌面版使用指南", "docs": "文件", + "feedback": "提供回饋", "github": "Github", - "helpFeedback": "幫助與回饋", + "help": "協助與支援", "loadingReleases": "正在載入版本資訊…", "managerExtension": "管理器擴充功能", "more": "更多…", @@ -772,6 +1004,12 @@ "recentReleases": "近期發布", "reinstall": "重新安裝", "updateAvailable": "有更新", + "updateComfyUI": "更新 ComfyUI", + "updateComfyUIFailed": "ComfyUI 更新失敗,請再試一次。", + "updateComfyUIStarted": "開始更新", + "updateComfyUIStartedDetail": "ComfyUI 更新已排入佇列,請稍候……", + "updateComfyUISuccess": "更新完成", + "updateComfyUISuccessDetail": "ComfyUI 已更新,正在重新啟動……", "whatsNew": "有什麼新功能?" }, "icon": { @@ -785,6 +1023,18 @@ "inbox": "收件匣", "star": "星號" }, + "imageCompare": { + "noImages": "沒有可比較的圖像" + }, + "imageCrop": { + "cropPreviewAlt": "裁切預覽", + "loading": "載入中...", + "noInputImage": "未連接輸入影像" + }, + "importFailed": { + "copyError": "複製錯誤", + "title": "匯入失敗" + }, "install": { "appDataLocationTooltip": "ComfyUI 的應用程式資料目錄。儲存:\n- 日誌\n- 伺服器設定", "appPathLocationTooltip": "ComfyUI 的應用程式資產目錄。儲存 ComfyUI 程式碼與資產", @@ -798,6 +1048,8 @@ "failedToSelectDirectory": "選擇目錄失敗", "gpu": "GPU", "gpuPicker": { + "amdDescription": "使用您的 AMD 顯示卡搭配 ROCm™ 加速以獲得最佳效能。", + "amdSubtitle": "AMD ROCm™", "appleMetalDescription": "利用您 Mac 的 GPU 提升速度並獲得更好的整體體驗", "cpuDescription": "當 GPU 加速不可用時,使用 CPU 模式以取得相容性", "cpuSubtitle": "CPU 模式", @@ -824,6 +1076,8 @@ "selectGpuDescription": "選擇您擁有的 GPU 類型" }, "helpImprove": "請協助改進 ComfyUI", + "insideAppInstallDir": "此資料夾位於 ComfyUI Desktop 應用程式包內,更新時會被刪除。請選擇安裝資料夾以外的目錄,例如 Documents/ComfyUI。", + "insideUpdaterCache": "此資料夾位於 ComfyUI 更新快取中,每次更新時都會清除。請選擇其他位置儲存您的資料。", "installLocation": "安裝位置", "installLocationDescription": "選擇 ComfyUI 使用者資料的目錄。Python 環境將安裝在所選位置。", "installLocationTooltip": "ComfyUI 的使用者資料目錄。儲存:\n- Python 環境\n- 模型\n- 自訂節點\n", @@ -898,6 +1152,16 @@ "issueReport": { "helpFix": "協助修復此問題" }, + "linearMode": { + "beta": "Beta - 提供回饋", + "downloadAll": "全部下載", + "dragAndDropImage": "拖曳圖片到此", + "graphMode": "圖形模式", + "linearMode": "簡易模式", + "rerun": "重新執行", + "reuseParameters": "重用參數", + "runCount": "執行次數:" + }, "load3d": { "applyingTexture": "正在套用材質貼圖...", "backgroundColor": "背景顏色", @@ -924,20 +1188,24 @@ "lineart": "線稿", "normal": "一般", "original": "原始", + "pointCloud": "點雲", "wireframe": "線框" }, "model": "模型", "openIn3DViewer": "在 3D 檢視器中開啟", + "panoramaMode": "全景", "previewOutput": "預覽輸出", "reloadingModel": "重新載入模型中...", "removeBackgroundImage": "移除背景圖片", "resizeNodeMatchOutput": "調整節點以符合輸出", "scene": "場景", "showGrid": "顯示格線", + "showSkeleton": "顯示骨架", "startRecording": "開始錄影", "stopRecording": "停止錄影", "switchCamera": "切換相機", "switchingMaterialMode": "正在切換材質模式...", + "tiledMode": "平鋪", "unsupportedFileType": "不支援的檔案類型(支援 .gltf、.glb、.obj、.fbx、.stl)", "upDirection": "上方方向", "upDirections": { @@ -958,6 +1226,11 @@ "title": "3D 檢視器(測試版)" } }, + "loadWorkflowWarning": { + "coreNodesFromVersion": "來自版本 {version} 的核心節點:", + "outdatedVersion": "此工作流程是以較新版本的 ComfyUI({version})建立的。部分節點可能無法正確運作。", + "outdatedVersionGeneric": "此工作流程是以較新版本的 ComfyUI 建立的。部分節點可能無法正確運作。" + }, "maintenance": { "None": "無", "OK": "正常", @@ -976,7 +1249,15 @@ "showManual": "顯示維護任務", "status": "狀態", "terminalDefaultMessage": "當您執行疑難排解指令時,任何輸出都會顯示在這裡。", - "title": "維護" + "title": "維護", + "unsafeMigration": { + "action": "請使用下方「基本路徑」維護任務,將 ComfyUI 移動到安全位置。", + "appInstallDir": "您的基本路徑位於 ComfyUI Desktop 應用程式包內。此資料夾在更新時可能會被刪除或覆蓋。請選擇安裝資料夾外的目錄,例如 Documents/ComfyUI。", + "generic": "您目前的 ComfyUI 基本路徑位於可能在更新時被刪除或修改的位置。為避免資料遺失,請將其移動到安全的資料夾。", + "oneDrive": "您的基本路徑位於 OneDrive,這可能導致同步問題及意外資料遺失。請選擇未由 OneDrive 管理的本機資料夾。", + "title": "偵測到不安全的安裝位置", + "updaterCache": "您的基本路徑位於 ComfyUI 更新快取中,每次更新時都會清除。請為您的資料選擇其他位置。" + } }, "manager": { "allMissingNodesInstalled": "所有缺少的節點已成功安裝", @@ -1077,6 +1358,8 @@ "totalNodes": "節點總數", "tryAgainLater": "請稍後再試。", "tryDifferentSearch": "請嘗試其他搜尋關鍵字。", + "tryUpdate": "嘗試更新", + "tryUpdateTooltip": "從儲存庫拉取最新變更。Nightly 版本可能有無法自動偵測的更新。", "uninstall": "解除安裝", "uninstallSelected": "解除安裝所選項目", "uninstalling": "正在解除安裝", @@ -1087,31 +1370,110 @@ "version": "版本" }, "maskEditor": { + "activateLayer": "啟用圖層", + "applyToWholeImage": "套用至整張圖片", + "baseImageLayer": "基礎影像圖層", + "baseLayerPreview": "基礎圖層預覽", + "black": "黑色", + "brushSettings": "筆刷設定", + "brushShape": "筆刷形狀", + "clear": "清除", + "clickToResetZoom": "點擊以重設縮放", + "colorSelectSettings": "顏色選擇設定", + "colorSelector": "顏色選擇器", + "fillOpacity": "填充不透明度", + "hardness": "硬度", + "imageLayer": "影像圖層", + "invert": "反轉", + "layers": "圖層", + "livePreview": "即時預覽", + "maskBlendingOptions": "遮罩混合選項", + "maskLayer": "遮罩圖層", + "maskOpacity": "遮罩不透明度", + "maskTolerance": "遮罩容差", + "method": "方法", + "mirrorHorizontal": "水平鏡像", + "mirrorVertical": "垂直鏡像", + "negative": "負片", + "opacity": "不透明度", + "paintBucketSettings": "油漆桶設定", + "paintLayer": "繪圖圖層", + "redo": "重做", + "resetToDefault": "重設為預設值", + "rotateLeft": "向左旋轉", + "rotateRight": "向右旋轉", + "selectionOpacity": "選取不透明度", + "smoothingPrecision": "平滑精度", + "stepSize": "步進大小", + "stopAtMask": "於遮罩處停止", + "thickness": "粗細", + "title": "遮罩編輯器", + "tolerance": "容差", + "undo": "復原", + "white": "白色" }, "mediaAsset": { + "actions": { + "copyJobId": "複製作業 ID", + "delete": "刪除", + "download": "下載", + "exportWorkflow": "匯出工作流程", + "insertAsNodeInWorkflow": "插入為工作流程節點", + "inspect": "檢查資產", + "more": "更多選項", + "moreOptions": "更多選項", + "openWorkflow": "在新分頁中以工作流程開啟", + "seeMoreOutputs": "查看更多輸出", + "zoom": "放大" + }, "assetDeletedSuccessfully": "資源刪除成功", "deleteAssetDescription": "此資源將被永久移除。", "deleteAssetTitle": "刪除此資源?", "deleteSelectedDescription": "{count} 個資源將被永久移除。", "deleteSelectedTitle": "刪除選取的資源?", "deletingImportedFilesCloudOnly": "僅雲端版本支援刪除匯入的檔案", + "failedToCreateNode": "建立節點失敗", "failedToDeleteAsset": "刪除資源失敗", + "failedToExportWorkflow": "匯出工作流程失敗", "jobIdToast": { "copied": "已複製", "error": "錯誤", "jobIdCopied": "工作 ID 已複製到剪貼簿", "jobIdCopyFailed": "複製工作 ID 失敗" }, + "noJobIdFound": "此資產找不到作業 ID", + "noWorkflowDataFound": "此資產找不到工作流程資料", + "nodeAddedToWorkflow": "{nodeType} 節點已加入工作流程", + "nodeTypeNotFound": "找不到節點類型 {nodeType}", "selection": { "assetsDeletedSuccessfully": "{count} 個資源刪除成功", "deleteSelected": "刪除", + "deleteSelectedAll": "全部刪除", "deselectAll": "取消全選", "downloadSelected": "下載", + "downloadSelectedAll": "全部下載", "downloadStarted": "正在下載 {count} 個檔案...", "downloadsStarted": "已開始下載 {count} 個檔案", + "exportWorkflowAll": "全部匯出工作流程", + "failedToAddNodes": "加入節點至工作流程失敗", "failedToDeleteAssets": "刪除選取資源失敗", - "selectedCount": "已選取資源:{count}" - } + "insertAllAssetsAsNodes": "將所有資產插入為節點", + "multipleSelectedAssets": "已選取多個資產", + "noWorkflowsFound": "所選資產中未找到工作流程資料", + "noWorkflowsToExport": "未找到可匯出的工作流程資料", + "nodesAddedToWorkflow": "已將 {count} 個節點加入工作流程", + "openWorkflowAll": "全部開啟工作流程", + "partialAddNodesSuccess": "成功加入 {succeeded} 個,失敗 {failed} 個", + "partialDeleteSuccess": "{succeeded} 刪除成功,{failed} 刪除失敗", + "partialWorkflowsExported": "成功匯出 {succeeded} 個,失敗 {failed} 個", + "partialWorkflowsOpened": "已開啟 {succeeded} 個工作流程,失敗 {failed} 個", + "selectedCount": "已選取資源:{count}", + "workflowsExported": "成功匯出 {count} 個工作流程", + "workflowsOpened": "已在新分頁開啟 {count} 個工作流程" + }, + "unsupportedFileType": "此載入節點不支援的檔案類型", + "workflowExportedSuccessfully": "工作流程匯出成功", + "workflowOpenedInNewTab": "工作流程已在新分頁開啟" }, "menu": { "autoQueue": "自動排隊", @@ -1119,11 +1481,13 @@ "batchCountTooltip": "工作流程產生應排入佇列的次數", "clear": "清除工作流程", "clipspace": "開啟 Clipspace", + "customNodesManager": "自訂節點管理器", "dark": "深色", "disabled": "已停用", "disabledTooltip": "工作流程將不會自動排入佇列", "execute": "執行", "help": "說明", + "helpAndFeedback": "說明與回饋", "hideMenu": "隱藏選單", "instant": "立即", "instantTooltip": "每次產生完成後,工作流程會立即排入佇列", @@ -1137,6 +1501,7 @@ "resetView": "重設畫布視圖", "run": "執行", "runWorkflow": "執行工作流程(Shift 於前方排隊)", + "runWorkflowDisabled": "工作流程包含不支援的節點(以紅色標示)。請移除後再執行工作流程。", "runWorkflowFront": "執行工作流程(前方排隊)", "settings": "設定", "showMenu": "顯示選單", @@ -1152,6 +1517,7 @@ "Canvas Performance": "畫布效能", "Canvas Toggle Lock": "切換畫布鎖定", "Check for Custom Node Updates": "檢查自訂節點更新", + "Check for Updates": "檢查更新", "Clear Pending Tasks": "清除待處理任務", "Clear Workflow": "清除工作流程", "Clipspace": "Clipspace", @@ -1168,13 +1534,14 @@ "Custom Nodes Manager": "自訂節點管理員", "Decrease Brush Size in MaskEditor": "在 MaskEditor 中減小筆刷大小", "Delete Selected Items": "刪除選取項目", + "Desktop User Guide": "桌面版使用指南", "Duplicate Current Workflow": "複製目前工作流程", "Edit": "編輯", "Edit Subgraph Widgets": "編輯子圖小工具", "Exit Subgraph": "退出子圖", "Experimental: Browse Model Assets": "實驗性:瀏覽模型資源", "Experimental: Enable AssetAPI": "實驗性:啟用 AssetAPI", - "Experimental: Enable Vue Nodes": "實驗性:啟用 Vue 節點", + "Experimental: Enable Nodes 2_0": "實驗性:啟用 Nodes 2.0", "Export": "匯出", "Export (API)": "匯出(API)", "File": "檔案", @@ -1186,12 +1553,15 @@ "Increase Brush Size in MaskEditor": "在 MaskEditor 中增大筆刷大小", "Install Missing Custom Nodes": "安裝缺少的自訂節點", "Interrupt": "中斷", + "Job History": "任務歷史", "Load Default Workflow": "載入預設工作流程", "Lock Canvas": "鎖定畫布", "Manage group nodes": "管理群組節點", "Manager": "管理員", "Manager Menu (Legacy)": "管理員選單(舊版)", "Minimap": "迷你地圖", + "Mirror Horizontal in MaskEditor": "在遮罩編輯器中水平鏡像", + "Mirror Vertical in MaskEditor": "在遮罩編輯器中垂直鏡像", "Model Library": "模型庫", "Move Selected Nodes Down": "選取節點下移", "Move Selected Nodes Left": "選取節點左移", @@ -1204,8 +1574,16 @@ "Node Links": "節點連結", "Open": "開啟", "Open 3D Viewer (Beta) for Selected Node": "為選取節點開啟 3D 檢視器(測試版)", + "Open Color Picker in MaskEditor": "在 MaskEditor 中開啟顏色選擇器", + "Open Custom Nodes Folder": "開啟自訂節點資料夾", + "Open DevTools": "開啟開發者工具", + "Open Inputs Folder": "開啟輸入資料夾", + "Open Logs Folder": "開啟日誌資料夾", "Open Mask Editor for Selected Node": "為選取節點開啟遮罩編輯器", + "Open Models Folder": "開啟模型資料夾", + "Open Outputs Folder": "開啟輸出資料夾", "Open Sign In Dialog": "開啟登入對話框", + "Open extra_model_paths_yaml": "開啟 extra_model_paths.yaml", "Pin/Unpin Selected Items": "釘選/取消釘選選取項目", "Pin/Unpin Selected Nodes": "釘選/取消釘選選取節點", "Previous Opened Workflow": "上一個已開啟的工作流程", @@ -1213,10 +1591,16 @@ "Queue Prompt": "加入提示至佇列", "Queue Prompt (Front)": "將提示加入佇列前端", "Queue Selected Output Nodes": "將選取的輸出節點加入佇列", + "Quit": "結束", "Redo": "重做", "Refresh Node Definitions": "重新整理節點定義", + "Reinstall": "重新安裝", + "Rename": "重新命名", "Reset View": "重設視圖", "Resize Selected Nodes": "調整選取節點大小", + "Restart": "重新啟動", + "Rotate Left in MaskEditor": "在遮罩編輯器中向左旋轉", + "Rotate Right in MaskEditor": "在遮罩編輯器中向右旋轉", "Save": "儲存", "Save As": "另存新檔", "Show Keybindings Dialog": "顯示快捷鍵對話框", @@ -1225,12 +1609,13 @@ "Sign Out": "登出", "Toggle Essential Bottom Panel": "切換基本底部面板", "Toggle Logs Bottom Panel": "切換日誌底部面板", + "Toggle Queue Panel V2": "切換佇列面板 V2", "Toggle Search Box": "切換搜尋框", + "Toggle Simple Mode": "切換簡易模式", "Toggle Terminal Bottom Panel": "切換終端機底部面板", "Toggle Theme (Dark/Light)": "切換主題(深色/淺色)", "Toggle View Controls Bottom Panel": "切換檢視控制底部面板", "Toggle promotion of hovered widget": "切換懸停小工具提升狀態", - "Toggle the Custom Nodes Manager Progress Bar": "切換自訂節點管理器進度條", "Undo": "復原", "Ungroup selected group nodes": "取消群組選取的群組節點", "Unload Models": "卸載模型", @@ -1255,30 +1640,56 @@ "missingModels": "缺少模型", "missingModelsMessage": "載入圖形時,找不到以下模型" }, + "missingNodes": { + "cloud": { + "description": "此工作流程使用了雲端版本尚未支援的自訂節點。", + "gotIt": "知道了", + "learnMore": "了解更多", + "priorityMessage": "我們已自動標記這些節點,以便優先新增支援。", + "replacementInstruction": "在此期間,請將這些節點(畫布上以紅色標示)替換為支援的節點,或嘗試其他工作流程。", + "title": "這些節點尚未在 Comfy Cloud 上提供" + }, + "oss": { + "description": "此工作流程使用了你尚未安裝的自訂節點。", + "replacementInstruction": "請安裝這些節點以執行此工作流程,或以已安裝的替代節點取代。缺少的節點會在畫布上以紅色標示。", + "title": "此工作流程有缺少的節點" + } + }, + "nightly": { + "badge": { + "label": "預覽版本", + "tooltip": "您正在使用 ComfyUI 的夜間版本。請使用反饋按鈕分享您對這些功能的看法。" + } + }, "nodeCategories": { + "": "", "3d": "3D", "3d_models": "3D 模型", "BFL": "BFL", + "Bria": "Bria", "ByteDance": "字節跳動", "Gemini": "雙子星", "Ideogram": "Ideogram", "Kling": "Kling", "LTXV": "LTXV", "Luma": "Luma", + "Meshy": "Meshy", "MiniMax": "MiniMax", "Moonvalley Marey": "月谷馬雷", "OpenAI": "OpenAI", - "Pika": "Pika", "PixVerse": "PixVerse", "Recraft": "Recraft", "Rodin": "羅丹", "Runway": "跑道", "Sora": "蒼穹", "Stability AI": "Stability AI", + "Tencent": "Tencent", + "Topaz": "Topaz", "Tripo": "三重奏", "Veo": "Veo", "Vidu": "維度", "Wan": "Wan", + "WaveSpeed": "WaveSpeed", "_for_testing": "_for_testing", "advanced": "進階", "animation": "動畫", @@ -1299,6 +1710,7 @@ "controlnet": "ControlNet", "create": "建立", "custom_sampling": "自訂取樣", + "dataset": "資料集", "debug": "除錯", "deprecated": "已棄用", "edit_models": "編輯模型", @@ -1310,8 +1722,10 @@ "image": "影像", "inpaint": "修補", "instructpix2pix": "instructpix2pix", + "kandinsky5": "kandinsky5", "latent": "潛空間", "loaders": "載入器", + "logic": "邏輯", "lotus": "lotus", "ltxv": "ltxv", "mask": "遮罩", @@ -1345,7 +1759,15 @@ "upscaling": "放大", "utils": "工具", "video": "影片", - "video_models": "影片模型" + "video_models": "影片模型", + "zimage": "zimage" + }, + "nodeErrors": { + "content": "節點內容錯誤", + "header": "節點標頭錯誤", + "render": "節點渲染錯誤", + "slots": "節點插槽錯誤", + "widgets": "節點元件錯誤" }, "nodeHelpPage": { "documentationPage": "說明文件頁面", @@ -1362,6 +1784,7 @@ "notSupported": { "continue": "繼續", "continueTooltip": "我確定我的裝置受支援", + "illustrationAlt": "難過的女孩插圖", "learnMore": "了解更多", "message": "僅支援以下裝置:", "reportIssue": "回報問題", @@ -1371,12 +1794,136 @@ }, "title": "您的裝置不受支援" }, + "progressToast": { + "allDownloadsCompleted": "所有下載已完成", + "downloadingModel": "正在下載模型...", + "downloadsFailed": "{count} 個下載失敗 | {count} 個下載失敗 | {count} 個下載失敗", + "failed": "失敗", + "filter": { + "all": "全部", + "completed": "已完成", + "failed": "失敗" + }, + "finished": "已完成", + "importingModels": "正在匯入模型", + "noImportsInQueue": "佇列中沒有 {filter}", + "pending": "處理中", + "progressCount": "{completed} / {total}" + }, + "queue": { + "completedIn": "於 {duration} 完成", + "inQueue": "排隊中...", + "initializingAlmostReady": "初始化中 - 即將完成", + "jobAddedToQueue": "工作已加入佇列", + "jobDetails": { + "computeHoursUsed": "運算時數", + "errorMessage": "錯誤訊息", + "estimatedFinishIn": "預計完成於", + "estimatedStartIn": "預計開始於", + "eta": { + "minutes": "~{count} 分鐘 | ~{count} 分鐘", + "minutesRange": "~{lo}-{hi} 分鐘", + "seconds": "~{count} 秒 | ~{count} 秒", + "secondsRange": "~{lo}-{hi} 秒" + }, + "failedAfter": "失敗於", + "generatedOn": "生成於", + "header": "工作詳情", + "jobId": "工作 ID", + "queuePosition": "佇列位置", + "queuePositionValue": "~{count} 個工作在您之前 | ~{count} 個工作在您之前", + "queuedAt": "排隊時間", + "report": "回報", + "timeElapsed": "已過時間", + "totalGenerationTime": "總生成時間", + "workflow": "工作流程" + }, + "jobHistory": "工作歷史", + "jobList": { + "sortComputeHoursUsed": "運算時數(最多優先)", + "sortMostRecent": "最新", + "sortTotalGenerationTime": "總生成時間(最長優先)", + "undated": "未標日期" + }, + "jobMenu": { + "addToCurrentWorkflow": "加入目前工作流程", + "cancelJob": "取消工作", + "copyErrorMessage": "複製錯誤訊息", + "copyJobId": "複製工作 ID", + "delete": "刪除", + "deleteAsset": "刪除資產", + "download": "下載", + "exportWorkflow": "匯出工作流程", + "inspectAsset": "檢視資產", + "openAsWorkflowNewTab": "以工作流程於新分頁開啟", + "openWorkflowNewTab": "於新分頁開啟工作流程", + "removeJob": "移除工作", + "reportError": "回報錯誤" + }, + "toggleJobHistory": "切換工作歷史" + }, "releaseToast": { + "description": "查看本次更新的最新改進與功能。", "newVersionAvailable": "有新版本可用!", "skip": "跳過", "update": "更新", "whatsNew": "有什麼新功能?" }, + "rightSidePanel": { + "addFavorite": "收藏", + "advancedInputs": "進階輸入", + "bypass": "繞過", + "color": "節點顏色", + "fallbackGroupTitle": "群組", + "fallbackNodeTitle": "節點", + "favorites": "已收藏輸入", + "favoritesNone": "尚無已收藏輸入", + "favoritesNoneDesc": "你收藏的輸入會顯示在這裡", + "favoritesNoneTooltip": "將元件加星標,快速存取無需選擇節點", + "globalSettings": { + "canvas": "畫布", + "connectionLinks": "連接線", + "gridSpacing": "網格間距", + "linkShape": "連線形狀", + "nodes": "節點", + "nodes2": "節點 2.0", + "searchPlaceholder": "搜尋快速設定...", + "showAdvanced": "顯示進階參數", + "showAdvancedTooltip": "這是一個重要設定,設為 TRUE 時會顯示所有節點的進階參數", + "showConnectedLinks": "顯示已連接線", + "showInfoBadges": "顯示資訊徽章", + "showToolbox": "選取時顯示工具箱", + "snapNodesToGrid": "節點貼齊網格", + "title": "全域設定", + "viewAllSettings": "檢視所有設定" + }, + "groupSettings": "群組設定", + "groups": "群組", + "hideAdvancedInputsButton": "隱藏進階輸入", + "hideInput": "隱藏輸入", + "info": "資訊", + "inputs": "輸入", + "inputsNone": "無輸入", + "inputsNoneTooltip": "此節點沒有輸入", + "locateNode": "在畫布上定位節點", + "mute": "靜音", + "noSelection": "請選擇節點以檢視其屬性與資訊。", + "nodeState": "節點狀態", + "nodes": "節點", + "nodesNoneDesc": "無節點", + "noneSearchDesc": "沒有符合搜尋的項目", + "normal": "一般", + "parameters": "參數", + "pinned": "已釘選", + "properties": "屬性", + "removeFavorite": "取消收藏", + "settings": "設定", + "showAdvancedInputsButton": "顯示進階輸入", + "showInput": "顯示輸入", + "title": "未選取節點 | 已選取 1 個節點 | 已選取 {count} 個節點", + "togglePanel": "切換屬性面板", + "workflowOverview": "工作流程總覽" + }, "selectionToolbox": { "Bypass Group Nodes": "繞過群組節點", "Set Group Nodes to Always": "將群組節點設為總是", @@ -1389,6 +1936,8 @@ "serverConfig": { "modifiedConfigs": "您已修改以下伺服器設定。請重新啟動以套用變更。", "restart": "重新啟動", + "restartRequiredToastDetail": "請重新啟動應用程式以套用伺服器設定變更。", + "restartRequiredToastSummary": "需要重新啟動", "revertChanges": "還原變更" }, "serverConfigCategories": { @@ -1457,6 +2006,10 @@ "enable-cors-header": { "name": "啟用 CORS 標頭:使用「*」允許所有來源或指定網域" }, + "enable-manager-legacy-ui": { + "name": "使用舊版 Manager 介面", + "tooltip": "使用舊版 ComfyUI-Manager 介面而非新版介面。" + }, "fast": { "name": "啟用部分未經測試且可能降低品質的最佳化。" }, @@ -1558,6 +2111,7 @@ "CustomColorPalettes": "自訂色彩調色盤", "DevMode": "開發者模式", "EditTokenWeight": "編輯權重", + "Execution": "執行", "Extension": "擴充功能", "General": "一般", "Graph": "圖形", @@ -1572,12 +2126,14 @@ "Mask Editor": "遮罩編輯器", "Menu": "選單", "ModelLibrary": "模型庫", - "NewEditor": "新編輯器", "Node": "節點", "Node Search Box": "節點搜尋框", "Node Widget": "節點元件", "NodeLibrary": "節點庫", + "Nodes 2_0": "Nodes 2.0", "Notification Preferences": "通知偏好設定", + "Other": "其他", + "PLY": "PLY", "PlanCredits": "方案與點數", "Pointer": "指標", "Queue": "佇列", @@ -1596,7 +2152,8 @@ "Vue Nodes": "Vue 節點", "VueNodes": "Vue 節點", "Window": "視窗", - "Workflow": "工作流程" + "Workflow": "工作流程", + "Workspace": "工作區" }, "shape": { "CARD": "卡片", @@ -1622,11 +2179,14 @@ "viewControls": "檢視控制" }, "sideToolbar": { + "activeJobStatus": "進行中作業:{status}", "assets": "資源", "backToAssets": "返回所有資源", "browseTemplates": "瀏覽範例模板", "downloads": "下載", + "generatedAssetsHeader": "已產生資產", "helpCenter": "說明中心", + "importedAssetsHeader": "已匯入資產", "labels": { "assets": "資源", "console": "控制台", @@ -1669,6 +2229,47 @@ }, "openWorkflow": "在本機檔案系統中開啟工作流程", "queue": "佇列", + "queueProgressOverlay": { + "activeJobs": "{count} 個執行中作業", + "activeJobsShort": "{count} 個進行中", + "activeJobsSuffix": "執行中作業", + "cancelJobTooltip": "取消作業", + "clearHistory": "清除作業佇列歷史", + "clearHistoryDialogAssetsNote": "這些作業產生的資產不會被刪除,隨時可從資產面板檢視。", + "clearHistoryDialogDescription": "下方所有已完成或失敗的作業將會從此作業佇列面板中移除。", + "clearHistoryDialogTitle": "要清除作業佇列歷史嗎?", + "clearQueueTooltip": "清除佇列", + "clearQueued": "清除已排入佇列", + "colonPercent": ":{percent}", + "currentNode": "目前節點:", + "expandCollapsedQueue": "展開作業佇列", + "filterAllWorkflows": "所有工作流程", + "filterBy": "篩選依據", + "filterCurrentWorkflow": "目前工作流程", + "filterJobs": "篩選作業", + "interruptAll": "中斷所有執行中作業", + "jobQueue": "作業佇列", + "jobsCompleted": "{count} 個作業已完成", + "jobsFailed": "{count} 個作業失敗", + "moreOptions": "更多選項", + "noActiveJobs": "沒有執行中作業", + "preview": "預覽", + "queuedSuffix": "已排入佇列", + "running": "執行中", + "showAssets": "顯示資產", + "showAssetsPanel": "顯示資產面板", + "sortBy": "排序依據", + "sortJobs": "排序作業", + "stubClipTextEncode": "CLIP 文字編碼:", + "title": "佇列進度", + "total": "總計:{percent}", + "viewAllJobs": "檢視所有作業", + "viewGrid": "網格檢視", + "viewJobHistory": "檢視作業歷史", + "viewList": "清單檢視" + }, + "searchAssets": "搜尋資產", + "sidebar": "側邊欄", "templates": "範本", "themeToggle": "切換主題", "workflowTab": { @@ -1711,24 +2312,58 @@ "subscription": { "addApiCredits": "新增 API 點數", "addCredits": "新增點數", + "addCreditsLabel": "隨時可儲值點數", "benefits": { "benefit1": "合作節點每月點數 — 需要時可隨時加值", "benefit2": "每項任務最多運行 30 分鐘" }, "beta": "測試版", + "billedMonthly": "每月收費", + "billedYearly": "每年收費 {total}", + "billingComingSoon": { + "message": "團隊計費功能即將推出。屆時你可以為你的工作區訂閱方案,並依照每位成員計價。請持續關注最新消息。", + "title": "即將推出" + }, + "cancelSubscription": "取消訂閱", + "changeTo": "切換至 {plan}", "comfyCloud": "Comfy Cloud", + "comfyCloudLogo": "Comfy Cloud 標誌", + "contactOwnerToSubscribe": "請聯絡工作區擁有者以訂閱", + "contactUs": "聯絡我們", + "creditsRemainingThisMonth": "本月剩餘點數", + "creditsRemainingThisYear": "本年剩餘點數", + "creditsYouveAdded": "您已儲值的點數", + "currentPlan": "目前方案", + "customLoRAsLabel": "匯入您自己的 LoRAs", + "description": "選擇最適合您的方案", "expiresDate": "將於 {date} 到期", + "gpuLabel": "RTX 6000 Pro(96GB VRAM)", + "haveQuestions": "有疑問或想了解企業方案?", "invoiceHistory": "發票記錄", "learnMore": "了解更多", + "managePayment": "管理付款", + "managePlan": "管理方案", "manageSubscription": "管理訂閱", + "maxDuration": { + "creator": "30 分鐘", + "founder": "30 分鐘", + "pro": "1 小時", + "standard": "30 分鐘" + }, + "maxDurationLabel": "每次工作流程最長執行時間", "messageSupport": "聯繫客服", + "monthly": "每月", "monthlyBonusDescription": "每月點數獎勵", + "monthlyCreditsInfo": "這些點數每月重置,不可累積", + "monthlyCreditsLabel": "每月點數", "monthlyCreditsRollover": "這些點數將結轉至下個月", + "mostPopular": "最受歡迎", "nextBillingCycle": "下個計費週期", "partnerNodesBalance": "「合作夥伴節點」點數餘額", "partnerNodesCredits": "合作節點點數", "partnerNodesDescription": "用於執行商業/專有模型", "perMonth": "美元 / 月", + "plansAndPricing": "方案與價格", "prepaidCreditsInfo": "單獨購買且不會過期的點數", "prepaidDescription": "預付點數", "renewsDate": "將於 {date} 續訂", @@ -1738,14 +2373,46 @@ "waitingForSubscription": "請在新分頁中完成訂閱。完成後我們會自動偵測!" }, "subscribeNow": "立即訂閱", + "subscribeTo": "訂閱 {plan}", "subscribeToComfyCloud": "訂閱 Comfy Cloud", "subscribeToRun": "訂閱", "subscribeToRunFull": "訂閱運行方案", + "subscriptionRequiredMessage": "會員需訂閱才能在雲端執行工作流程", + "tierNameYearly": "{name} 年度方案", + "tiers": { + "creator": { + "name": "創作者版" + }, + "founder": { + "name": "創始版" + }, + "pro": { + "name": "專業版" + }, + "standard": { + "name": "標準版" + } + }, "title": "訂閱方案", "titleUnsubscribed": "訂閱 Comfy Cloud", "totalCredits": "總點數", + "upgrade": "升級", + "upgradePlan": "升級方案", + "upgradeTo": "升級至 {plan}", + "usdPerMonth": "美元/月", + "videoEstimateExplanation": "此估算以 Wan 2.2 圖轉影範本的預設設定(5 秒、640x640、16fps、4 步採樣)為基礎。", + "videoEstimateHelp": "查看更多此範本細節", + "videoEstimateLabel": "以 Wan 2.2 圖轉影範本約可產生的 5 秒影片數量", + "videoEstimateTryTemplate": "試用此範本", + "videoTemplateBasedCredits": "以 Wan 2.2 圖轉影產生的影片", + "viewEnterprise": "查看企業方案", "viewMoreDetails": "查看更多詳情", + "viewMoreDetailsPlans": "查看更多方案與價格細節", "viewUsageHistory": "檢視使用記錄", + "workspaceNotSubscribed": "此工作區尚未訂閱", + "yearly": "每年", + "yearlyCreditsLabel": "年度總點數", + "yearlyDiscount": "八折優惠", "yourPlanIncludes": "您的方案包含:" }, "tabMenu": { @@ -1757,8 +2424,14 @@ "duplicateTab": "複製分頁", "removeFromBookmarks": "從書籤移除" }, + "templateWidgets": { + "sort": { + "searchPlaceholder": "搜尋……" + } + }, "templateWorkflows": { "activeFilters": "篩選條件:", + "allTemplates": "所有範本", "categories": "分類", "category": { "3D": "3D", @@ -1785,6 +2458,7 @@ "error": { "templateNotFound": "找不到範本 \"{templateName}\"" }, + "licenseFilter": "授權", "loading": "正在載入範本...", "loadingMore": "載入更多範本...", "modelFilter": "模型篩選", @@ -1801,12 +2475,14 @@ "default": "預設", "modelSizeLowToHigh": "模型大小 (低到高)", "newest": "最新", + "popular": "熱門", "recommended": "推薦", "searchPlaceholder": "搜尋...", "vramLowToHigh": "VRAM 使用量 (低到高)" }, "sorting": "排序依據", "title": "從範本開始", + "useCaseFilter": "任務", "useCasesSelected": "{count} 個使用案例" }, "toastMessages": { @@ -1835,9 +2511,22 @@ "failedToLoadModel": "無法載入 3D 模型", "failedToPurchaseCredits": "購買點數失敗:{error}", "failedToQueue": "加入佇列失敗", + "failedToToggleCamera": "切換相機失敗", + "failedToToggleGrid": "切換格線失敗", + "failedToUpdateBackgroundColor": "更新背景顏色失敗", + "failedToUpdateBackgroundImage": "更新背景圖像失敗", + "failedToUpdateBackgroundRenderMode": "更新背景渲染模式為 {mode} 失敗", + "failedToUpdateEdgeThreshold": "更新邊緣閾值失敗", + "failedToUpdateFOV": "更新視野失敗", + "failedToUpdateLightIntensity": "更新光源強度失敗", + "failedToUpdateMaterialMode": "更新材質模式失敗", + "failedToUpdateUpDirection": "更新上方方向失敗", + "failedToUploadBackgroundImage": "上傳背景圖像失敗", "fileLoadError": "無法在 {fileName} 中找到工作流程", + "fileTooLarge": "檔案過大({size} MB)。最大支援大小為 {maxSize} MB", "fileUploadFailed": "檔案上傳失敗", "interrupted": "執行已被中斷", + "legacyMaskEditorDeprecated": "舊版遮罩編輯器即將淘汰並很快移除。", "migrateToLitegraphReroute": "重導節點將於未來版本移除。點擊以遷移至 litegraph 原生重導。", "modelLoadedSuccessfully": "3D 模型載入成功", "no3dScene": "沒有 3D 場景可套用材質", @@ -1864,12 +2553,14 @@ "selectUser": "選擇用戶" }, "userSettings": { + "accountSettings": "帳戶設定", "email": "電子郵件", "name": "名稱", "notSet": "未設定", "provider": "登入提供者", "title": "使用者設定", - "updatePassword": "更新密碼" + "updatePassword": "更新密碼", + "workspaceSettings": "工作區設定" }, "validation": { "descriptionRequired": "說明為必填項目", @@ -1898,22 +2589,32 @@ "updateFrontend": "更新前端" }, "vueNodesBanner": { - "message": "節點有了全新的外觀和感覺", + "desc": "– 更靈活的工作流程、強大的新元件、專為擴充性打造", + "title": "全新 Nodes 2.0 上線", "tryItOut": "試試看" }, "vueNodesMigration": { "button": "開啟設定", "message": "偏好經典節點設計?" }, + "vueNodesMigrationMainMenu": { + "message": "隨時可從主選單切換回 Nodes 2.0。" + }, "welcome": { "getStarted": "開始使用", "title": "歡迎使用 ComfyUI" }, "whatsNewPopup": { + "later": "稍後", "learnMore": "了解更多", "noReleaseNotes": "沒有可用的發行說明。" }, + "widgetFileUpload": { + "browseFiles": "瀏覽檔案", + "dropPrompt": "拖放您的檔案或" + }, "widgets": { + "node2only": "僅限 Node 2.0", "selectModel": "選擇模型", "uploadSelect": { "placeholder": "選擇...", @@ -1922,6 +2623,26 @@ "placeholderModel": "選擇模型...", "placeholderUnknown": "選擇媒體...", "placeholderVideo": "選擇影片..." + }, + "valueControl": { + "decrement": "減少數值", + "decrementDesc": "數值減 1 或選擇上一個選項", + "editSettings": "編輯控制設定", + "fixed": "固定數值", + "fixedDesc": "數值保持不變", + "header": { + "after": "之後", + "before": "之前", + "postfix": "執行工作流程:", + "prefix": "自動更新數值" + }, + "increment": "增加數值", + "incrementDesc": "數值加 1 或選擇下一個選項", + "linkToGlobal": "連結到", + "linkToGlobalDesc": "連結至全域數值控制設定的唯一數值", + "linkToGlobalSeed": "全域數值", + "randomize": "隨機數值", + "randomizeDesc": "每次生成後隨機打亂數值" } }, "workflowService": { @@ -1929,6 +2650,146 @@ "exportWorkflow": "匯出工作流程", "saveWorkflow": "儲存工作流程" }, + "workspace": { + "addedToWorkspace": "你已被加入 {workspaceName}", + "inviteAccepted": "已接受邀請", + "inviteFailed": "接受邀請失敗", + "unsavedChanges": { + "message": "您有未儲存的變更。是否要捨棄這些變更並切換工作區?", + "title": "未儲存的變更" + } + }, + "workspaceAuth": { + "errors": { + "accessDenied": "您沒有存取此工作區的權限", + "invalidFirebaseToken": "驗證失敗。請重新登入。", + "notAuthenticated": "您必須登入才能存取工作區", + "tokenExchangeFailed": "與工作區驗證失敗:{error}", + "workspaceNotFound": "找不到工作區" + } + }, + "workspacePanel": { + "createWorkspaceDialog": { + "create": "建立", + "message": "工作區可讓成員共用點數池。建立後您將成為擁有者。", + "nameLabel": "工作區名稱*", + "namePlaceholder": "輸入工作區名稱", + "title": "建立新工作區" + }, + "dashboard": { + "placeholder": "儀表板工作區設定" + }, + "deleteDialog": { + "message": "任何未使用的點數或未儲存的資產都將遺失。此操作無法復原。", + "messageWithName": "刪除「{name}」?任何未使用的點數或未儲存的資產都將遺失。此操作無法復原。", + "title": "確定要刪除此工作區?" + }, + "editWorkspaceDialog": { + "nameLabel": "工作區名稱", + "save": "儲存", + "title": "編輯工作區詳細資料" + }, + "invite": "邀請", + "inviteLimitReached": "你已達到最多 50 位成員的上限", + "inviteMember": "邀請成員", + "inviteMemberDialog": { + "createLink": "建立連結", + "linkCopied": "已複製", + "linkCopyFailed": "複製連結失敗", + "linkStep": { + "copyLink": "複製連結", + "done": "完成", + "message": "請確認他們的帳號使用此電子郵件。", + "title": "將此連結發送給對方" + }, + "message": "建立可分享的邀請連結並發送給對方", + "placeholder": "輸入對方的電子郵件", + "title": "邀請他人加入此工作區" + }, + "leaveDialog": { + "leave": "離開", + "message": "除非聯絡工作區擁有者,否則您將無法再次加入。", + "title": "確定要離開此工作區?" + }, + "members": { + "actions": { + "copyLink": "複製邀請連結", + "removeMember": "移除成員", + "revokeInvite": "撤銷邀請" + }, + "columns": { + "expiryDate": "到期日", + "inviteDate": "邀請日期", + "joinDate": "加入日期" + }, + "createNewWorkspace": "建立新工作區。", + "membersCount": "{count}/50 位成員", + "noInvites": "沒有待處理邀請", + "noMembers": "沒有成員", + "pendingInvitesCount": "{count} 個待處理邀請", + "personalWorkspaceMessage": "你目前無法邀請其他成員加入你的個人工作區。若要新增成員,請", + "tabs": { + "active": "已啟用", + "pendingCount": "待處理 ({count})" + } + }, + "menu": { + "deleteWorkspace": "刪除工作區", + "deleteWorkspaceDisabledTooltip": "請先取消工作區的有效訂閱", + "editWorkspace": "編輯工作區詳細資料", + "leaveWorkspace": "離開工作區" + }, + "removeMemberDialog": { + "error": "移除成員失敗", + "message": "此成員將會從你的工作區中移除。他們已使用的點數不會退還。", + "remove": "移除成員", + "success": "已移除成員", + "title": "要移除此成員嗎?" + }, + "revokeInviteDialog": { + "message": "此人將無法再加入你的工作區,他們的邀請連結也會失效。", + "revoke": "取消邀請", + "title": "要取消邀請此人嗎?" + }, + "tabs": { + "dashboard": "儀表板", + "membersCount": "成員 ({count})", + "planCredits": "方案與點數" + }, + "toast": { + "failedToCreateWorkspace": "建立工作區失敗", + "failedToDeleteWorkspace": "刪除工作區失敗", + "failedToFetchWorkspaces": "載入工作區失敗", + "failedToLeaveWorkspace": "離開工作區失敗", + "failedToUpdateWorkspace": "更新工作區失敗", + "workspaceCreated": { + "message": "訂閱方案、邀請團隊成員並開始協作。", + "subscribe": "訂閱", + "title": "已建立工作區" + }, + "workspaceDeleted": { + "message": "該工作區已被永久刪除。", + "title": "已刪除工作區" + }, + "workspaceLeft": { + "message": "你已離開該工作區。", + "title": "已離開工作區" + }, + "workspaceUpdated": { + "message": "工作區詳細資料已儲存。", + "title": "工作區已更新" + } + } + }, + "workspaceSwitcher": { + "createWorkspace": "建立新工作區", + "maxWorkspacesReached": "您最多只能擁有 10 個工作區。請刪除一個以建立新工作區。", + "personal": "個人", + "roleMember": "成員", + "roleOwner": "擁有者", + "subscribe": "訂閱", + "switchWorkspace": "切換工作區" + }, "zoomControls": { "hideMinimap": "隱藏小地圖", "label": "縮放控制", diff --git a/src/locales/zh-TW/nodeDefs.json b/src/locales/zh-TW/nodeDefs.json index 6a3de3b2b..266ce77dd 100644 --- a/src/locales/zh-TW/nodeDefs.json +++ b/src/locales/zh-TW/nodeDefs.json @@ -39,6 +39,87 @@ "sigmas": { "name": "sigmas" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AddTextPrefix": { + "display_name": "新增文字前綴", + "inputs": { + "prefix": { + "name": "前綴", + "tooltip": "要新增的前綴。" + }, + "texts": { + "name": "文字", + "tooltip": "要處理的文字。" + } + }, + "outputs": { + "0": { + "name": "文字", + "tooltip": "已處理的文字" + } + } + }, + "AddTextSuffix": { + "display_name": "新增文字後綴", + "inputs": { + "suffix": { + "name": "後綴", + "tooltip": "要新增的後綴。" + }, + "texts": { + "name": "文字", + "tooltip": "要處理的文字。" + } + }, + "outputs": { + "0": { + "name": "文字", + "tooltip": "已處理的文字" + } + } + }, + "AdjustBrightness": { + "display_name": "調整亮度", + "inputs": { + "factor": { + "name": "亮度係數", + "tooltip": "亮度係數。1.0 = 無變化,<1.0 = 變暗,>1.0 = 變亮。" + }, + "images": { + "name": "影像", + "tooltip": "要處理的影像。" + } + }, + "outputs": { + "0": { + "name": "影像", + "tooltip": "已處理的影像" + } + } + }, + "AdjustContrast": { + "display_name": "調整對比度", + "inputs": { + "factor": { + "name": "對比度係數", + "tooltip": "對比度係數。1.0 = 無變化,<1.0 = 對比度降低,>1.0 = 對比度提高。" + }, + "images": { + "name": "影像", + "tooltip": "要處理的影像。" + } + }, + "outputs": { + "0": { + "name": "影像", + "tooltip": "已處理的影像" + } } }, "AlignYourStepsScheduler": { @@ -70,6 +151,11 @@ "name": "volume", "tooltip": "以分貝 (dB) 為單位的音量調整。0 = 無變化,+6 = 兩倍,-6 = 一半,依此類推" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "AudioConcat": { @@ -86,6 +172,11 @@ "name": "direction", "tooltip": "將 audio2 附加在 audio1 之後或之前。" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "AudioEncoderEncode": { @@ -131,6 +222,11 @@ "name": "merge_method", "tooltip": "用於合併音訊波形的方法。" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BasicGuider": { @@ -142,6 +238,11 @@ "model": { "name": "model" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BasicScheduler": { @@ -159,6 +260,50 @@ "steps": { "name": "步驟數" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchImagesNode": { + "display_name": "批次影像", + "inputs": { + "images": { + "name": "影像" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchLatentsNode": { + "display_name": "批次 latent", + "inputs": { + "latents": { + "name": "latent" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchMasksNode": { + "display_name": "批次遮罩", + "inputs": { + "masks": { + "name": "遮罩" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BetaSamplingScheduler": { @@ -176,6 +321,73 @@ "steps": { "name": "步驟數" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BriaImageEditNode": { + "description": "使用 Bria 最新模型編輯圖像", + "display_name": "Bria 圖像編輯", + "inputs": { + "control_after_generate": { + "name": "生成後控制" + }, + "guidance_scale": { + "name": "指引強度", + "tooltip": "數值越高,圖像越貼合提示詞。" + }, + "image": { + "name": "圖像" + }, + "mask": { + "name": "遮罩", + "tooltip": "若未設定,編輯將套用至整張圖像。" + }, + "model": { + "name": "model" + }, + "moderation": { + "name": "審核", + "tooltip": "審核設定" + }, + "moderation_prompt_content_moderation": { + "name": "提示詞內容審核" + }, + "moderation_visual_input_moderation": { + "name": "輸入圖像審核" + }, + "moderation_visual_output_moderation": { + "name": "輸出圖像審核" + }, + "negative_prompt": { + "name": "負面提示詞" + }, + "prompt": { + "name": "提示詞", + "tooltip": "編輯圖像的指令" + }, + "seed": { + "name": "種子" + }, + "steps": { + "name": "步數" + }, + "structured_prompt": { + "name": "結構化提示詞", + "tooltip": "包含結構化編輯提示的 JSON 格式字串。若需精確、程式化控制,請使用此項取代一般提示詞。" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "結構化提示詞", + "tooltip": null + } } }, "ByteDanceFirstLastFrameNode": { @@ -201,13 +413,16 @@ "name": "首幀", "tooltip": "用於影片的首幀。" }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "此參數僅適用於 seedance-1-5-pro,其他模型將會忽略。" + }, "last_frame": { "name": "尾幀", "tooltip": "用於影片的尾幀。" }, "model": { - "name": "模型", - "tooltip": "模型名稱" + "name": "模型" }, "prompt": { "name": "提示詞", @@ -248,8 +463,7 @@ "tooltip": "要編輯的基礎圖片" }, "model": { - "name": "模型", - "tooltip": "模型名稱" + "name": "模型" }, "prompt": { "name": "提示詞", @@ -286,8 +500,7 @@ "tooltip": "圖像的自訂高度。僅在 `size_preset` 設為 `Custom` 時生效" }, "model": { - "name": "model", - "tooltip": "模型名稱" + "name": "model" }, "prompt": { "name": "prompt", @@ -336,8 +549,7 @@ "tooltip": "一至四張圖片。" }, "model": { - "name": "model", - "tooltip": "模型名稱" + "name": "model" }, "prompt": { "name": "prompt", @@ -381,13 +593,16 @@ "name": "duration", "tooltip": "輸出影片的持續時間(以秒為單位)。" }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "此參數僅適用於 seedance-1-5-pro,其他模型將會忽略。" + }, "image": { "name": "image", "tooltip": "用於影片的第一幀圖片。" }, "model": { - "name": "model", - "tooltip": "模型名稱" + "name": "model" }, "prompt": { "name": "prompt", @@ -489,9 +704,12 @@ "name": "持續時間", "tooltip": "輸出影片的持續時間(秒)。" }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "此參數僅適用於 seedance-1-5-pro,其他模型將會忽略。" + }, "model": { - "name": "模型", - "tooltip": "模型名稱" + "name": "模型" }, "prompt": { "name": "提示詞", @@ -531,6 +749,11 @@ "positive": { "name": "正向" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "CFGNorm": { @@ -769,6 +992,25 @@ } } }, + "CLIPTextEncodeKandinsky5": { + "display_name": "CLIPTextEncodeKandinsky5", + "inputs": { + "clip": { + "name": "clip" + }, + "clip_l": { + "name": "clip_l" + }, + "qwen25_7b": { + "name": "qwen25_7b" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "CLIPTextEncodeLumina2": { "description": "使用 CLIP 模型將系統提示與使用者提示編碼為嵌入向量,可用於引導擴散模型生成特定圖像。", "display_name": "CLIP 文本編碼(Lumina2)", @@ -959,6 +1201,29 @@ } } }, + "CenterCropImages": { + "display_name": "中心裁切影像", + "inputs": { + "height": { + "name": "高度", + "tooltip": "裁切高度。" + }, + "images": { + "name": "影像", + "tooltip": "要處理的影像。" + }, + "width": { + "name": "寬度", + "tooltip": "裁切寬度。" + } + }, + "outputs": { + "0": { + "name": "影像", + "tooltip": "已處理的影像" + } + } + }, "CheckpointLoader": { "display_name": "載入檢查點與設定檔(已淘汰)", "inputs": { @@ -1095,6 +1360,26 @@ } } }, + "ComfySwitchNode": { + "display_name": "切換", + "inputs": { + "on_false": { + "name": "為假時" + }, + "on_true": { + "name": "為真時" + }, + "switch": { + "name": "切換" + } + }, + "outputs": { + "0": { + "name": "輸出", + "tooltip": null + } + } + }, "ConditioningAverage": { "display_name": "條件設定(平均)", "inputs": { @@ -1327,14 +1612,14 @@ "name": "總秒數" } }, - "outputs": { - "0": { - "name": "正向" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "負向" + { + "tooltip": null } - } + ] }, "ConditioningTimestepsRange": { "display_name": "步驟範圍", @@ -1391,6 +1676,10 @@ "name": "維度", "tooltip": "應用上下文窗口的維度。" }, + "freenoise": { + "name": "自由雜訊", + "tooltip": "是否應用 FreeNoise 雜訊洗牌,可改善視窗混合效果。" + }, "fuse_method": { "name": "融合方法", "tooltip": "用於融合上下文窗口的方法。" @@ -1791,8 +2080,32 @@ "y": { "name": "Y 座標" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, + "CustomCombo": { + "display_name": "自訂組合", + "inputs": { + "choice": { + "name": "選項" + }, + "index": { + }, + "option1": { + } + }, + "outputs": [ + null, + { + "name": "INDEX", + "tooltip": null + } + ] + }, "DiffControlNetLoader": { "display_name": "載入 ControlNet 模型(diff)", "inputs": { @@ -1829,7 +2142,12 @@ } }, "DisableNoise": { - "display_name": "停用雜訊" + "display_name": "停用雜訊", + "outputs": { + "0": { + "tooltip": null + } + } }, "DualCFGGuider": { "display_name": "雙 CFG 引導器", @@ -1855,6 +2173,11 @@ "style": { "name": "風格" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "DualCLIPLoader": { @@ -1938,6 +2261,11 @@ "name": "取樣率", "tooltip": "空白音訊片段的取樣率。" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyChromaRadianceLatentImage": { @@ -1981,6 +2309,25 @@ } } }, + "EmptyFlux2LatentImage": { + "display_name": "空白 Flux 轉 Latent", + "inputs": { + "batch_size": { + "name": "批次大小" + }, + "height": { + "name": "高度" + }, + "width": { + "name": "寬度" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptyHunyuanImageLatent": { "display_name": "EmptyHunyuanImageLatent", "inputs": { @@ -2022,6 +2369,28 @@ } } }, + "EmptyHunyuanVideo15Latent": { + "display_name": "空白 HunyuanVideo 1.5 Latent", + "inputs": { + "batch_size": { + "name": "批次大小" + }, + "height": { + "name": "高度" + }, + "length": { + "name": "長度" + }, + "width": { + "name": "寬度" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptyImage": { "display_name": "空白圖片", "inputs": { @@ -2071,6 +2440,11 @@ "seconds": { "name": "秒數" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyLatentHunyuan3Dv2": { @@ -2083,6 +2457,11 @@ "resolution": { "name": "解析度" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyLatentImage": { @@ -2130,6 +2509,28 @@ } } }, + "EmptyQwenImageLayeredLatentImage": { + "display_name": "空白 Qwen 圖像分層 Latent", + "inputs": { + "batch_size": { + "name": "批次大小" + }, + "height": { + "name": "高度" + }, + "layers": { + "name": "圖層數" + }, + "width": { + "name": "寬度" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptySD3LatentImage": { "display_name": "EmptySD3LatentImage", "inputs": { @@ -2177,6 +2578,11 @@ "steps": { "name": "步驟數" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ExtendIntermediateSigmas": { @@ -2197,6 +2603,11 @@ "steps": { "name": "步驟數" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FeatherMask": { @@ -2217,6 +2628,11 @@ "top": { "name": "上方" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FlipSigmas": { @@ -2225,6 +2641,102 @@ "sigmas": { "name": "sigmas" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2MaxImageNode": { + "description": "根據提示詞與解析度同步生成圖像。", + "display_name": "Flux.2 [max] 圖像", + "inputs": { + "control_after_generate": { + "name": "生成後控制" + }, + "height": { + "name": "高度" + }, + "images": { + "name": "參考圖像", + "tooltip": "最多可使用 9 張圖像作為參考。" + }, + "prompt": { + "name": "提示詞", + "tooltip": "用於圖像生成或編輯的提示詞" + }, + "prompt_upsampling": { + "name": "提示詞升頻", + "tooltip": "是否對提示詞進行升頻。啟用時,會自動調整提示詞以產生更具創意的圖像。" + }, + "seed": { + "name": "隨機種子", + "tooltip": "用於產生雜訊的隨機種子。" + }, + "width": { + "name": "寬度" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2ProImageNode": { + "description": "根據提示詞與解析度同步生成圖像。", + "display_name": "Flux.2 [pro] 圖像", + "inputs": { + "control_after_generate": { + "name": "生成後控制" + }, + "height": { + "name": "高度" + }, + "images": { + "name": "參考圖像", + "tooltip": "最多可使用 9 張圖像作為參考。" + }, + "prompt": { + "name": "提示詞", + "tooltip": "用於圖像生成或編輯的提示詞" + }, + "prompt_upsampling": { + "name": "提示詞升頻", + "tooltip": "是否對提示詞進行升頻。啟用時,會自動調整提示詞以產生更具創意的圖像。" + }, + "seed": { + "name": "隨機種子", + "tooltip": "用於產生雜訊的隨機種子。" + }, + "width": { + "name": "寬度" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2Scheduler": { + "display_name": "Flux2Scheduler", + "inputs": { + "height": { + "name": "高度" + }, + "steps": { + "name": "步數" + }, + "width": { + "name": "寬度" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FluxDisableGuidance": { @@ -2547,6 +3059,11 @@ "s2": { "name": "s2" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FreeU_V2": { @@ -2567,6 +3084,11 @@ "s2": { "name": "s2" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "GITSScheduler": { @@ -2625,6 +3147,58 @@ } } }, + "GeminiImage2Node": { + "description": "透過 Google Vertex API 同步產生或編輯影像。", + "display_name": "Nano Banana Pro(Google Gemini Image)", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio", + "tooltip": "若設為「auto」,則會配合輸入影像的長寬比;若未提供影像,通常會產生 16:9 的正方形。" + }, + "control_after_generate": { + "name": "control after generate" + }, + "files": { + "name": "files", + "tooltip": "可選的檔案,作為模型的參考。可接受來自 Gemini Generate Content Input Files 節點的輸入。" + }, + "images": { + "name": "images", + "tooltip": "可選的參考影像。若要加入多張影像,請使用 Batch Images 節點(最多 14 張)。" + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "描述要產生影像或要套用編輯的文字提示。請包含任何限制、風格或模型應遵循的細節。" + }, + "resolution": { + "name": "resolution", + "tooltip": "目標輸出解析度。2K/4K 會使用 Gemini 原生升頻器。" + }, + "response_modalities": { + "name": "response_modalities", + "tooltip": "選擇「IMAGE」僅輸出影像,或「IMAGE+TEXT」同時回傳產生的影像與文字回應。" + }, + "seed": { + "name": "seed", + "tooltip": "當 seed 設定為特定值時,模型會盡力在重複請求時提供相同的回應,但不保證完全一致。更改模型或參數(如 temperature)即使使用相同 seed 也可能導致回應不同。預設會使用隨機 seed 值。" + }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "決定 AI 行為的基礎指令。" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "tooltip": null + } + } + }, "GeminiImageNode": { "description": "透過 Google API 同步編輯影像。", "display_name": "Google Gemini 影像", @@ -2652,9 +3226,17 @@ "name": "提示詞", "tooltip": "生成的文字提示詞" }, + "response_modalities": { + "name": "response_modalities", + "tooltip": "選擇「IMAGE」僅輸出影像,或「IMAGE+TEXT」同時回傳產生的影像與文字回應。" + }, "seed": { "name": "種子值", "tooltip": "當種子值固定為特定值時,模型會盡力為重複請求提供相同的回應。不保證輸出具有確定性。此外,更改模型或參數設置(例如溫度)即使使用相同的種子值也可能導致回應發生變化。預設情況下,使用隨機種子值。" + }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "決定 AI 行為的基礎指令。" } }, "outputs": { @@ -2716,6 +3298,10 @@ "name": "種子值", "tooltip": "當種子值固定為特定值時,模型會盡力為重複請求提供相同回應。不保證輸出具有確定性。此外,更改模型或參數設定(例如溫度)可能導致回應變化,即使使用相同的種子值。預設使用隨機種子值。" }, + "system_prompt": { + "name": "system_prompt", + "tooltip": "決定 AI 行為的基礎指令。" + }, "video": { "name": "影片", "tooltip": "可選的影片,用作模型的上下文。" @@ -2727,6 +3313,72 @@ } } }, + "GenerateTracks": { + "display_name": "GenerateTracks", + "inputs": { + "bezier": { + "name": "bezier", + "tooltip": "啟用貝茲曲線路徑,使用中點作為控制點。" + }, + "end_x": { + "name": "end_x", + "tooltip": "結束位置的標準化 X 座標(0-1)。" + }, + "end_y": { + "name": "end_y", + "tooltip": "結束位置的標準化 Y 座標(0-1)。" + }, + "height": { + "name": "height" + }, + "interpolation": { + "name": "interpolation", + "tooltip": "控制沿路徑移動的時序/速度。" + }, + "mid_x": { + "name": "mid_x", + "tooltip": "貝茲曲線的標準化 X 控制點。僅在啟用 'bezier' 時使用。" + }, + "mid_y": { + "name": "mid_y", + "tooltip": "貝茲曲線的標準化 Y 控制點。僅在啟用 'bezier' 時使用。" + }, + "num_frames": { + "name": "num_frames" + }, + "num_tracks": { + "name": "num_tracks" + }, + "start_x": { + "name": "start_x", + "tooltip": "起始位置的標準化 X 座標(0-1)。" + }, + "start_y": { + "name": "start_y", + "tooltip": "起始位置的標準化 Y 座標(0-1)。" + }, + "track_mask": { + "name": "track_mask", + "tooltip": "可選的 mask,用於標示可見的影格。" + }, + "track_spread": { + "name": "track_spread", + "tooltip": "軌跡之間的標準化距離。軌跡會垂直於移動方向分布。" + }, + "width": { + "name": "width" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "track_length", + "tooltip": null + } + } + }, "GetImageSize": { "description": "回傳圖片的寬度和高度,並保持原樣傳遞。", "display_name": "取得圖片尺寸", @@ -2735,17 +3387,17 @@ "name": "圖片" } }, - "outputs": { - "0": { - "name": "寬度" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "高度" + { + "tooltip": null }, - "2": { - "name": "批次大小" + { + "tooltip": null } - } + ] }, "GetVideoComponents": { "description": "從影片中提取所有元件:影格、音訊與影格率。", @@ -2783,6 +3435,11 @@ "tapered_corners": { "name": "圓角" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Hunyuan3Dv2Conditioning": { @@ -2792,14 +3449,14 @@ "name": "clip_vision_output" } }, - "outputs": { - "0": { - "name": "正向" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "負向" + { + "tooltip": null } - } + ] }, "Hunyuan3Dv2ConditioningMultiView": { "display_name": "Hunyuan3Dv2ConditioningMultiView", @@ -2817,14 +3474,14 @@ "name": "右視圖" } }, - "outputs": { - "0": { - "name": "正向" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "負向" + { + "tooltip": null } - } + ] }, "HunyuanImageToVideo": { "display_name": "HunyuanImageToVideo", @@ -2896,6 +3553,120 @@ } } }, + "HunyuanVideo15ImageToVideo": { + "display_name": "HunyuanVideo15ImageToVideo", + "inputs": { + "batch_size": { + "name": "batch_size" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "height": { + "name": "height" + }, + "length": { + "name": "length" + }, + "negative": { + "name": "negative" + }, + "positive": { + "name": "positive" + }, + "start_image": { + "name": "start_image" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "width" + } + }, + "outputs": { + "0": { + "name": "positive", + "tooltip": null + }, + "1": { + "name": "negative", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "HunyuanVideo15LatentUpscaleWithModel": { + "display_name": "Hunyuan Video 15 Latent Upscale With Model", + "inputs": { + "crop": { + "name": "crop" + }, + "height": { + "name": "height" + }, + "model": { + "name": "model" + }, + "samples": { + "name": "samples" + }, + "upscale_method": { + "name": "upscale_method" + }, + "width": { + "name": "width" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "HunyuanVideo15SuperResolution": { + "display_name": "HunyuanVideo15SuperResolution", + "inputs": { + "clip_vision_output": { + "name": "clip_vision_output" + }, + "latent": { + "name": "latent" + }, + "negative": { + "name": "negative" + }, + "noise_augmentation": { + "name": "雜訊增強" + }, + "positive": { + "name": "positive" + }, + "start_image": { + "name": "起始影像" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "positive", + "tooltip": null + }, + "1": { + "name": "negative", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, "HyperTile": { "display_name": "HyperTile", "inputs": { @@ -3100,6 +3871,11 @@ "strength": { "name": "強度" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageBatch": { @@ -3163,6 +3939,26 @@ "image": { "name": "圖片" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageCompare": { + "description": "將兩張圖像並排顯示,並可用滑桿比較差異。", + "display_name": "圖像比較", + "inputs": { + "compare_view": { + "name": "compare_view" + }, + "image_a": { + "name": "image_a" + }, + "image_b": { + "name": "image_b" + } } }, "ImageCompositeMasked": { @@ -3186,6 +3982,11 @@ "y": { "name": "Y 座標" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageCrop": { @@ -3206,6 +4007,30 @@ "y": { "name": "Y 座標" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageDeduplication": { + "display_name": "影像去重", + "inputs": { + "images": { + "name": "影像", + "tooltip": "要處理的影像清單。" + }, + "similarity_threshold": { + "name": "相似度閾值", + "tooltip": "相似度閾值(0-1)。數值越高表示越相似。超過此閾值的影像會被視為重複。" + } + }, + "outputs": { + "0": { + "name": "影像", + "tooltip": "已處理的影像" + } } }, "ImageFlip": { @@ -3217,6 +4042,11 @@ "image": { "name": "影像" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageFromBatch": { @@ -3231,6 +4061,42 @@ "length": { "name": "長度" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageGrid": { + "display_name": "影像網格", + "inputs": { + "cell_height": { + "name": "格子高度", + "tooltip": "每個格子的高度。" + }, + "cell_width": { + "name": "格子寬度", + "tooltip": "每個格子的寬度。" + }, + "columns": { + "name": "欄數", + "tooltip": "網格中的欄數。" + }, + "images": { + "name": "影像", + "tooltip": "要處理的影像清單。" + }, + "padding": { + "name": "間距", + "tooltip": "影像之間的間距。" + } + }, + "outputs": { + "0": { + "name": "影像", + "tooltip": "已處理的影像" + } } }, "ImageInvert": { @@ -3339,6 +4205,11 @@ "rotation": { "name": "旋轉" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageScale": { @@ -3387,6 +4258,11 @@ "upscale_method": { "name": "放大方法" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageScaleToTotalPixels": { @@ -3398,6 +4274,9 @@ "megapixels": { "name": "百萬像素" }, + "resolution_steps": { + "name": "解析度步驟" + }, "upscale_method": { "name": "放大方法" } @@ -3452,6 +4331,11 @@ "spacing_width": { "name": "間距寬度" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageToMask": { @@ -3463,6 +4347,11 @@ "image": { "name": "圖片" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageUpscaleWithModel": { @@ -3572,6 +4461,29 @@ "mask": { "name": "遮罩" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "JoinAudioChannels": { + "description": "將左聲道與右聲道的單聲道音訊合併為立體聲音訊。", + "display_name": "合併音訊聲道", + "inputs": { + "audio_left": { + "name": "audio_left" + }, + "audio_right": { + "name": "audio_right" + } + }, + "outputs": { + "0": { + "name": "audio", + "tooltip": null + } } }, "JoinImageWithAlpha": { @@ -3697,6 +4609,58 @@ "sampler_name": { "name": "取樣器名稱" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Kandinsky5ImageToVideo": { + "display_name": "Kandinsky5ImageToVideo", + "inputs": { + "batch_size": { + "name": "批次大小" + }, + "height": { + "name": "高度" + }, + "length": { + "name": "長度" + }, + "negative": { + "name": "negative" + }, + "positive": { + "name": "positive" + }, + "start_image": { + "name": "起始圖片" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "寬度" + } + }, + "outputs": { + "0": { + "name": "positive", + "tooltip": null + }, + "1": { + "name": "negative", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": "空的影片 latent" + }, + "3": { + "name": "cond_latent", + "tooltip": "已清理編碼的起始圖片,用於取代模型輸出 latent 的雜訊起始" + } } }, "KarrasScheduler": { @@ -3714,6 +4678,11 @@ "steps": { "name": "步驟數" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "KlingCameraControlI2VNode": { @@ -3869,7 +4838,6 @@ } }, "KlingImage2VideoNode": { - "description": "Kling 圖像轉影片節點", "display_name": "Kling 圖像轉影片", "inputs": { "aspect_ratio": { @@ -3957,6 +4925,35 @@ } } }, + "KlingImageToVideoWithAudio": { + "display_name": "Kling 圖片(首幀)轉影片並加音訊", + "inputs": { + "duration": { + "name": "時長" + }, + "generate_audio": { + "name": "產生音訊" + }, + "mode": { + "name": "模式" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "提示詞", + "tooltip": "正向文字提示。" + }, + "start_frame": { + "name": "起始幀" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingLipSyncAudioToVideoNode": { "description": "Kling 影音口型同步節點。將影片檔案中的嘴型動作與音訊檔案的語音內容同步。", "display_name": "Kling 影音口型同步", @@ -4018,6 +5015,227 @@ } } }, + "KlingMotionControl": { + "display_name": "Kling 動作控制", + "inputs": { + "character_orientation": { + "name": "角色朝向", + "tooltip": "控制角色的面向/朝向來源。\nvideo:動作、表情、鏡頭移動與朝向皆依據動作參考影片(其他細節由提示詞決定)。\nimage:動作與表情仍依據動作參考影片,但角色朝向與參考圖片一致(鏡頭/其他細節由提示詞決定)。" + }, + "keep_original_sound": { + "name": "保留原始音訊" + }, + "mode": { + "name": "模式" + }, + "prompt": { + "name": "提示詞" + }, + "reference_image": { + "name": "參考圖片" + }, + "reference_video": { + "name": "參考影片", + "tooltip": "用於驅動動作/表情的動作參考影片。\n時長限制依角色朝向而定:\n - 圖片:3–10秒(最多10秒)\n - 影片:3–30秒(最多30秒)" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProEditVideoNode": { + "description": "使用 Kling 最新模型編輯現有影片。", + "display_name": "Kling Omni 編輯影片(專業版)", + "inputs": { + "keep_original_sound": { + "name": "保留原始音訊" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "提示詞", + "tooltip": "描述影片內容的文字提示,可包含正向與負向描述。" + }, + "reference_images": { + "name": "參考圖片", + "tooltip": "最多可加入 4 張額外參考圖片。" + }, + "resolution": { + "name": "解析度" + }, + "video": { + "name": "影片", + "tooltip": "要編輯的影片。輸出影片長度將與原影片相同。" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProFirstLastFrameNode": { + "description": "使用起始影格、可選的結束影格,或參考圖片,搭配最新 Kling 模型。", + "display_name": "Kling Omni 首末影格轉影片 (Pro)", + "inputs": { + "duration": { + "name": "時長" + }, + "end_frame": { + "name": "結束影格", + "tooltip": "影片的可選結束影格。不可與「reference_images」同時使用。" + }, + "first_frame": { + "name": "起始影格" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "提示詞", + "tooltip": "描述影片內容的文字提示,可包含正面與負面描述。" + }, + "reference_images": { + "name": "參考圖片", + "tooltip": "最多可加入 6 張額外參考圖片。" + }, + "resolution": { + "name": "解析度" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageNode": { + "description": "使用 Kling 最新模型建立或編輯圖像。", + "display_name": "Kling Omni 圖像 (Pro)", + "inputs": { + "aspect_ratio": { + "name": "長寬比" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "提示詞", + "tooltip": "描述圖像內容的文字提示,可包含正面與負面描述。" + }, + "reference_images": { + "name": "參考圖片", + "tooltip": "最多可加入 10 張額外參考圖片。" + }, + "resolution": { + "name": "解析度" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageToVideoNode": { + "description": "使用最多 7 張參考圖片,搭配最新 Kling 模型產生影片。", + "display_name": "Kling Omni 圖像轉影片 (Pro)", + "inputs": { + "aspect_ratio": { + "name": "長寬比" + }, + "duration": { + "name": "時長" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "提示詞", + "tooltip": "描述影片內容的文字提示,可包含正面與負面描述。" + }, + "reference_images": { + "name": "參考圖片", + "tooltip": "最多可加入 7 張參考圖片。" + }, + "resolution": { + "name": "解析度" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProTextToVideoNode": { + "description": "使用文字提示,搭配最新 Kling 模型產生影片。", + "display_name": "Kling Omni 文字轉影片 (Pro)", + "inputs": { + "aspect_ratio": { + "name": "長寬比" + }, + "duration": { + "name": "時長" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "提示詞", + "tooltip": "描述影片內容的文字提示,可包含正面與負面描述。" + }, + "resolution": { + "name": "解析度" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProVideoToVideoNode": { + "description": "使用一段影片和最多 4 張參考圖片,搭配最新 Kling 模型生成影片。", + "display_name": "Kling Omni 影片轉影片(專業版)", + "inputs": { + "aspect_ratio": { + "name": "長寬比" + }, + "duration": { + "name": "時長" + }, + "keep_original_sound": { + "name": "保留原始聲音" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "提示詞", + "tooltip": "描述影片內容的文字提示,可包含正面或負面描述。" + }, + "reference_images": { + "name": "參考圖片", + "tooltip": "最多 4 張額外參考圖片。" + }, + "reference_video": { + "name": "參考影片", + "tooltip": "作為參考的影片。" + }, + "resolution": { + "name": "解析度" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingSingleImageVideoEffectNode": { "description": "根據 effect_scene 產生影片時,實現不同的特殊效果。", "display_name": "Kling 影片特效", @@ -4132,6 +5350,35 @@ } } }, + "KlingTextToVideoWithAudio": { + "display_name": "Kling 文字轉影片(含音訊)", + "inputs": { + "aspect_ratio": { + "name": "長寬比" + }, + "duration": { + "name": "時長" + }, + "generate_audio": { + "name": "生成音訊" + }, + "mode": { + "name": "模式" + }, + "model_name": { + "name": "model_name" + }, + "prompt": { + "name": "提示詞", + "tooltip": "正向文字提示。" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingVideoExtendNode": { "description": "Kling Video 延伸節點。可延伸由其他 Kling 節點製作的影片。video_id 是透過其他 Kling 節點產生的。", "display_name": "Kling Video 延伸", @@ -4186,6 +5433,26 @@ } } }, + "LTXAVTextEncoderLoader": { + "description": "[Recipes]\n\nltxav: gemma 3 12B", + "display_name": "LTXV 音訊文字編碼器載入器", + "inputs": { + "ckpt_name": { + "name": "ckpt_name" + }, + "device": { + "name": "device" + }, + "text_encoder": { + "name": "text_encoder" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LTXVAddGuide": { "display_name": "LTXV 添加引導", "inputs": { @@ -4228,6 +5495,76 @@ } } }, + "LTXVAudioVAEDecode": { + "display_name": "LTXV 音訊 VAE 解碼", + "inputs": { + "audio_vae": { + "name": "audio_vae", + "tooltip": "用於解碼 latent 的 Audio VAE 模型。" + }, + "samples": { + "name": "samples", + "tooltip": "要解碼的 latent。" + } + }, + "outputs": { + "0": { + "name": "Audio", + "tooltip": null + } + } + }, + "LTXVAudioVAEEncode": { + "display_name": "LTXV 音訊 VAE 編碼", + "inputs": { + "audio": { + "name": "audio", + "tooltip": "要編碼的音訊。" + }, + "audio_vae": { + "name": "audio_vae", + "tooltip": "用於編碼的 Audio VAE 模型。" + } + }, + "outputs": { + "0": { + "name": "Audio Latent", + "tooltip": null + } + } + }, + "LTXVAudioVAELoader": { + "display_name": "LTXV 音訊 VAE 載入器", + "inputs": { + "ckpt_name": { + "name": "ckpt_name", + "tooltip": "要載入的 Audio VAE 檢查點。" + } + }, + "outputs": { + "0": { + "name": "Audio VAE", + "tooltip": null + } + } + }, + "LTXVConcatAVLatent": { + "display_name": "LTXVConcatAVLatent", + "inputs": { + "audio_latent": { + "name": "audio_latent" + }, + "video_latent": { + "name": "video_latent" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, "LTXVConditioning": { "display_name": "LTXV 條件化", "inputs": { @@ -4280,6 +5617,33 @@ } } }, + "LTXVEmptyLatentAudio": { + "display_name": "LTXV 空 latent 音訊", + "inputs": { + "audio_vae": { + "name": "audio_vae", + "tooltip": "用於取得組態的 Audio VAE 模型。" + }, + "batch_size": { + "name": "batch_size", + "tooltip": "批次中的 latent 音訊樣本數。" + }, + "frame_rate": { + "name": "frame_rate", + "tooltip": "每秒影格數。" + }, + "frames_number": { + "name": "frames_number", + "tooltip": "影格數量。" + } + }, + "outputs": { + "0": { + "name": "Latent", + "tooltip": null + } + } + }, "LTXVImgToVideo": { "display_name": "LTXV 圖片轉影片", "inputs": { @@ -4326,6 +5690,47 @@ } } }, + "LTXVImgToVideoInplace": { + "display_name": "LTXVImgToVideoInplace", + "inputs": { + "bypass": { + "name": "繞過", + "tooltip": "繞過條件處理。" + }, + "image": { + "name": "圖片" + }, + "latent": { + "name": "latent" + }, + "strength": { + "name": "強度" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, + "LTXVLatentUpsampler": { + "display_name": "LTXVLatentUpsampler", + "inputs": { + "samples": { + "name": "樣本" + }, + "upscale_model": { + "name": "放大模型" + }, + "vae": { + "name": "vae" + } + } + }, "LTXVPreprocess": { "display_name": "LTXV 預處理", "inputs": { @@ -4374,6 +5779,25 @@ } } }, + "LTXVSeparateAVLatent": { + "description": "LTXV 分離 AV latent", + "display_name": "LTXVSeparateAVLatent", + "inputs": { + "av_latent": { + "name": "av_latent" + } + }, + "outputs": { + "0": { + "name": "video_latent", + "tooltip": null + }, + "1": { + "name": "audio_latent", + "tooltip": null + } + } + }, "LaplaceScheduler": { "display_name": "Laplace 排程器", "inputs": { @@ -4392,6 +5816,11 @@ "steps": { "name": "步驟數" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LatentAdd": { @@ -4529,6 +5958,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LatentConcat": { @@ -4592,6 +6026,25 @@ } } }, + "LatentCutToBatch": { + "display_name": "LatentCutToBatch", + "inputs": { + "dim": { + "name": "維度" + }, + "samples": { + "name": "樣本" + }, + "slice_size": { + "name": "切片大小" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LatentFlip": { "display_name": "翻轉 Latent", "inputs": { @@ -4745,6 +6198,19 @@ } } }, + "LatentUpscaleModelLoader": { + "display_name": "載入 Latent 放大模型", + "inputs": { + "model_name": { + "name": "model_name" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LazyCache": { "description": "自製版 EasyCache - 更「簡單」的 EasyCache 實作版本。整體表現不如 EasyCache,但在某些罕見情況下表現更好,且與 ComfyUI 中的所有內容具有通用相容性。", "display_name": "懶快取", @@ -4792,70 +6258,32 @@ }, "upload 3d model": { }, - "width": { - "name": "寬度" - } - }, - "outputs": { - "0": { - "name": "圖片" - }, - "1": { - "name": "遮罩" - }, - "2": { - "name": "網格路徑" - }, - "3": { - "name": "法線" - }, - "4": { - "name": "線稿" - }, - "5": { - "name": "相機資訊" - }, - "6": { - "name": "錄製影片" - } - } - }, - "Load3DAnimation": { - "display_name": "載入 3D - 動畫", - "inputs": { - "height": { - "name": "高度" - }, - "image": { - "name": "影像" - }, - "model_file": { - "name": "模型檔案" + "upload extra resources": { }, "width": { "name": "寬度" } }, - "outputs": { - "0": { - "name": "圖片" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "遮罩" + { + "tooltip": null }, - "2": { - "name": "網格路徑" + { + "tooltip": null }, - "3": { - "name": "法線" + { + "tooltip": null }, - "4": { - "name": "相機資訊" + { + "tooltip": null }, - "5": { - "name": "錄製影片" + { + "tooltip": null } - } + ] }, "LoadAudio": { "display_name": "載入音訊", @@ -4869,6 +6297,11 @@ "upload": { "name": "選擇檔案上傳" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LoadImage": { @@ -4882,6 +6315,21 @@ } } }, + "LoadImageDataSetFromFolder": { + "display_name": "從資料夾載入圖片資料集", + "inputs": { + "folder": { + "name": "資料夾", + "tooltip": "從此資料夾載入圖片。" + } + }, + "outputs": { + "0": { + "name": "圖片", + "tooltip": "已載入的圖片清單" + } + } + }, "LoadImageMask": { "display_name": "載入圖片(作為遮罩)", "inputs": { @@ -4900,6 +6348,8 @@ "description": "從輸出資料夾載入圖片。當點擊重新整理按鈕時,節點會更新圖片清單並自動選取第一張圖片,方便進行反覆操作。", "display_name": "載入圖片(來自輸出)", "inputs": { + "Auto-refresh after generation": { + }, "image": { "name": "影像" }, @@ -4910,41 +6360,22 @@ } } }, - "LoadImageSetFromFolderNode": { - "description": "從目錄載入一批影像用於訓練。", - "display_name": "從資料夾載入影像資料集", + "LoadImageTextDataSetFromFolder": { + "display_name": "從資料夾載入圖片與文字資料集", "inputs": { "folder": { "name": "資料夾", - "tooltip": "要載入影像的資料夾。" - }, - "resize_method": { - "name": "調整大小方法" + "tooltip": "從此資料夾載入圖片。" } - } - }, - "LoadImageTextSetFromFolderNode": { - "description": "載入一批來自目錄的圖片和標題用於訓練。", - "display_name": "從資料夾載入影像和文字資料集", - "inputs": { - "clip": { - "name": "CLIP 模型", - "tooltip": "用於編碼文字的 CLIP 模型。" + }, + "outputs": { + "0": { + "name": "圖片", + "tooltip": "已載入的圖片清單" }, - "folder": { - "name": "資料夾", - "tooltip": "要從中載入圖片的資料夾。" - }, - "height": { - "name": "高度", - "tooltip": "要將圖片調整到的高度。-1 表示使用原始高度。" - }, - "resize_method": { - "name": "調整大小方法" - }, - "width": { - "name": "寬度", - "tooltip": "要將圖片調整到的寬度。-1 表示使用原始寬度。" + "1": { + "name": "文字", + "tooltip": "文字標註清單" } } }, @@ -4956,6 +6387,25 @@ } } }, + "LoadTrainingDataset": { + "display_name": "載入訓練資料集", + "inputs": { + "folder_name": { + "name": "folder_name", + "tooltip": "包含已儲存資料集的資料夾名稱(位於輸出目錄內)。" + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "latent 字典的列表" + }, + "1": { + "name": "conditioning", + "tooltip": "conditioning 列表的列表" + } + } + }, "LoadVideo": { "display_name": "載入影片", "inputs": { @@ -5027,7 +6477,6 @@ } }, "LoraModelLoader": { - "description": "從訓練 LoRA 節點載入已訓練的 LoRA 權重。", "display_name": "載入 LoRA 模型", "inputs": { "lora": { @@ -5043,11 +6492,11 @@ "tooltip": "修改擴散模型的強度。此值可以為負數。" } }, - "outputs": { - "0": { - "tooltip": "修改後的擴散模型。" + "outputs": [ + { + "name": "model" } - } + ] }, "LoraSave": { "display_name": "提取並儲存Lora", @@ -5075,14 +6524,15 @@ } }, "LossGraphNode": { - "description": "繪製損失圖表並將其儲存到輸出目錄。", "display_name": "繪製損失圖表", "inputs": { "filename_prefix": { - "name": "檔案名稱前綴" + "name": "檔案名稱前綴", + "tooltip": "儲存 loss 圖像的檔名前綴。" }, "loss": { - "name": "損失" + "name": "損失", + "tooltip": "來自訓練節點的 loss 映射。" } } }, @@ -5388,6 +6838,50 @@ } } }, + "MakeTrainingDataset": { + "display_name": "建立訓練資料集", + "inputs": { + "clip": { + "name": "clip", + "tooltip": "用於將文字編碼為條件的 CLIP 模型。" + }, + "images": { + "name": "圖片", + "tooltip": "要編碼的圖片清單。" + }, + "texts": { + "name": "文字", + "tooltip": "文字標題清單。可為 n 筆(與圖片數量相同)、1 筆(全部重複),或省略(使用空字串)。" + }, + "vae": { + "name": "vae", + "tooltip": "用於將圖片編碼為 latent 的 VAE 模型。" + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "latent 字典清單" + }, + "1": { + "name": "conditioning", + "tooltip": "條件清單" + } + } + }, + "ManualSigmas": { + "display_name": "手動 Sigma", + "inputs": { + "sigmas": { + "name": "sigmas" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "MaskComposite": { "display_name": "遮罩合成", "inputs": { @@ -5406,6 +6900,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "MaskPreview": { @@ -5423,6 +6922,315 @@ "mask": { "name": "遮罩" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MergeImageLists": { + "display_name": "合併圖片清單", + "inputs": { + "images": { + "name": "圖片", + "tooltip": "要處理的圖片清單。" + } + }, + "outputs": { + "0": { + "name": "圖片", + "tooltip": "已處理的圖片" + } + } + }, + "MergeTextLists": { + "display_name": "合併文字清單", + "inputs": { + "texts": { + "name": "文字", + "tooltip": "要處理的文字清單。" + } + }, + "outputs": { + "0": { + "name": "文字", + "tooltip": "已處理的文字" + } + } + }, + "MeshyAnimateModelNode": { + "description": "將特定動畫動作應用於先前已綁定骨架的角色。", + "display_name": "Meshy:動畫模型", + "inputs": { + "action_id": { + "name": "action_id", + "tooltip": "請造訪 https://docs.meshy.ai/en/api/animation-library 以取得可用值列表。" + }, + "rig_task_id": { + "name": "rig_task_id" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + } + } + }, + "MeshyImageToModelNode": { + "display_name": "Meshy:圖片轉模型", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "image": { + "name": "image" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "指定產生模型的姿勢模式。" + }, + "seed": { + "name": "seed", + "tooltip": "Seed 控制此節點是否重新執行;無論 seed 為何,結果皆為非確定性。" + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "設為 false 時,將回傳未處理的三角網格。" + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "should_texture": { + "name": "should_texture", + "tooltip": "決定是否產生材質。設為 false 時會跳過材質階段,並回傳無材質的網格。" + }, + "should_texture_enable_pbr": { + "name": "enable_pbr" + }, + "should_texture_texture_prompt": { + "name": "texture_prompt" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyMultiImageToModelNode": { + "display_name": "Meshy:多圖轉模型", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "指定產生模型的姿勢模式。" + }, + "seed": { + "name": "seed", + "tooltip": "Seed 控制此節點是否重新執行;無論 seed 為何,結果皆為非確定性。" + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "設為 false 時,將回傳未處理的三角網格。" + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "should_texture": { + "name": "should_texture", + "tooltip": "決定是否產生材質。設為 false 時會跳過材質階段,並回傳無材質的網格。" + }, + "should_texture_enable_pbr": { + "name": "enable_pbr" + }, + "should_texture_texture_prompt": { + "name": "texture_prompt" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRefineNode": { + "description": "精修先前建立的草稿模型。", + "display_name": "Meshy:精修草稿模型", + "inputs": { + "enable_pbr": { + "name": "enable_pbr", + "tooltip": "除了基礎色彩外,還會產生 PBR 貼圖(金屬度、粗糙度、法線)。注意:若使用雕塑風格,請設為 false,因為雕塑風格會產生自己的 PBR 貼圖。" + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "model": { + "name": "model" + }, + "texture_image": { + "name": "texture_image", + "tooltip": "「texture_image」與「texture_prompt」只能擇一使用。" + }, + "texture_prompt": { + "name": "texture_prompt", + "tooltip": "提供文字提示以引導材質製作。最多 600 字元。不可與「texture_image」同時使用。" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRigModelNode": { + "description": "提供標準格式的骨架綁定角色。目前自動綁定不適用於無材質網格、非人形資產,或肢體結構不明確的人形資產。", + "display_name": "Meshy:骨架綁定模型", + "inputs": { + "height_meters": { + "name": "height_meters", + "tooltip": "角色模型的約略高度(公尺)。有助於縮放與骨架綁定精度。" + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "texture_image": { + "name": "texture_image", + "tooltip": "模型 UV 展開後的基礎色彩貼圖。" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "rig_task_id", + "tooltip": null + } + } + }, + "MeshyTextToModelNode": { + "display_name": "Meshy:文字生成模型", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "指定生成模型的姿勢模式。" + }, + "prompt": { + "name": "prompt" + }, + "seed": { + "name": "seed", + "tooltip": "Seed 控制節點是否重新執行;無論 seed 為何,結果皆為非確定性。" + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "設為 false 時,回傳未處理的三角網格。" + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "style": { + "name": "style" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyTextureNode": { + "display_name": "Meshy:材質模型", + "inputs": { + "enable_original_uv": { + "name": "啟用原始UV", + "tooltip": "使用模型的原始UV,而非產生新的UV。啟用後,Meshy會保留上傳模型中現有的材質。如果模型沒有原始UV,輸出品質可能會較差。" + }, + "image_style": { + "name": "影像風格", + "tooltip": "用一張2D影像來引導材質生成過程。不能與「text_style_prompt」同時使用。" + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "model": { + "name": "模型" + }, + "pbr": { + "name": "PBR" + }, + "text_style_prompt": { + "name": "文字風格提示", + "tooltip": "用文字描述你想要的物件材質風格。最多600字。不能與「image_style」同時使用。" + } + }, + "outputs": { + "0": { + "name": "模型檔案", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } } }, "MinimaxHailuoVideoNode": { @@ -7866,6 +9674,52 @@ } } }, + "NormalizeImages": { + "display_name": "標準化圖片", + "inputs": { + "images": { + "name": "圖片", + "tooltip": "要處理的圖片。" + }, + "mean": { + "name": "平均值", + "tooltip": "標準化的平均值。" + }, + "std": { + "name": "標準差", + "tooltip": "標準化的標準差。" + } + }, + "outputs": { + "0": { + "name": "圖片", + "tooltip": "已處理的圖片" + } + } + }, + "NormalizeVideoLatentStart": { + "description": "將影片 latent 的初始幀正規化,使其均值與標準差與後續參考幀相符。可幫助減少起始幀與影片其餘部分的差異。", + "display_name": "NormalizeVideoLatentStart", + "inputs": { + "latent": { + "name": "latent" + }, + "reference_frame_count": { + "name": "reference_frame_count", + "tooltip": "用作參考的 latent 幀數,從起始幀之後開始" + }, + "start_frame_count": { + "name": "start_frame_count", + "tooltip": "要正規化的 latent 幀數,從起始處開始計算" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, "OpenAIChatConfig": { "description": "允許為 OpenAI 聊天節點指定進階配置選項。", "display_name": "OpenAI ChatGPT 進階選項", @@ -8015,6 +9869,9 @@ "name": "遮罩", "tooltip": "可選的修補遮罩(白色區域將被取代)" }, + "model": { + "name": "model" + }, "n": { "name": "數量", "tooltip": "要產生多少張影像" @@ -8373,265 +10230,6 @@ } } }, - "PikaImageToVideoNode2_2": { - "description": "將圖像與提示詞發送至 Pika API v2.2 以生成影片。", - "display_name": "Pika 影像轉影片", - "inputs": { - "control_after_generate": { - "name": "生成後控制" - }, - "duration": { - "name": "時長" - }, - "image": { - "name": "圖像", - "tooltip": "要轉換為影片的圖像" - }, - "negative_prompt": { - "name": "負向提示詞" - }, - "prompt_text": { - "name": "提示詞" - }, - "resolution": { - "name": "解析度" - }, - "seed": { - "name": "種子" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaScenesV2_2": { - "description": "結合您的圖片,創建包含其中物件的影片。上傳多張圖片作為素材,生成高品質且融合所有圖片內容的影片。", - "display_name": "Pika Scenes(影片影像合成)", - "inputs": { - "aspect_ratio": { - "name": "長寬比", - "tooltip": "長寬比(寬度 / 高度)" - }, - "control_after_generate": { - "name": "生成後控制" - }, - "duration": { - "name": "時長" - }, - "image_ingredient_1": { - "name": "影像素材_1", - "tooltip": "將用作影片素材的圖片。" - }, - "image_ingredient_2": { - "name": "影像素材_2", - "tooltip": "將用作影片素材的圖片。" - }, - "image_ingredient_3": { - "name": "影像素材_3", - "tooltip": "將用作影片素材的圖片。" - }, - "image_ingredient_4": { - "name": "影像素材_4", - "tooltip": "將用作影片素材的圖片。" - }, - "image_ingredient_5": { - "name": "影像素材_5", - "tooltip": "將用作影片素材的圖片。" - }, - "ingredients_mode": { - "name": "素材模式" - }, - "negative_prompt": { - "name": "負向提示詞" - }, - "prompt_text": { - "name": "提示文字" - }, - "resolution": { - "name": "解析度" - }, - "seed": { - "name": "種子" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaStartEndFrameNode2_2": { - "description": "結合您的第一張與最後一張影像來產生影片。上傳兩張圖片以定義起點與終點,讓 AI 在它們之間創造平滑的過渡效果。", - "display_name": "Pika 首尾影格轉影片", - "inputs": { - "control_after_generate": { - "name": "生成後控制" - }, - "duration": { - "name": "時長" - }, - "image_end": { - "name": "結束影像", - "tooltip": "要結合的最後一張圖片。" - }, - "image_start": { - "name": "起始影像", - "tooltip": "要結合的第一張圖片。" - }, - "negative_prompt": { - "name": "負向提示詞" - }, - "prompt_text": { - "name": "提示文字" - }, - "resolution": { - "name": "解析度" - }, - "seed": { - "name": "種子" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaTextToVideoNode2_2": { - "description": "將文字提示發送至 Pika API v2.2 以生成影片。", - "display_name": "Pika 文字轉影片", - "inputs": { - "aspect_ratio": { - "name": "長寬比", - "tooltip": "長寬比(寬度 / 高度)" - }, - "control_after_generate": { - "name": "生成後控制" - }, - "duration": { - "name": "時長" - }, - "negative_prompt": { - "name": "負向提示" - }, - "prompt_text": { - "name": "提示文字" - }, - "resolution": { - "name": "解析度" - }, - "seed": { - "name": "種子" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikadditions": { - "description": "將任何物件或圖片加入您的影片。上傳影片並指定您想加入的內容,創造無縫整合的效果。", - "display_name": "Pikadditions(影片物件插入)", - "inputs": { - "control_after_generate": { - "name": "生成後控制" - }, - "image": { - "name": "影像", - "tooltip": "要加入到影片中的圖片。" - }, - "negative_prompt": { - "name": "負向提示" - }, - "prompt_text": { - "name": "提示文字" - }, - "seed": { - "name": "種子" - }, - "video": { - "name": "影片", - "tooltip": "要加入圖片的影片。" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikaffects": { - "description": "以特定的 Pikaffect 產生影片。支援的 Pikaffect 包含:Cake-ify、Crumble、Crush、Decapitate、Deflate、Dissolve、Explode、Eye-pop、Inflate、Levitate、Melt、Peel、Poke、Squish、Ta-da、Tear", - "display_name": "Pikaffects(影片特效)", - "inputs": { - "control_after_generate": { - "name": "生成後控制" - }, - "image": { - "name": "影像", - "tooltip": "要套用 Pikaffect 的參考圖片。" - }, - "negative_prompt": { - "name": "負向提示" - }, - "pikaffect": { - "name": "Pikaffect" - }, - "prompt_text": { - "name": "提示文字" - }, - "seed": { - "name": "種子" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikaswaps": { - "description": "將影片中的任何物件或區域以新圖片或物件進行替換。可使用遮罩或座標來定義要替換的區域。", - "display_name": "Pika Swaps(影片物件替換)", - "inputs": { - "control_after_generate": { - "name": "生成後控制" - }, - "image": { - "name": "影像", - "tooltip": "用來替換影片中被遮罩物件的圖片。" - }, - "mask": { - "name": "遮罩", - "tooltip": "使用遮罩來定義影片中要替換的區域" - }, - "negative_prompt": { - "name": "負向提示" - }, - "prompt_text": { - "name": "提示文字" - }, - "region_to_modify": { - "name": "region_to_modify", - "tooltip": "要修改的物件/區域的純文字描述。" - }, - "seed": { - "name": "種子" - }, - "video": { - "name": "影片", - "tooltip": "要進行物件替換的影片。" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, "PixverseImageToVideoNode": { "description": "根據提示詞與輸出尺寸同步生成影片。", "display_name": "PixVerse 影像轉影片", @@ -8786,6 +10384,11 @@ "steps": { "name": "步驟數" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "PorterDuffImageComposite": { @@ -8819,6 +10422,9 @@ "Preview3D": { "display_name": "3D 預覽", "inputs": { + "bg_image": { + "name": "bg_image" + }, "camera_info": { "name": "camera_info" }, @@ -8830,22 +10436,13 @@ } } }, - "Preview3DAnimation": { - "display_name": "預覽 3D - 動畫", - "inputs": { - "camera_info": { - "name": "camera_info" - }, - "model_file": { - "name": "model_file" - } - } - }, "PreviewAny": { "display_name": "預覽任意", "inputs": { "preview": { }, + "previewMode": { + }, "source": { "name": "來源" } @@ -8985,6 +10582,36 @@ } } }, + "RandomCropImages": { + "display_name": "隨機裁切影像", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "height": { + "name": "height", + "tooltip": "裁切高度。" + }, + "images": { + "name": "images", + "tooltip": "要處理的影像。" + }, + "seed": { + "name": "seed", + "tooltip": "隨機種子。" + }, + "width": { + "name": "width", + "tooltip": "裁切寬度。" + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "已處理的影像" + } + } + }, "RandomNoise": { "display_name": "隨機雜訊", "inputs": { @@ -8994,6 +10621,11 @@ "noise_seed": { "name": "noise_seed" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RebatchImages": { @@ -9034,6 +10666,11 @@ "audio": { "name": "音訊" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RecraftColorRGB": { @@ -9538,6 +11175,11 @@ "image": { "name": "影像" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RepeatLatentBatch": { @@ -9551,6 +11193,51 @@ } } }, + "ReplaceText": { + "display_name": "取代文字", + "inputs": { + "find": { + "name": "find", + "tooltip": "要尋找的文字。" + }, + "replace": { + "name": "replace", + "tooltip": "要取代成的文字。" + }, + "texts": { + "name": "texts", + "tooltip": "要處理的文字。" + } + }, + "outputs": { + "0": { + "name": "texts", + "tooltip": "已處理的文字" + } + } + }, + "ReplaceVideoLatentFrames": { + "display_name": "ReplaceVideoLatentFrames", + "inputs": { + "destination": { + "name": "destination", + "tooltip": "要被取代幀的目標 latent。" + }, + "index": { + "name": "index", + "tooltip": "來源 latent 幀將插入目標 latent 的起始幀索引。負值則從結尾倒數計算。" + }, + "source": { + "name": "source", + "tooltip": "提供要插入目標 latent 幀的來源 latent。如果未提供,則返回未更動的目標 latent。" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "RescaleCFG": { "display_name": "RescaleCFG", "inputs": { @@ -9580,6 +11267,104 @@ "target_width": { "name": "目標寬度" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ResizeImageMaskNode": { + "description": "使用各種縮放方法調整圖像或 mask 的大小。", + "display_name": "調整影像/遮罩大小", + "inputs": { + "input": { + "name": "input" + }, + "resize_type": { + "name": "resize_type", + "tooltip": "選擇調整大小的方式:依照精確尺寸、縮放比例、匹配其他圖像等。" + }, + "resize_type_crop": { + "name": "裁切" + }, + "resize_type_height": { + "name": "高度" + }, + "resize_type_width": { + "name": "寬度" + }, + "scale_method": { + "name": "scale_method", + "tooltip": "插值演算法。'area' 適合縮小,'lanczos' 適合放大,'nearest-exact' 適合像素藝術。" + } + }, + "outputs": { + "0": { + "name": "resized", + "tooltip": null + } + } + }, + "ResizeImagesByLongerEdge": { + "display_name": "依較長邊調整圖片尺寸", + "inputs": { + "images": { + "name": "images", + "tooltip": "要處理的圖片。" + }, + "longer_edge": { + "name": "longer_edge", + "tooltip": "較長邊的目標長度。" + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "已處理的圖片" + } + } + }, + "ResizeImagesByShorterEdge": { + "display_name": "依較短邊調整圖片尺寸", + "inputs": { + "images": { + "name": "images", + "tooltip": "要處理的圖片。" + }, + "shorter_edge": { + "name": "shorter_edge", + "tooltip": "較短邊的目標長度。" + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "已處理的圖片" + } + } + }, + "ResolutionBucket": { + "display_name": "解析度分桶", + "inputs": { + "conditioning": { + "name": "conditioning", + "tooltip": "條件列表(必須與 latents 長度相同)。" + }, + "latents": { + "name": "latents", + "tooltip": "依解析度分桶的 latent 字典列表。" + } + }, + "outputs": { + "0": { + "name": "latents", + "tooltip": "每個解析度分桶的 batched latent 字典列表。" + }, + "1": { + "name": "conditioning", + "tooltip": "每個解析度分桶的條件列表。" + } } }, "Rodin3D_Detail": { @@ -9833,6 +11618,11 @@ "steps": { "name": "步驟" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SD_4XUpscale_Conditioning": { @@ -9986,14 +11776,14 @@ "name": "Sigma 值" } }, - "outputs": { - "0": { - "name": "輸出" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "去噪輸出" + { + "tooltip": null } - } + ] }, "SamplerCustomAdvanced": { "display_name": "SamplerCustomAdvanced", @@ -10014,14 +11804,14 @@ "name": "Sigma 值" } }, - "outputs": { - "0": { - "name": "輸出" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "去噪輸出" + { + "tooltip": null } - } + ] }, "SamplerDPMAdaptative": { "display_name": "SamplerDPMAdaptative", @@ -10056,6 +11846,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_2M_SDE": { @@ -10073,6 +11868,11 @@ "solver_type": { "name": "solver_type" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_2S_Ancestral": { @@ -10084,6 +11884,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_3M_SDE": { @@ -10098,6 +11903,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_SDE": { @@ -10115,6 +11925,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerER_SDE": { @@ -10133,6 +11948,11 @@ "solver_type": { "name": "solver_type" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerAncestral": { @@ -10144,6 +11964,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerAncestralCFGPP": { @@ -10155,6 +11980,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerCFGpp": { @@ -10195,6 +12025,11 @@ "order": { "name": "順序" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerSASolver": { @@ -10227,6 +12062,37 @@ "use_pece": { "name": "use_pece" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerSEEDS2": { + "description": "此採樣器節點可代表多種採樣器:\n\nseeds_2\n- 預設設定\n\nexp_heun_2_x0\n- solver_type=phi_2, r=1.0, eta=0.0\n\nexp_heun_2_x0_sde\n- solver_type=phi_2, r=1.0, eta=1.0, s_noise=1.0", + "display_name": "SamplerSEEDS2", + "inputs": { + "eta": { + "name": "eta", + "tooltip": "隨機強度" + }, + "r": { + "name": "r", + "tooltip": "中間階段的相對步長(c2 節點)" + }, + "s_noise": { + "name": "s_noise", + "tooltip": "SDE 噪聲倍率" + }, + "solver_type": { + "name": "solver_type" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplingPercentToSigma": { @@ -10243,11 +12109,11 @@ "name": "sampling_percent" } }, - "outputs": { - "0": { - "name": "sigma_value" + "outputs": [ + { + "tooltip": null } - } + ] }, "SaveAnimatedPNG": { "display_name": "SaveAnimatedPNG", @@ -10365,6 +12231,44 @@ } } }, + "SaveImageDataSetToFolder": { + "display_name": "儲存影像資料集到資料夾", + "inputs": { + "filename_prefix": { + "name": "filename_prefix", + "tooltip": "儲存影像檔名的前綴。" + }, + "folder_name": { + "name": "folder_name", + "tooltip": "要儲存影像的資料夾名稱(於輸出目錄內)。" + }, + "images": { + "name": "images", + "tooltip": "要儲存的影像清單。" + } + } + }, + "SaveImageTextDataSetToFolder": { + "display_name": "儲存影像與文字資料集到資料夾", + "inputs": { + "filename_prefix": { + "name": "filename_prefix", + "tooltip": "儲存影像檔名的前綴。" + }, + "folder_name": { + "name": "folder_name", + "tooltip": "要儲存影像的資料夾名稱(於輸出目錄內)。" + }, + "images": { + "name": "images", + "tooltip": "要儲存的影像清單。" + }, + "texts": { + "name": "texts", + "tooltip": "要儲存的文字標註清單。" + } + } + }, "SaveImageWebsocket": { "display_name": "SaveImageWebsocket", "inputs": { @@ -10384,7 +12288,7 @@ } } }, - "SaveLoRANode": { + "SaveLoRA": { "display_name": "儲存 LoRA 權重", "inputs": { "lora": { @@ -10393,11 +12297,11 @@ }, "prefix": { "name": "prefix", - "tooltip": "用於儲存 LoRA 檔案的前綴。" + "tooltip": "儲存 LoRA 檔案時使用的前綴。" }, "steps": { - "name": "步數", - "tooltip": "選填:LoRA 已訓練的步數,用於命名儲存的檔案。" + "name": "steps", + "tooltip": "選填:LoRA 訓練的步數,將用於命名儲存檔案。" } } }, @@ -10414,6 +12318,27 @@ } } }, + "SaveTrainingDataset": { + "display_name": "儲存訓練資料集", + "inputs": { + "conditioning": { + "name": "conditioning", + "tooltip": "來自 MakeTrainingDataset 的 conditioning 清單。" + }, + "folder_name": { + "name": "folder_name", + "tooltip": "要儲存資料集的資料夾名稱(於輸出目錄內)。" + }, + "latents": { + "name": "latents", + "tooltip": "來自 MakeTrainingDataset 的 latent 字典清單。" + }, + "shard_size": { + "name": "shard_size", + "tooltip": "每個分片檔案的樣本數。" + } + } + }, "SaveVideo": { "description": "將輸入的影像儲存到您的 ComfyUI 輸出目錄。", "display_name": "儲存影片", @@ -10534,6 +12459,11 @@ "sigmas": { "name": "sigmas" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SetHookKeyframes": { @@ -10574,6 +12504,58 @@ } } }, + "ShuffleDataset": { + "display_name": "隨機重排影像資料集", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images", + "tooltip": "要處理的影像清單。" + }, + "seed": { + "name": "seed", + "tooltip": "隨機種子。" + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "已處理的影像" + } + } + }, + "ShuffleImageTextDataset": { + "display_name": "隨機重排影像-文字資料集", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images", + "tooltip": "要隨機重排的影像清單。" + }, + "seed": { + "name": "seed", + "tooltip": "隨機種子。" + }, + "texts": { + "name": "texts", + "tooltip": "要隨機重排的文字清單。" + } + }, + "outputs": { + "0": { + "name": "images", + "tooltip": "已隨機重排的影像" + }, + "1": { + "name": "texts", + "tooltip": "已隨機重排的文字" + } + } + }, "SkipLayerGuidanceDiT": { "description": "可用於所有 DiT 模型的通用 SkipLayerGuidance 節點版本。", "display_name": "跳過層引導 DiT", @@ -10670,6 +12652,11 @@ "width": { "name": "寬度" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SplitAudioChannels": { @@ -10680,14 +12667,14 @@ "name": "音訊" } }, - "outputs": { - "0": { - "name": "左聲道" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "右聲道" + { + "tooltip": null } - } + ] }, "SplitImageWithAlpha": { "display_name": "以 Alpha 通道分割影像", @@ -10715,14 +12702,14 @@ "name": "步驟" } }, - "outputs": { - "0": { - "name": "高 sigmas" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "低 sigmas" + { + "tooltip": null } - } + ] }, "SplitSigmasDenoise": { "display_name": "分割 Sigmas(去噪)", @@ -10734,14 +12721,14 @@ "name": "sigmas" } }, - "outputs": { - "0": { - "name": "高 sigmas" + "outputs": [ + { + "tooltip": null }, - "1": { - "name": "低 sigmas" + { + "tooltip": null } - } + ] }, "StabilityAudioInpaint": { "description": "使用文字指令轉換現有音訊樣本的部分內容。", @@ -11343,6 +13330,21 @@ } } }, + "StripWhitespace": { + "display_name": "去除空白字元", + "inputs": { + "texts": { + "name": "文字", + "tooltip": "要處理的文字。" + } + }, + "outputs": { + "0": { + "name": "文字", + "tooltip": "已處理的文字" + } + } + }, "StyleModelApply": { "display_name": "套用風格模型", "inputs": { @@ -11428,6 +13430,84 @@ } } }, + "TencentImageToModelNode": { + "display_name": "Hunyuan3D:圖片轉模型(專業版)", + "inputs": { + "control_after_generate": { + "name": "生成後控制" + }, + "face_count": { + "name": "面數" + }, + "generate_type": { + "name": "生成類型" + }, + "generate_type_pbr": { + "name": "PBR" + }, + "image": { + "name": "圖片" + }, + "image_back": { + "name": "背面圖片" + }, + "image_left": { + "name": "左側圖片" + }, + "image_right": { + "name": "右側圖片" + }, + "model": { + "name": "模型", + "tooltip": "「LowPoly」選項在 `3.1` 模型中不可用。" + }, + "seed": { + "name": "種子", + "tooltip": "種子控制節點是否重新執行;無論種子如何,結果皆為非確定性。" + } + }, + "outputs": { + "0": { + "name": "模型檔案", + "tooltip": null + } + } + }, + "TencentTextToModelNode": { + "display_name": "Hunyuan3D:文字轉模型(專業版)", + "inputs": { + "control_after_generate": { + "name": "生成後控制" + }, + "face_count": { + "name": "面數" + }, + "generate_type": { + "name": "生成類型" + }, + "generate_type_pbr": { + "name": "PBR" + }, + "model": { + "name": "模型", + "tooltip": "「LowPoly」選項在 `3.1` 模型中不可用。" + }, + "prompt": { + "name": "提示詞", + "tooltip": "最多支援 1024 個字元。" + }, + "seed": { + "name": "種子", + "tooltip": "種子控制節點是否重新執行;無論種子如何,結果皆為非確定性。" + } + }, + "outputs": { + "0": { + "name": "模型檔案", + "tooltip": null + } + } + }, "TextEncodeAceStepAudio": { "display_name": "TextEncodeAceStepAudio", "inputs": { @@ -11523,6 +13603,70 @@ } } }, + "TextEncodeZImageOmni": { + "display_name": "TextEncodeZImageOmni", + "inputs": { + "auto_resize_images": { + "name": "自動調整圖像尺寸" + }, + "clip": { + "name": "clip" + }, + "image1": { + "name": "圖像1" + }, + "image2": { + "name": "圖像2" + }, + "image3": { + "name": "圖像3" + }, + "image_encoder": { + "name": "圖像編碼器" + }, + "prompt": { + "name": "提示詞" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TextToLowercase": { + "display_name": "轉換為小寫", + "inputs": { + "texts": { + "name": "文字", + "tooltip": "要處理的文字。" + } + }, + "outputs": { + "0": { + "name": "文字", + "tooltip": "已處理的文字" + } + } + }, + "TextToUppercase": { + "display_name": "轉換為大寫", + "inputs": { + "texts": { + "name": "文字", + "tooltip": "要處理的文字。" + } + }, + "outputs": { + "0": { + "name": "文字", + "tooltip": "已處理的文字" + } + } + }, "ThresholdMask": { "display_name": "闾值遮罩", "inputs": { @@ -11532,6 +13676,11 @@ "value": { "name": "數值" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "TomePatchModel": { @@ -11550,6 +13699,118 @@ } } }, + "TopazImageEnhance": { + "description": "業界標準的放大與影像增強。", + "display_name": "Topaz 影像增強", + "inputs": { + "color_preservation": { + "name": "色彩保留", + "tooltip": "保留原始色彩。" + }, + "creativity": { + "name": "創意度" + }, + "crop_to_fill": { + "name": "裁切填滿", + "tooltip": "預設當輸出長寬比不同時會加黑邊。啟用後將裁切影像以填滿輸出尺寸。" + }, + "face_enhancement": { + "name": "臉部增強", + "tooltip": "處理時增強臉部(如有)。" + }, + "face_enhancement_creativity": { + "name": "臉部增強創意度", + "tooltip": "設定臉部增強的創意程度。" + }, + "face_enhancement_strength": { + "name": "臉部增強強度", + "tooltip": "控制增強臉部相對於背景的銳利度。" + }, + "face_preservation": { + "name": "臉部保留", + "tooltip": "保留主體的臉部特徵。" + }, + "image": { + "name": "影像" + }, + "model": { + "name": "模型" + }, + "output_height": { + "name": "輸出高度", + "tooltip": "設為 0 代表與原始高度或指定的輸出寬度相同。" + }, + "output_width": { + "name": "輸出寬度", + "tooltip": "設為 0 代表自動計算(通常為原始尺寸或指定的輸出高度)。" + }, + "prompt": { + "name": "提示詞", + "tooltip": "可選的文字提示,用於創意放大指引。" + }, + "subject_detection": { + "name": "主體偵測" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TopazVideoEnhance": { + "description": "以強大的升頻與修復技術為影片注入新生命。", + "display_name": "Topaz Video Enhance", + "inputs": { + "dynamic_compression_level": { + "name": "CQP 等級", + "tooltip": "CQP 等級。" + }, + "interpolation_duplicate": { + "name": "移除重複幀", + "tooltip": "分析輸入影片中的重複幀並移除。" + }, + "interpolation_duplicate_threshold": { + "name": "重複幀靈敏度", + "tooltip": "重複幀的偵測靈敏度。" + }, + "interpolation_enabled": { + "name": "啟用插幀" + }, + "interpolation_frame_rate": { + "name": "輸出幀率", + "tooltip": "輸出影片的幀率。" + }, + "interpolation_model": { + "name": "插幀模型" + }, + "interpolation_slowmo": { + "name": "慢動作倍數", + "tooltip": "對輸入影片應用的慢動作倍數。例如,2 會讓輸出變成原來的兩倍慢,時長加倍。" + }, + "upscaler_creativity": { + "name": "創意程度", + "tooltip": "創意等級(僅適用於 Starlight (Astra) Creative)。" + }, + "upscaler_enabled": { + "name": "啟用升頻" + }, + "upscaler_model": { + "name": "升頻模型" + }, + "upscaler_resolution": { + "name": "升頻解析度" + }, + "video": { + "name": "影片" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "TorchCompileModel": { "display_name": "Torch 編譯模型", "inputs": { @@ -11577,6 +13838,10 @@ "name": "批次大小", "tooltip": "用於訓練的批次大小。" }, + "bucket_mode": { + "name": "解析度分桶模式", + "tooltip": "啟用解析度分桶模式。啟用後,需從 ResolutionBucket 節點輸入預先分桶的 latent。" + }, "control_after_generate": { "name": "生成後控制" }, @@ -11637,20 +13902,20 @@ "tooltip": "訓練時使用的資料類型。" } }, - "outputs": { - "0": { - "name": "含 LoRA 的模型" + "outputs": [ + { + "tooltip": "已套用 LoRA 的模型" }, - "1": { - "name": "LoRA" + { + "tooltip": "LoRA 權重" }, - "2": { - "name": "損失" + { + "tooltip": "損失歷史" }, - "3": { - "name": "步數" + { + "tooltip": "總訓練步數" } - } + ] }, "TrimAudioDuration": { "description": "將音訊張量修剪至選定的時間範圍。", @@ -11667,6 +13932,11 @@ "name": "起始索引", "tooltip": "開始時間(秒),可為負數表示從末尾計算(支援小數秒)。" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "TrimVideoLatent": { @@ -11708,23 +13978,62 @@ "TripoConversionNode": { "display_name": "Tripo:轉換模型", "inputs": { + "animate_in_place": { + "name": "原地動畫" + }, + "bake": { + "name": "烘焙" + }, + "export_orientation": { + "name": "匯出方向" + }, + "export_vertex_colors": { + "name": "匯出頂點色" + }, "face_limit": { "name": "面數限制" }, + "fbx_preset": { + "name": "FBX 預設" + }, + "flatten_bottom": { + "name": "底部平整化" + }, + "flatten_bottom_threshold": { + "name": "平整化閾值" + }, + "force_symmetry": { + "name": "強制對稱" + }, "format": { "name": "格式" }, "original_model_task_id": { "name": "原始模型任務ID" }, + "pack_uv": { + "name": "打包 UV" + }, + "part_names": { + "name": "部件名稱" + }, + "pivot_to_center_bottom": { + "name": "樞軸移至底部中心" + }, "quad": { "name": "四邊形" }, + "scale_factor": { + "name": "縮放係數" + }, "texture_format": { "name": "紋理格式" }, "texture_size": { "name": "紋理尺寸" + }, + "with_animation": { + "name": "包含動畫" } } }, @@ -11734,6 +14043,9 @@ "face_limit": { "name": "面數限制" }, + "geometry_quality": { + "name": "幾何品質" + }, "image": { "name": "圖像" }, @@ -11786,6 +14098,9 @@ "face_limit": { "name": "face_limit" }, + "geometry_quality": { + "name": "幾何品質" + }, "image": { "name": "圖像" }, @@ -11903,6 +14218,9 @@ "face_limit": { "name": "面數限制" }, + "geometry_quality": { + "name": "幾何品質" + }, "image_seed": { "name": "圖片種子" }, @@ -11981,6 +14299,25 @@ } } }, + "TruncateText": { + "display_name": "截斷文字", + "inputs": { + "max_length": { + "name": "最大長度", + "tooltip": "文字最大長度。" + }, + "texts": { + "name": "文字", + "tooltip": "要處理的文字。" + } + }, + "outputs": { + "0": { + "name": "文字", + "tooltip": "已處理的文字" + } + } + }, "UNETLoader": { "display_name": "載入擴散模型", "inputs": { @@ -12122,6 +14459,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEDecodeHunyuan3D": { @@ -12139,6 +14481,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEDecodeTiled": { @@ -12186,6 +14533,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEEncodeForInpaint": { @@ -12264,6 +14616,63 @@ "steps": { "name": "步驟數" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Veo3FirstLastFrameNode": { + "description": "使用提示詞及首尾影格生成影片。", + "display_name": "Google Veo 3 首尾影格生成影片", + "inputs": { + "aspect_ratio": { + "name": "長寬比", + "tooltip": "輸出影片的長寬比" + }, + "control_after_generate": { + "name": "生成後控制" + }, + "duration": { + "name": "時長", + "tooltip": "輸出影片的秒數" + }, + "first_frame": { + "name": "起始影格", + "tooltip": "開始影格" + }, + "generate_audio": { + "name": "生成音訊", + "tooltip": "為影片生成音訊。" + }, + "last_frame": { + "name": "結束影格", + "tooltip": "結束影格" + }, + "model": { + "name": "模型" + }, + "negative_prompt": { + "name": "反向提示詞", + "tooltip": "用於引導影片中應避免內容的反向文字提示" + }, + "prompt": { + "name": "提示詞", + "tooltip": "影片的文字描述" + }, + "resolution": { + "name": "解析度" + }, + "seed": { + "name": "種子", + "tooltip": "影片生成的隨機種子" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Veo3VideoGenerationNode": { @@ -12392,6 +14801,166 @@ } } }, + "Vidu2ImageToVideoNode": { + "description": "從一張圖像及可選提示詞生成影片。", + "display_name": "Vidu2 圖像轉影片生成", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "duration": { + "name": "duration" + }, + "image": { + "name": "image", + "tooltip": "用作生成影片起始畫格的圖像。" + }, + "model": { + "name": "model" + }, + "movement_amplitude": { + "name": "movement_amplitude", + "tooltip": "畫面中物件的移動幅度。" + }, + "prompt": { + "name": "prompt", + "tooltip": "影片生成的可選文字提示(最多 2000 字元)。" + }, + "resolution": { + "name": "resolution" + }, + "seed": { + "name": "seed" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2ReferenceVideoNode": { + "description": "從多張參考圖像及提示詞生成影片。", + "display_name": "Vidu2 參考圖像轉影片生成", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "audio": { + "name": "audio", + "tooltip": "啟用後,影片將根據提示詞包含生成的語音及背景音樂。" + }, + "control_after_generate": { + "name": "control after generate" + }, + "duration": { + "name": "duration" + }, + "model": { + "name": "model" + }, + "movement_amplitude": { + "name": "movement_amplitude", + "tooltip": "畫面中物件的移動幅度。" + }, + "prompt": { + "name": "prompt", + "tooltip": "啟用後,影片將根據提示詞生成語音及背景音樂。" + }, + "resolution": { + "name": "resolution" + }, + "seed": { + "name": "seed" + }, + "subjects": { + "name": "subjects", + "tooltip": "每個主體最多可提供 3 張參考圖像(所有主體合計最多 7 張)。可在提示詞中以 @subject{subject_id} 參照。" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2StartEndToVideoNode": { + "description": "根據起始影格、結束影格與提示詞生成影片。", + "display_name": "Vidu2 起始/結束影格轉影片生成", + "inputs": { + "control_after_generate": { + "name": "生成後控制" + }, + "duration": { + "name": "duration" + }, + "end_frame": { + "name": "end_frame" + }, + "first_frame": { + "name": "first_frame" + }, + "model": { + "name": "model" + }, + "movement_amplitude": { + "name": "movement_amplitude", + "tooltip": "畫面中物件的移動幅度。" + }, + "prompt": { + "name": "prompt", + "tooltip": "提示描述(最多 2000 字元)。" + }, + "resolution": { + "name": "resolution" + }, + "seed": { + "name": "seed" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2TextToVideoNode": { + "description": "根據文字提示生成影片", + "display_name": "Vidu2 文字轉影片生成", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "background_music": { + "name": "background_music", + "tooltip": "是否為生成的影片加入背景音樂。" + }, + "control_after_generate": { + "name": "生成後控制" + }, + "duration": { + "name": "duration" + }, + "model": { + "name": "model" + }, + "prompt": { + "name": "prompt", + "tooltip": "用於影片生成的文字描述,最長 2000 字元。" + }, + "resolution": { + "name": "resolution" + }, + "seed": { + "name": "seed" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "ViduImageToVideoNode": { "description": "從圖像和可選提示生成影片", "display_name": "Vidu 圖像轉影片生成", @@ -12580,6 +15149,11 @@ "voxel": { "name": "voxel" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VoxelToMeshBasic": { @@ -12591,6 +15165,11 @@ "voxel": { "name": "voxel" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Wan22FunControlToVideo": { @@ -12870,6 +15449,10 @@ "name": "上下文步幅", "tooltip": "上下文窗口的步幅;僅適用於均勻排程。" }, + "freenoise": { + "name": "freenoise", + "tooltip": "是否套用 FreeNoise 噪聲洗牌,可提升視窗融合效果。" + }, "fuse_method": { "name": "融合方法", "tooltip": "用於融合上下文窗口的方法。" @@ -13210,6 +15793,10 @@ "name": "種子值", "tooltip": "用於生成的種子值。" }, + "shot_type": { + "name": "鏡頭類型", + "tooltip": "指定生成影片的鏡頭類型,即影片是單一連續鏡頭還是多鏡頭剪接。此參數僅在 prompt_extend 為 True 時生效。" + }, "watermark": { "name": "浮水印", "tooltip": "是否在結果中添加「AI生成」浮水印。" @@ -13221,6 +15808,196 @@ } } }, + "WanInfiniteTalkToVideo": { + "display_name": "WanInfiniteTalkToVideo", + "inputs": { + "audio_encoder_output_1": { + "name": "音訊編碼器輸出 1" + }, + "audio_scale": { + "name": "音訊縮放" + }, + "clip_vision_output": { + "name": "clip 視覺輸出" + }, + "height": { + "name": "高度" + }, + "length": { + "name": "長度" + }, + "mode": { + "name": "模式" + }, + "model": { + "name": "模型" + }, + "model_patch": { + "name": "模型修補" + }, + "motion_frame_count": { + "name": "動作影格數", + "tooltip": "用作動作參考的前置影格數量。" + }, + "negative": { + "name": "負向提示" + }, + "positive": { + "name": "正向提示" + }, + "previous_frames": { + "name": "前置影格" + }, + "start_image": { + "name": "起始圖像" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "寬度" + } + }, + "outputs": { + "0": { + "name": "模型", + "tooltip": null + }, + "1": { + "name": "正向提示", + "tooltip": null + }, + "2": { + "name": "負向提示", + "tooltip": null + }, + "3": { + "name": "latent", + "tooltip": null + }, + "4": { + "name": "裁切圖像", + "tooltip": null + } + } + }, + "WanMoveConcatTrack": { + "display_name": "WanMoveConcatTrack", + "inputs": { + "tracks_1": { + "name": "tracks_1" + }, + "tracks_2": { + "name": "tracks_2" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanMoveTrackToVideo": { + "display_name": "WanMoveTrackToVideo", + "inputs": { + "batch_size": { + "name": "批次大小" + }, + "clip_vision_output": { + "name": "clip_vision_output" + }, + "height": { + "name": "高度" + }, + "length": { + "name": "長度" + }, + "negative": { + "name": "negative" + }, + "positive": { + "name": "positive" + }, + "start_image": { + "name": "起始影像" + }, + "strength": { + "name": "強度", + "tooltip": "軌跡條件的強度。" + }, + "tracks": { + "name": "軌跡" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "寬度" + } + }, + "outputs": { + "0": { + "name": "positive", + "tooltip": null + }, + "1": { + "name": "negative", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "WanMoveTracksFromCoords": { + "display_name": "WanMoveTracksFromCoords", + "inputs": { + "track_coords": { + "name": "track_coords" + }, + "track_mask": { + "name": "track_mask" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "track_length", + "tooltip": null + } + } + }, + "WanMoveVisualizeTracks": { + "display_name": "WanMoveVisualizeTracks", + "inputs": { + "circle_size": { + "name": "圓圈大小" + }, + "images": { + "name": "影像" + }, + "line_resolution": { + "name": "線條解析度" + }, + "line_width": { + "name": "線寬" + }, + "opacity": { + "name": "不透明度" + }, + "tracks": { + "name": "軌跡" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WanPhantomSubjectToVideo": { "display_name": "萬幻影主體轉影片", "inputs": { @@ -13268,6 +16045,51 @@ } } }, + "WanReferenceVideoApi": { + "description": "使用輸入影片中的角色與聲音,結合提示詞,生成一個保持角色一致性的新影片。", + "display_name": "Wan Reference to Video", + "inputs": { + "control_after_generate": { + "name": "生成後控制" + }, + "duration": { + "name": "時長" + }, + "model": { + "name": "模型" + }, + "negative_prompt": { + "name": "反向提示詞", + "tooltip": "描述需要避免內容的反向提示詞。" + }, + "prompt": { + "name": "提示詞", + "tooltip": "描述元素與視覺特徵的提示詞。支援英文與中文。可使用 `character1`、`character2` 等標識來指代參考角色。" + }, + "reference_videos": { + "name": "參考影片" + }, + "seed": { + "name": "隨機種子" + }, + "shot_type": { + "name": "鏡頭類型", + "tooltip": "指定生成影片的鏡頭類型,即影片是單一連續鏡頭還是多個剪接鏡頭。" + }, + "size": { + "name": "尺寸" + }, + "watermark": { + "name": "浮水印", + "tooltip": "是否在結果中加入 AI 生成的浮水印。" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WanSoundImageToVideo": { "display_name": "萬聲圖像轉影片", "inputs": { @@ -13446,6 +16268,10 @@ "name": "種子值", "tooltip": "用於生成的種子值。" }, + "shot_type": { + "name": "鏡頭類型", + "tooltip": "指定生成影片的鏡頭類型,即影片是單一連續鏡頭還是多個剪接鏡頭。此參數僅在 prompt_extend 為 True 時生效。" + }, "size": { "name": "尺寸" }, @@ -13571,6 +16397,43 @@ } } }, + "WavespeedFlashVSRNode": { + "description": "快速高品質影片升頻器,提升解析度並恢復低解析度或模糊影片的清晰度。", + "display_name": "FlashVSR 影片升頻", + "inputs": { + "target_resolution": { + "name": "目標解析度" + }, + "video": { + "name": "影片" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WavespeedImageUpscaleNode": { + "description": "提升圖像解析度與品質,將照片升頻至 4K 或 8K,獲得銳利細緻的效果。", + "display_name": "WaveSpeed 圖像升頻", + "inputs": { + "image": { + "name": "圖像" + }, + "model": { + "name": "model" + }, + "target_resolution": { + "name": "目標解析度" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WebcamCapture": { "display_name": "網路攝影機擷取", "inputs": { @@ -13590,6 +16453,32 @@ } } }, + "ZImageFunControlnet": { + "display_name": "ZImageFunControlnet", + "inputs": { + "image": { + "name": "影像" + }, + "inpaint_image": { + "name": "修補影像" + }, + "mask": { + "name": "遮罩" + }, + "model": { + "name": "模型" + }, + "model_patch": { + "name": "模型補丁" + }, + "strength": { + "name": "強度" + }, + "vae": { + "name": "vae" + } + } + }, "unCLIPCheckpointLoader": { "display_name": "unCLIP 檢查點載入器", "inputs": { @@ -13614,5 +16503,19 @@ "name": "強度" } } + }, + "wanBlockSwap": { + "description": "NOP", + "display_name": "wanBlockSwap", + "inputs": { + "model": { + "name": "模型" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } } } diff --git a/src/locales/zh-TW/settings.json b/src/locales/zh-TW/settings.json index 291596e4c..44e745e2d 100644 --- a/src/locales/zh-TW/settings.json +++ b/src/locales/zh-TW/settings.json @@ -29,12 +29,26 @@ "name": "畫布背景圖片", "tooltip": "畫布背景的圖片網址。你可以在輸出面板中右鍵點擊圖片並選擇「設為背景」來使用,或是使用上傳按鈕上傳你自己的圖片。" }, + "Comfy_Canvas_LeftMouseClickBehavior": { + "name": "左鍵點擊行為", + "options": { + "Panning": "平移", + "Select": "選取" + } + }, + "Comfy_Canvas_MouseWheelScroll": { + "name": "滑鼠滾輪捲動", + "options": { + "Panning": "平移", + "Zoom in/out": "縮放" + } + }, "Comfy_Canvas_NavigationMode": { "name": "畫布導航模式", "options": { + "Custom": "自訂", "Drag Navigation": "拖曳導覽", - "Standard (New)": "標準(新)", - "Custom": "自訂" + "Standard (New)": "標準(新)" } }, "Comfy_Canvas_SelectionToolbox": { @@ -65,6 +79,17 @@ "Comfy_EnableWorkflowViewRestore": { "name": "在工作流程中儲存並還原畫布位置與縮放等級" }, + "Comfy_Execution_PreviewMethod": { + "name": "即時預覽方式", + "options": { + "auto": "auto", + "default": "default", + "latent2rgb": "latent2rgb", + "none": "none", + "taesd": "taesd" + }, + "tooltip": "在圖像生成過程中使用的即時預覽方式。「default」會使用伺服器 CLI 設定。" + }, "Comfy_FloatRoundingPrecision": { "name": "浮點元件小數點位數 [0 = 自動]。", "tooltip": "(需重新載入頁面)" @@ -86,6 +111,10 @@ "None": "無" } }, + "Comfy_Graph_LiveSelection": { + "name": "即時選取", + "tooltip": "啟用後,拖曳選取框時會即時選取/取消選取節點,類似其他設計工具的操作方式。" + }, "Comfy_Graph_ZoomSpeed": { "name": "畫布縮放速度" }, @@ -152,6 +181,15 @@ "name": "光源強度下限", "tooltip": "設定 3D 場景中允許的最小光源強度值。這會定義在調整任何 3D 控制元件照明時可設定的最低亮度限制。" }, + "Comfy_Load3D_PLYEngine": { + "name": "PLY 引擎", + "options": { + "fastply": "fastply", + "sparkjs": "sparkjs", + "threejs": "threejs" + }, + "tooltip": "選擇用於載入 PLY 檔案的引擎。「threejs」使用原生 Three.js PLYLoader(最適合網格 PLY 檔案)。「fastply」使用針對 ASCII 點雲 PLY 檔案優化的載入器。「sparkjs」則用於 3D Gaussian Splatting PLY 檔案。" + }, "Comfy_Load3D_ShowGrid": { "name": "初始網格可見性", "tooltip": "控制在建立新的 3D 元件時,網格是否預設可見。此預設值在建立後仍可針對每個元件單獨切換。" @@ -167,10 +205,6 @@ "name": "鎖定筆刷調整至主軸", "tooltip": "啟用後,筆刷調整只會根據你移動較多的方向,分別影響大小或硬度" }, - "Comfy_MaskEditor_UseNewEditor": { - "name": "使用新遮罩編輯器", - "tooltip": "切換到新遮罩編輯器介面" - }, "Comfy_ModelLibrary_AutoLoadAll": { "name": "自動載入所有模型資料夾", "tooltip": "若為開啟,當你打開模型庫時,所有資料夾將自動載入(這可能會導致載入時延遲)。若為關閉,只有在你點擊根目錄下的模型資料夾時才會載入。" @@ -238,6 +272,10 @@ "Comfy_Node_AllowImageSizeDraw": { "name": "在圖片預覽下方顯示寬度 × 高度" }, + "Comfy_Node_AlwaysShowAdvancedWidgets": { + "name": "在所有節點上總是顯示進階小工具", + "tooltip": "啟用後,所有節點的進階小工具將始終可見,無需單獨展開。" + }, "Comfy_Node_AutoSnapLinkToSlot": { "name": "自動吸附連結到節點插槽", "tooltip": "拖曳連結到節點時,連結會自動吸附到節點上可用的輸入插槽" @@ -298,6 +336,10 @@ "name": "佇列歷史記錄大小", "tooltip": "佇列歷史中顯示的最大任務數量。" }, + "Comfy_Queue_QPOV2": { + "name": "在資產側邊欄中使用統一任務佇列", + "tooltip": "將浮動任務佇列面板替換為嵌入資產側邊欄的等效任務佇列。您可以停用此選項以恢復浮動面板佈局。" + }, "Comfy_Sidebar_Location": { "name": "側邊欄位置", "options": { @@ -312,6 +354,13 @@ "small": "小" } }, + "Comfy_Sidebar_Style": { + "name": "側邊欄樣式", + "options": { + "connected": "連接式", + "floating": "浮動式" + } + }, "Comfy_Sidebar_UnifiedWidth": { "name": "統一側邊欄寬度" }, @@ -328,6 +377,14 @@ "Comfy_TreeExplorer_ItemPadding": { "name": "樹狀瀏覽器項目間距" }, + "Comfy_UI_TabBarLayout": { + "name": "分頁列佈局", + "options": { + "Default": "預設", + "Integrated": "整合" + }, + "tooltip": "控制分頁列的佈局。「整合」會將說明和使用者控制項移至分頁列區域。" + }, "Comfy_UseNewMenu": { "name": "使用新選單", "options": { @@ -339,6 +396,14 @@ "Comfy_Validation_Workflows": { "name": "驗證工作流程" }, + "Comfy_VueNodes_AutoScaleLayout": { + "name": "自動縮放佈局 (Vue 節點)", + "tooltip": "切換至 Vue 渲染時自動縮放節點位置以防止重疊" + }, + "Comfy_VueNodes_Enabled": { + "name": "現代節點設計 (Vue 節點)", + "tooltip": "現代:基於 DOM 的渲染,具備增強的互動性、原生瀏覽器功能和更新的視覺設計。經典:傳統畫布渲染。" + }, "Comfy_WidgetControlMode": { "name": "元件控制模式", "options": { @@ -376,6 +441,9 @@ "Comfy_Workflow_SortNodeIdOnSave": { "name": "儲存工作流程時排序節點 ID" }, + "Comfy_Workflow_WarnBlueprintOverwrite": { + "name": "覆蓋現有子圖藍圖前需要確認" + }, "Comfy_Workflow_WorkflowTabsPosition": { "name": "已開啟工作流程的位置", "options": { @@ -407,37 +475,5 @@ }, "pysssss_SnapToGrid": { "name": "總是對齊格線" - }, - "Comfy_Canvas_LeftMouseClickBehavior": { - "name": "左鍵點擊行為", - "options": { - "Panning": "平移", - "Select": "選取" - } - }, - "Comfy_Canvas_MouseWheelScroll": { - "name": "滑鼠滾輪捲動", - "options": { - "Panning": "平移", - "Zoom in/out": "縮放" - } - }, - "Comfy_Sidebar_Style": { - "name": "側邊欄樣式", - "options": { - "floating": "浮動式", - "connected": "連接式" - } - }, - "Comfy_VueNodes_AutoScaleLayout": { - "name": "自動縮放佈局 (Vue 節點)", - "tooltip": "切換至 Vue 渲染時自動縮放節點位置以防止重疊" - }, - "Comfy_VueNodes_Enabled": { - "name": "現代節點設計 (Vue 節點)", - "tooltip": "現代:基於 DOM 的渲染,具備增強的互動性、原生瀏覽器功能和更新的視覺設計。經典:傳統畫布渲染。" - }, - "Comfy_Workflow_WarnBlueprintOverwrite": { - "name": "覆蓋現有子圖藍圖前需要確認" } } diff --git a/src/locales/zh/commands.json b/src/locales/zh/commands.json index ff4773565..5b830c37a 100644 --- a/src/locales/zh/commands.json +++ b/src/locales/zh/commands.json @@ -1,4 +1,40 @@ { + "Comfy-Desktop_CheckForUpdates": { + "label": "检查更新" + }, + "Comfy-Desktop_Folders_OpenCustomNodesFolder": { + "label": "打开自定义节点文件夹" + }, + "Comfy-Desktop_Folders_OpenInputsFolder": { + "label": "打开输入文件夹" + }, + "Comfy-Desktop_Folders_OpenLogsFolder": { + "label": "打开日志文件夹" + }, + "Comfy-Desktop_Folders_OpenModelConfig": { + "label": "打开 extra_model_paths.yaml" + }, + "Comfy-Desktop_Folders_OpenModelsFolder": { + "label": "打开模型文件夹" + }, + "Comfy-Desktop_Folders_OpenOutputsFolder": { + "label": "打开输出文件夹" + }, + "Comfy-Desktop_OpenDevTools": { + "label": "打开开发者工具" + }, + "Comfy-Desktop_OpenUserGuide": { + "label": "桌面端用户手册" + }, + "Comfy-Desktop_Quit": { + "label": "退出" + }, + "Comfy-Desktop_Reinstall": { + "label": "重装" + }, + "Comfy-Desktop_Restart": { + "label": "重启" + }, "Comfy_3DViewer_Open3DViewer": { "label": "为所选节点开启 3D 浏览器(Beta 版)" }, @@ -155,18 +191,30 @@ "Comfy_Manager_ShowUpdateAvailablePacks": { "label": "检查更新" }, - "Comfy_Manager_ToggleManagerProgressDialog": { - "label": "切换进度对话框" - }, "Comfy_MaskEditor_BrushSize_Decrease": { "label": "减小 MaskEditor 中的笔刷大小" }, "Comfy_MaskEditor_BrushSize_Increase": { "label": "增加 MaskEditor 画笔大小" }, + "Comfy_MaskEditor_ColorPicker": { + "label": "在MaskEditor中打开取色器" + }, + "Comfy_MaskEditor_Mirror_Horizontal": { + "label": "在MaskEditor中水平镜像" + }, + "Comfy_MaskEditor_Mirror_Vertical": { + "label": "在MaskEditor中垂直镜像" + }, "Comfy_MaskEditor_OpenMaskEditor": { "label": "打开选中节点的遮罩编辑器" }, + "Comfy_MaskEditor_Rotate_Left": { + "label": "在MaskEditor中向左旋转" + }, + "Comfy_MaskEditor_Rotate_Right": { + "label": "在MaskEditor中向右旋转" + }, "Comfy_Memory_UnloadModels": { "label": "卸载模型" }, @@ -197,12 +245,18 @@ "Comfy_QueueSelectedOutputNodes": { "label": "队列所选输出节点" }, + "Comfy_Queue_ToggleOverlay": { + "label": "切换作业历史" + }, "Comfy_Redo": { "label": "重做" }, "Comfy_RefreshNodeDefinitions": { "label": "刷新节点定义" }, + "Comfy_RenameWorkflow": { + "label": "重命名工作流" + }, "Comfy_SaveWorkflow": { "label": "保存工作流" }, @@ -221,6 +275,12 @@ "Comfy_ToggleHelpCenter": { "label": "说明中心" }, + "Comfy_ToggleLinear": { + "label": "切换线性模式" + }, + "Comfy_ToggleQPOV2": { + "label": "切换队列面板V2" + }, "Comfy_ToggleTheme": { "label": "切换主题" }, diff --git a/src/locales/zh/main.json b/src/locales/zh/main.json index bad964f3a..02be0f523 100644 --- a/src/locales/zh/main.json +++ b/src/locales/zh/main.json @@ -1,6 +1,8 @@ { "actionbar": { - "dockToTop": "停靠到顶部" + "dockToTop": "停靠到顶部", + "feedback": "反馈", + "feedbackTooltip": "反馈" }, "apiNodesCostBreakdown": { "costPerRun": "每次运行的成本", @@ -16,25 +18,143 @@ "allModels": "全部模型", "ariaLabel": { "assetCard": "{name} - {type}资产", - "loadingAsset": "正在加载资源" + "loadingAsset": "正在加载资产" }, - "assets": "资源", + "assetCollection": "资产合集", + "assets": "资产", "baseModels": "基础模型", - "browseAssets": "浏览资源", + "browseAssets": "浏览资产", + "byType": "按类型", + "checkpoints": "模型", + "civitaiLinkExample": "案例: https://civitai.com/api/download/models/833921?type=Model&format=SafeTensor", + "civitaiLinkExampleStrong": "案例:", + "civitaiLinkExampleUrl": "https://civitai.com/models/10706/luisap-z-image-and-qwen-pixel-art-refiner?modelVersionId=2225295", + "civitaiLinkLabel": "Civitai 模型 download link", + "civitaiLinkLabelDownload": "下载", + "civitaiLinkPlaceholder": "粘贴链接到此", + "confirmModelDetails": "确认模型信息", "connectionError": "请检查您的网络连接后重试", + "deletion": { + "body": "从资产库永久移除这个模型。", + "complete": "{assetName} 已经删除。", + "failed": "{assetName} 无法删除。", + "header": "删除该模型?", + "inProgress": "正在删除 {assetName}..." + }, + "download": { + "complete": "下载完成", + "failed": "下载失败", + "inProgress": "正在下载 {assetName}..." + }, + "emptyImported": { + "canImport": "尚未导入模型。点击“导入模型”添加您的模型。", + "restricted": "个人模型仅限创作者及以上等级使用。" + }, + "errorFileTooLarge": "允许执行文件的文件大小限制", + "errorFormatNotAllowed": "仅允许 SafeTensor 格式", + "errorModelTypeNotSupported": "不支持该类型的模型", + "errorUnknown": "发生了意外错误", + "errorUnsafePickleScan": "CivitAI 在该文件中检测到不安全代码", + "errorUnsafeVirusScan": "CivitAI 在该文件中检测到木马或可疑文件", + "errorUploadFailed": "导入资产失败,请重试。", "failedToCreateNode": "创建节点失败。请重试或查看控制台获取详细信息。", "fileFormats": "文件格式", + "fileName": "文件名", + "fileSize": "文件大小", + "filterBy": "筛选方式", + "findInLibrary": "模型在模型库的{type}区里", + "finish": "完成", + "genericLinkPlaceholder": "粘贴链接到这", + "importAnother": "导入其他", + "imported": "已导入", + "jobId": "任务ID", "loadingModels": "正在加载{type}...", - "noAssetsFound": "未找到资源", + "maxFileSize": "最大文件大小:{size}", + "maxFileSizeValue": "1 GB", + "media": { + "audioPlaceholder": "音频", + "threeDModelPlaceholder": "3D 模型" + }, + "modelAssociatedWithLink": "您提供的链接的模型:", + "modelInfo": { + "addBaseModel": "添加基础模型...", + "addTag": "添加标签...", + "additionalTags": "附加标签", + "baseModelUnknown": "基础模型未知", + "basicInfo": "基本信息", + "compatibleBaseModels": "兼容基础模型", + "description": "描述", + "descriptionNotSet": "未设置描述", + "descriptionPlaceholder": "为此模型添加描述...", + "displayName": "显示名称", + "editDisplayName": "编辑显示名称", + "fileName": "文件名", + "modelDescription": "模型描述", + "modelTagging": "模型标签", + "modelType": "模型类型", + "noAdditionalTags": "无附加标签", + "selectModelPrompt": "选择一个模型以查看其信息", + "selectModelType": "选择模型类型...", + "source": "来源", + "title": "模型信息", + "triggerPhrases": "触发短语", + "viewOnSource": "在 {source} 上查看" + }, + "modelName": "模型名", + "modelNamePlaceholder": "输入该模型的名称", + "modelTypeSelectorLabel": "这是什么类型的模型?", + "modelTypeSelectorPlaceholder": "选择模型类型", + "modelUploaded": "模型导入成功!🎉", + "noAssetsFound": "未找到资产", "noModelsInFolder": "此文件夹中没有可用的{type}", - "searchAssetsPlaceholder": "搜索资源...", + "noValidSourceDetected": "检测不到有效的导入源", + "notSureLeaveAsIs": "不确定?那就放着不管吧", + "onlyCivitaiUrlsSupported": "仅支持 Civitai 链接", + "ownership": "所属", + "ownershipAll": "全部", + "ownershipMyModels": "我的模型", + "ownershipPublicModels": "公共模型", + "processingModel": "已开始下载", + "processingModelDescription": "您可以关闭此对话框。下载将在后台继续。", + "providerCivitai": "Civitai", + "providerHuggingFace": "Hugging Face", + "rename": { + "failed": "无法重命名资产。" + }, + "selectFrameworks": "选择框架", + "selectModelType": "选择模型类型", + "selectProjects": "选择项目", "sortAZ": "A-Z", "sortBy": "排序方式", "sortPopular": "最受欢迎", "sortRecent": "最近", "sortZA": "Z-A", + "sortingType": "排序类型", + "tags": "标签", + "tagsHelp": "按逗号区分标签", + "tagsPlaceholder": "例如:模型, checkpoint", "tryAdjustingFilters": "请尝试调整搜索或筛选条件", - "unknown": "未知" + "unknown": "未知", + "unsupportedUrlSource": "仅支持来自 {sources} URL 的模型", + "upgradeFeatureDescription": "该功能仅 Creator 和 Pro 订阅计划可用", + "upgradeToUnlockFeature": "升级订阅解锁该功能", + "upload": "导入", + "uploadFailed": "导入失败", + "uploadModel": "导入模型", + "uploadModelDescription1": "粘贴 Civitai 模型下载链接,将其添加到库中。", + "uploadModelDescription1Generic": "站贴模型下载链接,将其添加到模型库中。", + "uploadModelDescription2": "目前仅支持 https://civitai.com 链接。", + "uploadModelDescription2Generic": "仅支持来自以下提供服务的 URL :", + "uploadModelDescription2Link": "https://civitai.com/models", + "uploadModelDescription3": "最大文件大小:1 GB", + "uploadModelFailedToRetrieveMetadata": "检索元数据失败。请检查连接并重试。", + "uploadModelFromCivitai": "从 Civitai 导入模型", + "uploadModelGeneric": "导入模型", + "uploadModelHelpFooterText": "需要查找 URL 的帮助吗?点击下面的教程视频。", + "uploadModelHelpVideo": "上传模型视频", + "uploadModelHowDoIFindThis": "如何找到这个?", + "uploadSuccess": "模型导入成功!", + "uploadingModel": "正在导入模型..." }, "auth": { "apiKey": { @@ -148,12 +268,19 @@ "title": "创建一个账户" } }, + "boundingBox": { + "height": "高度", + "width": "宽度", + "x": "X", + "y": "Y" + }, "breadcrumbsMenu": { "clearWorkflow": "清除工作流", "deleteBlueprint": "删除蓝图", "deleteWorkflow": "删除工作流", "duplicate": "复制", - "enterNewName": "输入新名称" + "enterNewName": "输入新名称", + "missingNodesWarning": "工作流包含不支持的节点(红色突出显示)。" }, "clipboard": { "errorMessage": "复制到剪贴板失败", @@ -207,6 +334,7 @@ }, "retry": "重试", "retrying": "正在重试...", + "skipToCloudApp": "跳转到云应用", "start": { "desc": "无需任何设置。可在任何设备上使用。", "download": "下载 ComfyUI", @@ -237,7 +365,7 @@ "software": "软件与技术" }, "making": { - "3d": "3D 资源", + "3d": "3D 资产", "audio": "音频 / 音乐", "custom_nodes": "自定义节点和工作流", "images": "图片", @@ -320,60 +448,64 @@ "Add Group": "添加组", "Add Group For Selected Nodes": "为选定节点添加组", "Add Node": "添加节点", - "Add Subgraph to Library": "Add Subgraph to Library", - "Adjust Size": "Adjust Size", - "Align Selected To": "Align Selected To", - "Bottom": "Bottom", + "Add Subgraph to Library": "添加子工作流到节点库", + "Adjust Size": "调整尺寸", + "Align Selected To": "对齐选中项到", + "Bottom": "底部", "Bypass": "绕过", "Clone": "克隆", "Collapse": "折叠", - "Color": "Color", + "Color": "颜色", "Colors": "颜色", "Convert to Group Node": "转换为组节点", - "Convert to Subgraph": "Convert to Subgraph", - "Copy": "Copy", + "Convert to Subgraph": "转换为子工作流", + "Copy": "复制", "Copy (Clipspace)": "复制 (Clipspace)", - "Copy Image": "Copy Image", - "Delete": "Delete", - "Distribute Nodes": "Distribute Nodes", - "Duplicate": "Duplicate", - "Edit Subgraph Widgets": "Edit Subgraph Widgets", + "Copy Image": "复制图像", + "Delete": "删除", + "Distribute Nodes": "分布节点", + "Duplicate": "复制", + "Edit Subgraph Widgets": "编辑子工作流组件", "Expand": "展开", - "Expand Node": "Expand Node", - "Horizontal": "Horizontal", + "Expand Node": "展开节点", + "Extensions": "扩展", + "FavoriteWidget": "收藏组件", + "Horizontal": "水平", "Inputs": "输入", - "Left": "Left", + "Left": "左侧", "Manage": "管理", "Manage Group Nodes": "管理组节点", - "Minimize Node": "Minimize Node", + "Minimize Node": "最小化节点", "Mode": "模式", - "Node Info": "Node Info", + "Node Info": "节点信息", "Node Templates": "节点模板", - "Open Image": "Open Image", - "Open in Mask Editor": "Open in Mask Editor", + "Open Image": "打开图像", + "Open in Mask Editor": "用遮罩编辑器打开", "Outputs": "输出", - "Paste": "Paste", + "Paste": "站贴", "Pin": "固定", "Properties": "属性", "Properties Panel": "属性面板", "Remove": "删除", - "Remove Bypass": "Remove Bypass", - "Rename": "Rename", + "Remove Bypass": "删除所有忽略节点", + "Rename": "重命名", + "RenameWidget": "重命名组件", "Resize": "调整大小", - "Right": "Right", - "Run Branch": "Run Branch", - "Save Image": "Save Image", + "Right": "右侧", + "Run Branch": "运行支流节点", + "Save Image": "保存图像", "Save Selected as Template": "将选定节点另存为模板", "Search": "搜索", - "Shape": "Shape", + "Shape": "形状", "Shapes": "形状", "Title": "标题", - "Top": "Top", - "Unpack Subgraph": "Unpack Subgraph", + "Top": "顶部", + "UnfavoriteWidget": "取消收藏组件", + "Unpack Subgraph": "解包子工作流", "Unpin": "取消固定", - "Vertical": "Vertical", - "deprecated": "deprecated", - "new": "new" + "Vertical": "竖直", + "deprecated": "弃用", + "new": "新" }, "credits": { "accountInitialized": "账户已初始化", @@ -382,6 +514,7 @@ "additionalInfo": "附加信息", "apiPricing": "API 价格", "credits": "积分", + "creditsAvailable": "积分可用", "details": "详情", "eventType": "事件类型", "faqs": "常见问题", @@ -390,15 +523,46 @@ "messageSupport": "联系客服", "model": "模型", "purchaseCredits": "购买积分", + "refreshes": "于 {date} 刷新", "time": "时间", "topUp": { + "addMoreCredits": "获取更多积分", + "addMoreCreditsToRun": "获取更多积分来运行", + "amountToPayLabel": "支付金额(美元)", + "buy": "购买", + "buyCredits": "继续付款", "buyNow": "立即购买", + "contactUs": "联系我们", + "creditsDescription": "该工作流需要积分运行。", + "creditsPerDollar": "每美元积分数", + "creditsToReceiveLabel": "获得积分数", + "howManyCredits": "您想要多少积分?", "insufficientMessage": "您的积分不足,无法运行此工作流。", "insufficientTitle": "积分不足", + "insufficientWorkflowMessage": "您的积分不足以运行该工作流。", + "maxAllowed": "最多{credits}积分。", "maxAmount": "(最高 $1,000 美元)", + "maximumAmount": "最多${amount}", + "minRequired": "最少{credits}积分", + "minimumPurchase": "最低${amount}({credits}积分)", + "needMore": "需要更多?", + "purchaseError": "购买失败", + "purchaseErrorDetail": "购买积分失败:{error}", "quickPurchase": "快速购买", "seeDetails": "查看详情", - "topUp": "充值" + "selectAmount": "选择金额", + "templateNote": "*使用 Wan Fun Control 模板生成", + "topUp": "充值", + "unknownError": "发生未知错误", + "usdAmount": "${amount}", + "videosEstimate": "~{count} 个视频*", + "viewPricing": "查看定价详情", + "youGet": "获得积分", + "youPay": "支付金额(美元)" + }, + "unified": { + "message": "积分已合并", + "tooltip": "我们统一了 Comfy 的支付方式。现在全部使用 Comfy 积分 :\n- 合作节点(API节点)\n- Cloud 工作流\n\n您现有的合作伙伴节点积分将自动转换为 Comfy 积分。" }, "yourCreditBalance": "您的积分余额" }, @@ -414,6 +578,9 @@ "CLIP_VISION": "CLIP视觉", "CLIP_VISION_OUTPUT": "CLIP视觉输出", "COMBO": "组合", + "COMFY_AUTOGROW_V3": "COMFY_AUTOGROW_V3", + "COMFY_DYNAMICCOMBO_V3": "COMFY_DYNAMICCOMBO_V3", + "COMFY_MATCHTYPE_V3": "COMFY_MATCHTYPE_V3", "CONDITIONING": "条件", "CONTROL_NET": "ControlNet", "FLOAT": "浮点", @@ -424,24 +591,27 @@ "HOOKS": "约束", "HOOK_KEYFRAMES": "约束关键帧", "IMAGE": "图像", + "IMAGECOMPARE": "图像对比", "INT": "整数", "LATENT": "Latent", "LATENT_OPERATION": "Latent操作", + "LATENT_UPSCALE_MODEL": "LATENT_UPSCALE_MODEL", "LOAD3D_CAMERA": "加载3D相机", "LOAD_3D": "加载3D", - "LOAD_3D_ANIMATION": "加载3D动画", "LORA_MODEL": "LORA模型", "LOSS_MAP": "损失图", "LUMA_CONCEPTS": "Luma 概念", "LUMA_REF": "Luma 参考", "MASK": "遮罩", "MESH": "网格", + "MESHY_RIGGED_TASK_ID": "MESHY_RIGGED_TASK_ID", + "MESHY_TASK_ID": "MESHY_TASK_ID", "MODEL": "模型", - "MODEL_PATCH": "MODEL_PATCH", + "MODEL_PATCH": "模型补丁", "MODEL_TASK_ID": "模型任务ID", "NOISE": "噪波", - "OPENAI_CHAT_CONFIG": "OPENAI_CHAT_CONFIG", - "OPENAI_INPUT_FILES": "OPENAI输入文件", + "OPENAI_CHAT_CONFIG": "OpenAI对话文件", + "OPENAI_INPUT_FILES": "OpenAI输入文件", "PHOTOMAKER": "PhotoMaker", "PIXVERSE_TEMPLATE": "Pixverse 模板", "RECRAFT_COLOR": "Recraft 颜色", @@ -455,6 +625,7 @@ "STYLE_MODEL": "风格模型", "SVG": "SVG", "TIMESTEPS_RANGE": "时间间隔范围", + "TRACKS": "TRACKS", "UPSCALE_MODEL": "放大模型", "VAE": "VAE", "VIDEO": "视频", @@ -523,14 +694,17 @@ "amount": "数量", "apply": "应用", "architecture": "架构", + "asset": "{count} 个资源", "audioFailedToLoad": "音频加载失败", "audioProgress": "音频进度", "author": "作者", "back": "返回", + "batchRename": "批量重命名", "beta": "测试版", "bookmark": "保存到库", "calculatingDimensions": "正在计算尺寸", "cancel": "取消", + "cancelled": "已取消", "capture": "捕获", "category": "类别", "chart": "图表", @@ -540,6 +714,7 @@ "clearAll": "全部清除", "clearFilters": "清除筛选", "close": "关闭", + "closeDialog": "关闭对话框", "color": "颜色", "comfy": "舒适", "comfyOrgLogoAlt": "ComfyOrg 徽标", @@ -556,13 +731,17 @@ "control_before_generate": "生成前控制", "copied": "已复制", "copy": "复制", + "copyAll": "全部复制", "copyJobId": "复制队列 ID", "copyToClipboard": "复制到剪贴板", "copyURL": "复制链接", + "core": "核心", "currentUser": "当前用户", + "custom": "自定义", "customBackground": "自定义背景", "customize": "自定义", "customizeFolder": "自定义文件夹", + "decrement": "减小", "defaultBanner": "默认横幅", "delete": "删除", "deleteAudioFile": "删除音频文件", @@ -571,27 +750,35 @@ "description": "描述", "devices": "设备", "disableAll": "禁用全部", + "disableSelected": "禁用选中项", + "disableThirdParty": "禁用第三方", "disabling": "禁用中", "dismiss": "关闭", "download": "下载", "downloadImage": "下载图片", "downloadVideo": "下载视频", + "downloading": "正在下载", "dropYourFileOr": "拖放您的文件或", "duplicate": "复制", "edit": "编辑", "editImage": "编辑图片", "editOrMaskImage": "编辑或遮罩图片", + "emDash": "-", "empty": "空", "enableAll": "启用全部", "enableOrDisablePack": "启用或禁用包", + "enableSelected": "启用选中项", "enabled": "已启用", "enabling": "启用中", + "enterBaseName": "输入基础名称", + "enterNewName": "输入新名称", "error": "错误", "errorLoadingImage": "图片加载出错", "errorLoadingVideo": "视频加载出错", "experimental": "测试版", "export": "导出", "extensionName": "扩展名称", + "failed": "失败", "failedToCopyJobId": "未能复制队列 ID", "failedToDownloadImage": "图片下载失败", "failedToDownloadVideo": "视频下载失败", @@ -607,12 +794,15 @@ "goToNode": "转到节点", "graphNavigation": "图形导航", "halfSpeed": "0.5倍", + "hideLeftPanel": "隐藏左侧面板", + "hideRightPanel": "隐藏右侧面板", "icon": "图标", "imageFailedToLoad": "图像加载失败", "imagePreview": "图片预览 - 使用方向键切换图片", "imageUrl": "图片网址", "import": "导入", "inProgress": "进行中", + "increment": "增加", "info": "节点信息", "insert": "插入", "install": "安装", @@ -620,12 +810,14 @@ "installing": "正在安装", "interrupted": "已中断", "itemSelected": "已选择 {selectedCount} 项", + "itemsCopiedToClipboard": "已复制到剪贴板", "itemsSelected": "已选择 {selectedCount} 项", + "job": "任务", "jobIdCopied": "队列 ID 已复制到剪贴板", "keybinding": "按键绑定", "keybindingAlreadyExists": "快捷键已存在", "learnMore": "了解更多", - "listening": "正在聆听...", + "listening": "正在监听...", "liveSamplingPreview": "实时采样预览", "loadAllFolders": "加载所有文件夹", "loadWorkflow": "加载工作流", @@ -638,14 +830,18 @@ "micPermissionDenied": "麦克风权限被拒绝", "migrate": "迁移", "missing": "缺失", + "more": "更多", "moreOptions": "更多选项", "moreWorkflows": "更多工作流", "multiSelectDropdown": "多选下拉框", "name": "名称", "newFolder": "新文件夹", "next": "下一个", + "nightly": "NIGHTLY", "no": "否", "noAudioRecorded": "未录制音频", + "noItems": "无项目", + "noResults": "无结果", "noResultsFound": "未找到结果", "noTasksFound": "未找到任务", "noTasksFoundMessage": "队列中没有任务。", @@ -656,26 +852,45 @@ "nodeSlotsError": "节点插槽错误", "nodeWidgetsError": "节点控件错误", "nodes": "节点", + "nodesCount": "{count} 个节点", "nodesRunning": "节点正在运行", "none": "无", + "nothingToCopy": "没有可以复制的内容", + "nothingToDelete": "没有可以删除的内容", + "nothingToDuplicate": "Nothing to duplicate", + "nothingToRename": "没有可以重命名的内容", "ok": "确定", "openManager": "打开管理器", "openNewIssue": "打开新问题", + "or": "或", "overwrite": "覆盖", + "playPause": "开启/暂停", "playRecording": "播放录音", "playbackSpeed": "播放速度", "playing": "播放中", "pressKeysForNewBinding": "按下按键设置新绑定", "preview": "预览", + "profile": "档案", "progressCountOf": "共", + "queued": "已执行", "ready": "就绪", "reconnected": "已重新连接", "reconnecting": "重新连接中", "refresh": "刷新", "refreshNode": "刷新节点", + "relativeTime": { + "daysAgo": "{count}天 前", + "hoursAgo": "{count}时 前", + "minutesAgo": "{count}分 前", + "monthsAgo": "{count}月 前", + "now": "刚才", + "weeksAgo": "{count}周 前", + "yearsAgo": "{count}年 前" + }, "releaseTitle": "{package} {version} 发布", "reloadToApplyChanges": "重新加载以应用更改", "removeImage": "移除图片", + "removeTag": "移除标签", "removeVideo": "移除视频", "rename": "重命名", "reportIssue": "发送报告", @@ -690,21 +905,31 @@ "resizeFromTopRight": "从右上角调整大小", "restart": "重新启动", "resultsCount": "找到 {count} 个结果", + "running": "正在运行", "save": "保存", "saving": "正在保存", + "scrollLeft": "向左滚动", + "scrollRight": "向右滚动", "search": "搜索", "searchExtensions": "搜索扩展", "searchFailedMessage": "我们找不到任何与您的搜索匹配的设置。请尝试调整您的搜索词。", "searchKeybindings": "搜索快捷键", "searchModels": "搜索模型", "searchNodes": "搜索节点", + "searchPlaceholder": "搜索占位符", "searchSettings": "搜索设置", "searchWorkflows": "搜索工作流", "seeTutorial": "查看教程", + "selectItemsToCopy": "选择复制", + "selectItemsToDelete": "选择删除", + "selectItemsToDuplicate": "选择复制", + "selectItemsToRename": "选择重命名", "selectedFile": "已选文件", "setAsBackground": "设为背景", "settings": "设置", + "showLeftPanel": "显示左侧面板", "showReport": "显示报告", + "showRightPanel": "显示右侧面板", "singleSelectDropdown": "单选下拉框", "sort": "排序", "source": "来源", @@ -712,12 +937,14 @@ "status": "状态", "stopPlayback": "停止播放", "stopRecording": "停止录音", + "submit": "提交", "success": "成功", "systemInfo": "系统信息", "terminal": "终端", "title": "标题", "triggerPhrase": "触发短语", "unknownError": "未知错误", + "untitled": "无标题", "update": "更新", "updateAvailable": "有更新可用", "updateFrontend": "更新前端", @@ -725,6 +952,7 @@ "updating": "更新中", "upload": "上传", "usageHint": "使用提示", + "use": "使用", "user": "用户", "versionMismatchWarning": "版本兼容性警告", "versionMismatchWarningMessage": "{warning}:{detail} 请参阅 https://docs.comfy.org/installation/update_comfyui#common-update-issues 以取得更新说明。", @@ -732,11 +960,10 @@ "videoPreview": "视频预览 - 使用方向键切换视频", "viewImageOfTotal": "查看第 {index} 张图片,共 {total} 张", "viewVideoOfTotal": "查看第 {index} 个视频,共 {total} 个", - "vitePreloadErrorMessage": "应用已发布新版本。是否立即重新加载?\n如果不重新加载,应用的某些功能可能无法正常工作。\n您可以先拒绝,保存进度后再重新加载。", - "vitePreloadErrorTitle": "新版本可用", "volume": "音量", "warning": "警告", - "workflow": "工作流" + "workflow": "工作流", + "you": "你" }, "graphCanvasMenu": { "fitView": "适应视图", @@ -758,12 +985,17 @@ "create": "创建组节点", "enterName": "输入名称" }, + "help": { + "helpCenterMenu": "帮助中心菜单", + "recentReleases": "最近发布" + }, "helpCenter": { "clickToLearnMore": "点击了解更多 →", "desktopUserGuide": "桌面端用户指南", "docs": "文档", + "feedback": "提交反馈", "github": "Github", - "helpFeedback": "帮助与反馈", + "help": "帮助和支持", "loadingReleases": "加载发布信息...", "managerExtension": "管理扩展", "more": "更多...", @@ -772,6 +1004,12 @@ "recentReleases": "最近发布", "reinstall": "重新安装", "updateAvailable": "更新", + "updateComfyUI": "更新 ComfyUI", + "updateComfyUIFailed": "更新 ComfyUI 失败,请重试。", + "updateComfyUIStarted": "更新开始", + "updateComfyUIStartedDetail": "正在更新 ComfyUI,请稍等...", + "updateComfyUISuccess": "更新完成", + "updateComfyUISuccessDetail": "ComfyUI 已更新,正在重启...", "whatsNew": "新功能?" }, "icon": { @@ -785,6 +1023,18 @@ "inbox": "收件箱", "star": "星星" }, + "imageCompare": { + "noImages": "没有可以对比的图像" + }, + "imageCrop": { + "cropPreviewAlt": "裁剪预览", + "loading": "加载中...", + "noInputImage": "未连接输入图像" + }, + "importFailed": { + "copyError": "复制错误", + "title": "导入失败" + }, "install": { "appDataLocationTooltip": "ComfyUI 的应用数据目录。存储:\n- 日志\n- 服务器配置", "appPathLocationTooltip": "ComfyUI 的应用资产目录。存储 ComfyUI 代码和资产", @@ -798,6 +1048,8 @@ "failedToSelectDirectory": "选择目录失败", "gpu": "GPU", "gpuPicker": { + "amdDescription": "使用您的 AMD GPU 结合 ROCm™ 加速以获得最佳性能。", + "amdSubtitle": "AMD ROCm™", "appleMetalDescription": "利用您的Mac GPU以获得更快速度和更佳体验", "cpuDescription": "当GPU加速不可用时,使用CPU模式以获得兼容性", "cpuSubtitle": "CPU模式", @@ -824,6 +1076,8 @@ "selectGpuDescription": "选择你拥有的 GPU 类型" }, "helpImprove": "请帮助我们改进ComfyUI", + "insideAppInstallDir": "该文件夹位于 ComfyUI Desktop 应用程序包中,程序更新时会删除该文件夹。选择安装文件夹之外的目录,例如 Documents/ComfyUI。", + "insideUpdaterCache": "该文件夹位于 ComfyUI 更新缓存中,程序更新时会清除该缓存。为您的数据选择一个不同的位置。", "installLocation": "安装位置", "installLocationDescription": "选择 ComfyUI 用户数据的存放目录。将安装一个 Python 环境到所选位置。请确保所选磁盘有足够的空间(约 15GB)。", "installLocationTooltip": "ComfyUI 的用户数据目录。存储:\n- Python 环境\n- 模型\n- 自定义节点\n", @@ -898,6 +1152,16 @@ "issueReport": { "helpFix": "帮助修复这个" }, + "linearMode": { + "beta": "测试版 - 提供反馈", + "downloadAll": "全部下载", + "dragAndDropImage": "拖拽图片到此处", + "graphMode": "图形模式", + "linearMode": "简易模式", + "rerun": "重新运行", + "reuseParameters": "复用参数", + "runCount": "运行次数:" + }, "load3d": { "applyingTexture": "应用纹理中...", "backgroundColor": "背景颜色", @@ -924,20 +1188,24 @@ "lineart": "线稿", "normal": "法线", "original": "原始", + "pointCloud": "点云", "wireframe": "线框" }, "model": "模型", "openIn3DViewer": "在 3D 查看器中打开", + "panoramaMode": "全景", "previewOutput": "预览输出", "reloadingModel": "正在重新加载模型...", "removeBackgroundImage": "移除背景图片", "resizeNodeMatchOutput": "调整节点以匹配输出", "scene": "场景", "showGrid": "显示网格", + "showSkeleton": "显示骨架", "startRecording": "开始录制", "stopRecording": "停止录制", "switchCamera": "切换摄影机类型", "switchingMaterialMode": "切换材质模式中...", + "tiledMode": "分快", "unsupportedFileType": "不支持的文件类型(支持 .gltf、.glb、.obj、.fbx、.stl)", "upDirection": "上方向", "upDirections": { @@ -958,6 +1226,11 @@ "title": "3D 查看器(测试版)" } }, + "loadWorkflowWarning": { + "coreNodesFromVersion": "核心节点来源于 {version} 版本。", + "outdatedVersion": "这个工作流由新版 ComfyUI({version})创建,部分节点可能无法正常运行。", + "outdatedVersionGeneric": "这个工作流由新版 ComfyUI 创建,部分节点可能无法正常运行。" + }, "maintenance": { "None": "无", "OK": "确定", @@ -976,7 +1249,15 @@ "showManual": "显示维护任务", "status": "状态", "terminalDefaultMessage": "当你运行一个故障排除命令时,任何输出都会在这里显示。", - "title": "维护" + "title": "维护", + "unsafeMigration": { + "action": "使用下方的 \"基本路径\" 维护将路径移动到安全位置。", + "appInstallDir": "您当前的 ComfyUI 基本路径位于 ComfyUI Desktop 应用程序包中,程序更新时会删除该文件夹。选择安装文件夹之外的目录,例如 Documents/ComfyUI。", + "generic": "您当前的 ComfyUI 基本路径所在的位置可能会在更新期间被删除或修改。为避免数据丢失,请将其移至安全文件夹。", + "oneDrive": "您当前的 ComfyUI 基本路径在 OneDrive上,这可能会导致同步问题和意外的数据丢失。选择一个不受 OneDrive 管理的本地文件夹。", + "title": "检测到安装路径不安全", + "updaterCache": "您当前的 ComfyUI 基本路径位于 ComfyUI 更新缓存中,程序更新时会清除该缓存。为您的数据选择一个不同的位置。" + } }, "manager": { "allMissingNodesInstalled": "所有缺失节点已成功安装", @@ -1077,6 +1358,8 @@ "totalNodes": "节点总数", "tryAgainLater": "请稍后再试。", "tryDifferentSearch": "请尝试不同的搜索查询。", + "tryUpdate": "尝试更新", + "tryUpdateTooltip": "从库中拉取更新。测试版本可能有无法自动检测到更新。", "uninstall": "卸载", "uninstallSelected": "卸载所选", "uninstalling": "正在卸载", @@ -1087,31 +1370,110 @@ "version": "版本" }, "maskEditor": { + "activateLayer": "活跃层", + "applyToWholeImage": "应用到图像整体", + "baseImageLayer": "基础图像层", + "baseLayerPreview": "基础图像层预览", + "black": "黑", + "brushSettings": "笔刷设置", + "brushShape": "笔刷形状", + "clear": "清除", + "clickToResetZoom": "点击重置缩放", + "colorSelectSettings": "色彩选取设置", + "colorSelector": "色彩选取", + "fillOpacity": "填充不透明度", + "hardness": "硬度", + "imageLayer": "图像层", + "invert": "反转", + "layers": "图层", + "livePreview": "实时预览", + "maskBlendingOptions": "遮罩混合设置", + "maskLayer": "遮罩层", + "maskOpacity": "遮罩不透明度", + "maskTolerance": "遮罩阈值", + "method": "方法", + "mirrorHorizontal": "水平翻转", + "mirrorVertical": "垂直翻转", + "negative": "负面", + "opacity": "不透明度", + "paintBucketSettings": "填充设置", + "paintLayer": "绘画层", + "redo": "重做", + "resetToDefault": "重置为默认", + "rotateLeft": "向左旋转", + "rotateRight": "向右旋转", + "selectionOpacity": "选取不透明度", + "smoothingPrecision": "预测平滑", + "stepSize": "间距", + "stopAtMask": "遇到遮罩时停止", + "thickness": "厚度", + "title": "遮罩编辑器", + "tolerance": "阈值", + "undo": "撤回", + "white": "白" }, "mediaAsset": { + "actions": { + "copyJobId": "复制任务ID", + "delete": "删除", + "download": "下载", + "exportWorkflow": "导出工作流", + "insertAsNodeInWorkflow": "作为节点插入到工作流", + "inspect": "查看资产", + "more": "更多设置", + "moreOptions": "更多选项", + "openWorkflow": "在新标签页中读取工作流", + "seeMoreOutputs": "查看更多输出", + "zoom": "放大" + }, "assetDeletedSuccessfully": "资产删除成功", "deleteAssetDescription": "此资产将被永久删除。", "deleteAssetTitle": "删除此资产?", "deleteSelectedDescription": "{count} 项资产将被永久删除。", "deleteSelectedTitle": "删除所选资产?", "deletingImportedFilesCloudOnly": "删除导入文件仅支持云版本", + "failedToCreateNode": "创建节点失败", "failedToDeleteAsset": "删除资产失败", + "failedToExportWorkflow": "工作流导出失败", "jobIdToast": { "copied": "已复制", "error": "错误", "jobIdCopied": "任务 ID 已复制到剪贴板", "jobIdCopyFailed": "复制队列 ID 失败" }, + "noJobIdFound": "该资产不包含任务ID", + "noWorkflowDataFound": "该资产不包含工作流数据", + "nodeAddedToWorkflow": "{nodeType} 节点已添加到工作流", + "nodeTypeNotFound": "节点类型 {nodeType} 不存在", "selection": { - "assetsDeletedSuccessfully": "已成功删除 {count} 个资源", + "assetsDeletedSuccessfully": "已成功删除 {count} 个资产", "deleteSelected": "删除", + "deleteSelectedAll": "全部删除", "deselectAll": "取消全选", "downloadSelected": "下载", + "downloadSelectedAll": "全部下载", "downloadStarted": "正在下载 {count} 个文件...", "downloadsStarted": "开始下载 {count} 个文件", + "exportWorkflowAll": "导出所有工作流", + "failedToAddNodes": "添加节点到工作流失败", "failedToDeleteAssets": "未能删除所选资产", - "selectedCount": "已选择资产:{count}" - } + "insertAllAssetsAsNodes": "将所有资源作为节点插入", + "multipleSelectedAssets": "已选择多个资源", + "noWorkflowsFound": "所选资源中未找到工作流数据", + "noWorkflowsToExport": "未找到可导出的工作流数据", + "nodesAddedToWorkflow": "已添加 {count} 个节点到工作流", + "openWorkflowAll": "打开所有工作流", + "partialAddNodesSuccess": "成功添加 {succeeded} 个,失败 {failed} 个", + "partialDeleteSuccess": "{succeeded} 删除成功, {failed} 失败", + "partialWorkflowsExported": "成功导出 {succeeded} 个,失败 {failed} 个", + "partialWorkflowsOpened": "已打开 {succeeded} 个工作流,失败 {failed} 个", + "selectedCount": "已选择资产:{count}", + "workflowsExported": "成功导出 {count} 个工作流", + "workflowsOpened": "已在新标签页打开 {count} 个工作流" + }, + "unsupportedFileType": "加载节点不支持该类型文件", + "workflowExportedSuccessfully": "工作流导出成功!", + "workflowOpenedInNewTab": "工作流已在新标签页中打开" }, "menu": { "autoQueue": "自动执行", @@ -1119,11 +1481,13 @@ "batchCountTooltip": "工作流生成次数", "clear": "清空工作流", "clipspace": "打开剪贴板", + "customNodesManager": "自定义节点管理器", "dark": "深色", "disabled": "禁用", "disabledTooltip": "工作流将不会自动执行", "execute": "执行", "help": "说明", + "helpAndFeedback": "帮助与反馈", "hideMenu": "隐藏菜单", "instant": "实时", "instantTooltip": "工作流将会在生成完成后立即执行", @@ -1137,6 +1501,7 @@ "resetView": "重置视图", "run": "运行", "runWorkflow": "运行工作流(Shift排在前面)", + "runWorkflowDisabled": "工作流包含不支持的节点(已用红色突出显示)。删除这些节点以运行工作流。", "runWorkflowFront": "运行工作流(排在前面)", "settings": "设定", "showMenu": "显示菜单", @@ -1152,6 +1517,7 @@ "Canvas Performance": "画布性能", "Canvas Toggle Lock": "切换视图锁定", "Check for Custom Node Updates": "检查自定义节点更新", + "Check for Updates": "检查更新", "Clear Pending Tasks": "清除待处理任务", "Clear Workflow": "清除工作流", "Clipspace": "剪贴空间", @@ -1162,19 +1528,20 @@ "ComfyUI Forum": "ComfyUI 论坛", "ComfyUI Issues": "ComfyUI 问题", "Contact Support": "联系支持", - "Convert Selection to Subgraph": "将选中内容转换为子图", + "Convert Selection to Subgraph": "将选中内容转换为子工作流", "Convert selected nodes to group node": "将选中节点转换为组节点", "Custom Nodes (Legacy)": "自定义节点(旧版)", "Custom Nodes Manager": "自定义节点管理器", "Decrease Brush Size in MaskEditor": "在 MaskEditor 中减小笔刷大小", "Delete Selected Items": "删除选定的项目", + "Desktop User Guide": "桌面端用户手册", "Duplicate Current Workflow": "复制当前工作流", "Edit": "编辑", - "Edit Subgraph Widgets": "编辑子图组件", - "Exit Subgraph": "退出子图", - "Experimental: Browse Model Assets": "实验性:浏览模型资源", + "Edit Subgraph Widgets": "编辑子工作流组件", + "Exit Subgraph": "退出子工作流", + "Experimental: Browse Model Assets": "实验性:浏览模型资产", "Experimental: Enable AssetAPI": "实验性:启用 AssetAPI", - "Experimental: Enable Vue Nodes": "实验性:启用 Vue 节点", + "Experimental: Enable Nodes 2_0": "实验性:启用 Nodes 2.0", "Export": "导出", "Export (API)": "导出 (API)", "File": "文件", @@ -1186,12 +1553,15 @@ "Increase Brush Size in MaskEditor": "在 MaskEditor 中增大笔刷大小", "Install Missing Custom Nodes": "安装缺失的自定义节点", "Interrupt": "中断", + "Job History": "任务历史", "Load Default Workflow": "加载默认工作流", "Lock Canvas": "锁定画布", "Manage group nodes": "管理组节点", "Manager": "管理器", "Manager Menu (Legacy)": "管理菜单(旧版)", "Minimap": "小地图", + "Mirror Horizontal in MaskEditor": "在蒙版编辑器中水平翻转", + "Mirror Vertical in MaskEditor": "在蒙版编辑器中垂直翻转", "Model Library": "模型库", "Move Selected Nodes Down": "下移所选节点", "Move Selected Nodes Left": "左移所选节点", @@ -1204,8 +1574,16 @@ "Node Links": "节点连接", "Open": "打开", "Open 3D Viewer (Beta) for Selected Node": "为选中节点打开3D查看器(测试版)", + "Open Color Picker in MaskEditor": "在 MaskEditor 中打开取色器", + "Open Custom Nodes Folder": "打开自定义节点文件夹", + "Open DevTools": "打开开发者工具", + "Open Inputs Folder": "打开输入文件夹", + "Open Logs Folder": "打开日志文件夹", "Open Mask Editor for Selected Node": "为选中节点打开 Mask 编辑器", + "Open Models Folder": "打开模型文件夹", + "Open Outputs Folder": "打开输出文件夹", "Open Sign In Dialog": "打开登录对话框", + "Open extra_model_paths_yaml": "打开 extra_model_paths.yaml", "Pin/Unpin Selected Items": "固定/取消固定选定项目", "Pin/Unpin Selected Nodes": "固定/取消固定选定节点", "Previous Opened Workflow": "上一个打开的工作流", @@ -1213,10 +1591,16 @@ "Queue Prompt": "执行提示词", "Queue Prompt (Front)": "执行提示词 (优先执行)", "Queue Selected Output Nodes": "将所选输出节点加入队列", + "Quit": "退出", "Redo": "重做", "Refresh Node Definitions": "刷新节点定义", + "Reinstall": "重装", + "Rename": "重命名", "Reset View": "重置视图", "Resize Selected Nodes": "调整选定节点的大小", + "Restart": "重启", + "Rotate Left in MaskEditor": "在蒙版编辑器中向左旋转", + "Rotate Right in MaskEditor": "在蒙版编辑器中向右旋转", "Save": "保存", "Save As": "另存为", "Show Keybindings Dialog": "显示快捷键对话框", @@ -1225,18 +1609,19 @@ "Sign Out": "退出登录", "Toggle Essential Bottom Panel": "切换基础底部面板", "Toggle Logs Bottom Panel": "切换日志底部面板", + "Toggle Queue Panel V2": "切换队列面板 V2", "Toggle Search Box": "切换搜索框", + "Toggle Simple Mode": "切换简易模式", "Toggle Terminal Bottom Panel": "切换终端底部面板", "Toggle Theme (Dark/Light)": "切换主题(暗/亮)", "Toggle View Controls Bottom Panel": "切换视图控制底部面板", "Toggle promotion of hovered widget": "切换悬停小部件的提升", - "Toggle the Custom Nodes Manager Progress Bar": "切换自定义节点管理器进度条", "Undo": "撤销", "Ungroup selected group nodes": "解散选中组节点", "Unload Models": "卸载模型", "Unload Models and Execution Cache": "卸载模型和执行缓存", "Unlock Canvas": "解除锁定画布", - "Unpack the selected Subgraph": "解包选中子图", + "Unpack the selected Subgraph": "解包选中子工作流", "View": "视图", "Workflows": "工作流", "Zoom In": "放大画面", @@ -1255,30 +1640,56 @@ "missingModels": "缺少模型", "missingModelsMessage": "加载工作流时,未找到以下模型" }, + "missingNodes": { + "cloud": { + "description": "该工作流包含 Comfy Cloud 目前不支持的节点", + "gotIt": "好的", + "learnMore": "查看更多", + "priorityMessage": "我们已经标记这些节点,将会优先支持它们。", + "replacementInstruction": "同时,如果可能的话,将这些节点(红色突出显示)替换为已经支持的节点,或者尝试不同的工作流。", + "title": "Comfy Cloud 目前不支持这些节点" + }, + "oss": { + "description": "该工作流包含您未安装的自定义节点", + "replacementInstruction": "安装这些节点后运行此工作流,或者用已安装的节点替换它们。缺失的节点在画布上以红色突出显示。", + "title": "该工作流含有缺失节点" + } + }, + "nightly": { + "badge": { + "label": "预览版", + "tooltip": "您正在使用 ComfyUI 的夜间版本。请使用反馈按钮分享您对这些功能的看法。" + } + }, "nodeCategories": { + "": "", "3d": "3d", "3d_models": "3D模型", "BFL": "BFL", + "Bria": "Bria", "ByteDance": "字节跳动", "Gemini": "Gemini", "Ideogram": "Ideogram", "Kling": "Kling", "LTXV": "LTXV", "Luma": "Luma", + "Meshy": "Meshy", "MiniMax": "MiniMax", "Moonvalley Marey": "Moonvalley Marey", "OpenAI": "OpenAI", - "Pika": "Pika", "PixVerse": "PixVerse", "Recraft": "Recraft", - "Rodin": "罗丹", - "Runway": "跑道", + "Rodin": "Rodin", + "Runway": "Runway", "Sora": "Sora", "Stability AI": "Stability AI", + "Tencent": "Tencent", + "Topaz": "Topaz", "Tripo": "Tripo", "Veo": "Veo", "Vidu": "Vidu", - "Wan": "万相", + "Wan": "Wan万相", + "WaveSpeed": "WaveSpeed", "_for_testing": "_用于测试", "advanced": "高级", "animation": "动画", @@ -1299,6 +1710,7 @@ "controlnet": "ControlNet", "create": "创建", "custom_sampling": "自定义采样", + "dataset": "dataset", "debug": "调试", "deprecated": "已弃用", "edit_models": "编辑模型", @@ -1310,8 +1722,10 @@ "image": "图像", "inpaint": "局部重绘", "instructpix2pix": "InstructPix2Pix", + "kandinsky5": "kandinsky5", "latent": "Latent", "loaders": "加载器", + "logic": "逻辑", "lotus": "lotus", "ltxv": "LTXV", "mask": "遮罩", @@ -1325,7 +1739,7 @@ "postprocessing": "后处理", "preprocessors": "预处理器", "primitive": "基础", - "qwen": "千问", + "qwen": "Qwen千问", "samplers": "采样器", "sampling": "采样", "save": "保存", @@ -1345,7 +1759,15 @@ "upscaling": "放大", "utils": "工具", "video": "视频", - "video_models": "视频模型" + "video_models": "视频模型", + "zimage": "zimage" + }, + "nodeErrors": { + "content": "节点内容错误", + "header": "节点头错误", + "render": "节点渲染错误", + "slots": "节点接口错误", + "widgets": "节点组件错误" }, "nodeHelpPage": { "documentationPage": "文档页面", @@ -1362,6 +1784,7 @@ "notSupported": { "continue": "继续", "continueTooltip": "我确定我的设备是受支持的", + "illustrationAlt": "Sad girl illustration", "learnMore": "了解更多", "message": "仅支持以下设备:", "reportIssue": "报告问题", @@ -1371,12 +1794,136 @@ }, "title": "您的设备不受支持" }, + "progressToast": { + "allDownloadsCompleted": "所有下载已完成", + "downloadingModel": "正在下载模型...", + "downloadsFailed": "{count} 个下载失败", + "failed": "失败", + "filter": { + "all": "全部", + "completed": "已完成", + "failed": "失败" + }, + "finished": "已完成", + "importingModels": "正在导入模型", + "noImportsInQueue": "队列中没有 {filter}", + "pending": "待处理", + "progressCount": "{completed} / {total}" + }, + "queue": { + "completedIn": "{duration} 后完成", + "inQueue": "正在执行...", + "initializingAlmostReady": "初始化中 - 即将完成", + "jobAddedToQueue": "任务添加到队列", + "jobDetails": { + "computeHoursUsed": "计算耗时", + "errorMessage": "报错信息", + "estimatedFinishIn": "预计完成于", + "estimatedStartIn": "预计开始于", + "eta": { + "minutes": "~{count} 分钟 | ~{count} 分钟", + "minutesRange": "~{lo}-{hi} 分钟", + "seconds": "~{count} 秒 | ~{count} 秒", + "secondsRange": "~{lo}-{hi} 秒" + }, + "failedAfter": "失败于", + "generatedOn": "生成于", + "header": "任务细节", + "jobId": "任务ID", + "queuePosition": "队列位置", + "queuePositionValue": "在您前方有 ~{count} 个任务 | 在您前方有 ~{count} 个任务", + "queuedAt": "执行于", + "report": "反馈", + "timeElapsed": "耗时", + "totalGenerationTime": "总生成时间", + "workflow": "工作流" + }, + "jobHistory": "任务历史", + "jobList": { + "sortComputeHoursUsed": "计算用时(最先)", + "sortMostRecent": "最新", + "sortTotalGenerationTime": "生成时间(最慢)", + "undated": "无日期" + }, + "jobMenu": { + "addToCurrentWorkflow": "添加到当前工作流", + "cancelJob": "取消任务", + "copyErrorMessage": "复制报错信息", + "copyJobId": "复制任务ID", + "delete": "删除", + "deleteAsset": "删除资产", + "download": "下载", + "exportWorkflow": "导出工作流", + "inspectAsset": "查看资产", + "openAsWorkflowNewTab": "在新标签页中读取工作流", + "openWorkflowNewTab": "在新标签页中打开工作流", + "removeJob": "移除任务", + "reportError": "反馈报错" + }, + "toggleJobHistory": "切换任务历史" + }, "releaseToast": { + "description": "在此更新中尝试最新的改进和功能。", "newVersionAvailable": "新版本可用!", "skip": "跳过", "update": "更新", "whatsNew": "新功能?" }, + "rightSidePanel": { + "addFavorite": "收藏", + "advancedInputs": "高级输入", + "bypass": "忽略", + "color": "节点颜色", + "fallbackGroupTitle": "分组", + "fallbackNodeTitle": "节点", + "favorites": "已收藏输入", + "favoritesNone": "暂无已收藏输入", + "favoritesNoneDesc": "你收藏的输入会显示在这里", + "favoritesNoneTooltip": "星标组件后可快速访问,无需选择节点", + "globalSettings": { + "canvas": "画布", + "connectionLinks": "连接线", + "gridSpacing": "网格间距", + "linkShape": "连线形状", + "nodes": "节点", + "nodes2": "节点 2.0", + "searchPlaceholder": "搜索快捷设置...", + "showAdvanced": "显示高级参数", + "showAdvancedTooltip": "这是一个重要设置,开启后将显示所有节点的高级参数", + "showConnectedLinks": "显示已连接的连线", + "showInfoBadges": "显示信息徽章", + "showToolbox": "选中时显示工具箱", + "snapNodesToGrid": "节点吸附到网格", + "title": "全局设置", + "viewAllSettings": "查看所有设置" + }, + "groupSettings": "分组设置", + "groups": "分组", + "hideAdvancedInputsButton": "隐藏高级输入", + "hideInput": "隐藏输入", + "info": "信息", + "inputs": "输入", + "inputsNone": "无输入", + "inputsNoneTooltip": "节点没有输入", + "locateNode": "在画布上定位节点", + "mute": "禁用", + "noSelection": "选择一个节点查看其属性信息。", + "nodeState": "节点状态", + "nodes": "节点", + "nodesNoneDesc": "暂无节点", + "noneSearchDesc": "没有符合搜索条件的项目", + "normal": "正常", + "parameters": "参数", + "pinned": "顶固", + "properties": "属性", + "removeFavorite": "取消收藏", + "settings": "设置", + "showAdvancedInputsButton": "显示高级输入", + "showInput": "显示输入", + "title": "无选中节点 | 选中了 1 个节点 | 选中了 {count} 个节点", + "togglePanel": "开关属性面板", + "workflowOverview": "工作流总览" + }, "selectionToolbox": { "Bypass Group Nodes": "绕过分组节点", "Set Group Nodes to Always": "将分组节点设置为始终", @@ -1389,6 +1936,8 @@ "serverConfig": { "modifiedConfigs": "您已修改以下服务器配置。重启以应用更改。", "restart": "重启", + "restartRequiredToastDetail": "重启软件应用设置。", + "restartRequiredToastSummary": "需要重启", "revertChanges": "撤销更改" }, "serverConfigCategories": { @@ -1457,6 +2006,10 @@ "enable-cors-header": { "name": "启用 CORS header:使用 \"*\" 代表所有来源或指定域名" }, + "enable-manager-legacy-ui": { + "name": "使用旧版管理器UI", + "tooltip": "是用旧版 ComfyUI-Manager UI。" + }, "fast": { "name": "启用一些未经测试且可能降低质量的优化。" }, @@ -1558,6 +2111,7 @@ "CustomColorPalettes": "自定义色彩主题", "DevMode": "开发模式", "EditTokenWeight": "编辑令牌权重", + "Execution": "执行", "Extension": "扩展", "General": "常规", "Graph": "画面", @@ -1572,12 +2126,14 @@ "Mask Editor": "遮罩编辑器", "Menu": "菜单", "ModelLibrary": "模型库", - "NewEditor": "新编辑器", "Node": "节点", "Node Search Box": "节点搜索框", "Node Widget": "节点组件", "NodeLibrary": "节点库", + "Nodes 2_0": "Nodes 2.0", "Notification Preferences": "通知偏好", + "Other": "其他", + "PLY": "PLY", "PlanCredits": "计划与积分", "Pointer": "指针", "Queue": "队列", @@ -1593,10 +2149,11 @@ "UV": "UV", "User": "用户", "Validation": "验证", - "Vue Nodes": "Vue 节点", - "VueNodes": "Vue 节点", + "Vue Nodes": "Nodes 2.0", + "VueNodes": "Nodes 2.0", "Window": "窗口", - "Workflow": "工作流" + "Workflow": "工作流", + "Workspace": "工作区" }, "shape": { "CARD": "卡片", @@ -1622,11 +2179,14 @@ "viewControls": "视图控制" }, "sideToolbar": { + "activeJobStatus": "当前任务:{status}", "assets": "资产", "backToAssets": "返回所有资产", "browseTemplates": "浏览示例模板", "downloads": "下载", + "generatedAssetsHeader": "生成的资源", "helpCenter": "帮助中心", + "importedAssetsHeader": "已导入资源", "labels": { "assets": "资产", "console": "控制台", @@ -1640,7 +2200,18 @@ "workflows": "工作流" }, "logout": "登出", - "mediaAssets": "媒体资源", + "mediaAssets": { + "filter3D": "3D", + "filterAudio": "音频", + "filterImage": "图像", + "filterText": "文本", + "filterVideo": "视频", + "sortFastestFirst": "生成时间(最快)", + "sortLongestFirst": "生成时间(最慢)", + "sortNewestFirst": "最新", + "sortOldestFirst": "最旧", + "title": "媒体资产" + }, "modelLibrary": "模型库", "newBlankWorkflow": "创建空白工作流", "noFilesFound": "未找到文件", @@ -1669,6 +2240,47 @@ }, "openWorkflow": "在本地文件系统中打开工作流", "queue": "队列", + "queueProgressOverlay": { + "activeJobs": "{count} 个活跃任务", + "activeJobsShort": "{count} 个活动任务 | {count} 个活动任务", + "activeJobsSuffix": "活跃任务", + "cancelJobTooltip": "取消任务", + "clearHistory": "清除任务记录", + "clearHistoryDialogAssetsNote": "这些任务生成的资产不会被删除,并且始终可以在资产面板中查看。", + "clearHistoryDialogDescription": "以下所有已完成或失败的任务将从队列面板中删除。", + "clearHistoryDialogTitle": "确定要清除任务记录?", + "clearQueueTooltip": "清理队列", + "clearQueued": "清除已执行", + "colonPercent": ":{percent}", + "currentNode": "当前节点:", + "expandCollapsedQueue": "展开任务队列", + "filterAllWorkflows": "全部工作流", + "filterBy": "筛选方式", + "filterCurrentWorkflow": "当前工作流", + "filterJobs": "筛选任务", + "interruptAll": "中断全部正在运行的任务", + "jobQueue": "任务队列", + "jobsCompleted": "{count} 任务完成 | {count} 任务完成", + "jobsFailed": "{count} 任务失败 | {count} 任务失败", + "moreOptions": "更多设置", + "noActiveJobs": "无活跃任务", + "preview": "预览", + "queuedSuffix": "已执行", + "running": "运行中", + "showAssets": "显示资产", + "showAssetsPanel": "显示资产面板", + "sortBy": "排序方式", + "sortJobs": "排序任务", + "stubClipTextEncode": "CLIP文本编码:", + "title": "队列进度", + "total": "全部:{percent}", + "viewAllJobs": "查看全部任务", + "viewGrid": "网格视图", + "viewJobHistory": "查看任务记录", + "viewList": "列表视图" + }, + "searchAssets": "搜索资产", + "sidebar": "侧边栏", "templates": "模板", "themeToggle": "切换主题", "workflowTab": { @@ -1691,19 +2303,19 @@ "workflows": "工作流" }, "subgraphStore": { - "blueprintName": "子图名称", - "confirmDelete": "此操作将永久从您的库中移除该子图", - "confirmDeleteTitle": "删除子图?", + "blueprintName": "子工作流名称", + "confirmDelete": "此操作将永久从您的库中移除该子工作流", + "confirmDeleteTitle": "删除子工作流?", "hidden": "隐藏/嵌套参数", "hideAll": "全部隐藏", - "loadFailure": "加载子图蓝图失败", - "overwriteBlueprint": "保存将用您的更改覆盖当前子图", - "overwriteBlueprintTitle": "覆盖现有子图?", - "promoteOutsideSubgraph": "不在子图中时无法提升小部件", - "publish": "发布子图", + "loadFailure": "加载子工作流蓝图失败", + "overwriteBlueprint": "保存将用您的更改覆盖当前子工作流", + "overwriteBlueprintTitle": "覆盖现有子工作流?", + "promoteOutsideSubgraph": "不在子工作流中时无法提升小部件", + "publish": "发布子工作流", "publishSuccess": "已保存到节点库", - "publishSuccessMessage": "您可以在节点库的“子图蓝图”下找到您的子图蓝图", - "saveBlueprint": "保存子图到节点库", + "publishSuccessMessage": "您可以在节点库的“子工作流蓝图”下找到您的子工作流蓝图", + "saveBlueprint": "保存子工作流到节点库", "showAll": "全部显示", "showRecommended": "显示推荐控件", "shown": "节点上显示" @@ -1711,24 +2323,58 @@ "subscription": { "addApiCredits": "添加API额度", "addCredits": "添加积分", + "addCreditsLabel": "随时获取更多积分", "benefits": { "benefit1": "合作伙伴节点的月度积分 — 按需充值", "benefit2": "每个队列最长运行 30 分钟" }, "beta": "测试版", + "billedMonthly": "每月付款", + "billedYearly": "{total} 每年付款", + "billingComingSoon": { + "message": "团队计费功能即将上线。您将可以为您的工作区按成员数订阅套餐。敬请关注后续更新。", + "title": "即将推出" + }, + "cancelSubscription": "取消订阅", + "changeTo": "更改为 {plan}", "comfyCloud": "Comfy 云", + "comfyCloudLogo": "Comfy Cloud Logo", + "contactOwnerToSubscribe": "请联系工作区所有者进行订阅", + "contactUs": "联系我们", + "creditsRemainingThisMonth": "本月剩余积分", + "creditsRemainingThisYear": "今年剩余积分", + "creditsYouveAdded": "你获得的积分", + "currentPlan": "当前订阅计划", + "customLoRAsLabel": "导入您的 Lora", + "description": "选择最适合您的订阅计划", "expiresDate": "于 {date} 过期", + "gpuLabel": "RTX 6000 Pro (96GB VRAM)", + "haveQuestions": "对企业级有疑问?", "invoiceHistory": "发票历史", "learnMore": "了解更多", + "managePayment": "管理付款", + "managePlan": "管理订阅", "manageSubscription": "管理订阅", + "maxDuration": { + "creator": "30 分钟", + "founder": "30 分钟", + "pro": "1 小时", + "standard": "30 分钟" + }, + "maxDurationLabel": "运行单个工作流的最大时长", "messageSupport": "消息支持", + "monthly": "月度", "monthlyBonusDescription": "每月积分奖励", + "monthlyCreditsInfo": "积分每月刷新,不会保留", + "monthlyCreditsLabel": "每月积分", "monthlyCreditsRollover": "这些积分将结转到下个月", + "mostPopular": "最受欢迎", "nextBillingCycle": "下一个计费周期", "partnerNodesBalance": "\"合作伙伴节点\"积分余额", "partnerNodesCredits": "合作伙伴节点积分", "partnerNodesDescription": "用于运行商业/专有模型", "perMonth": "美元 / 月", + "plansAndPricing": "订阅和定价", "prepaidCreditsInfo": "单独购买且不会过期的积分", "prepaidDescription": "预付款额度", "renewsDate": "将于 {date} 续订", @@ -1738,14 +2384,46 @@ "waitingForSubscription": "请在新标签页中完成订阅。我们会自动检测到您已完成!" }, "subscribeNow": "立即订阅", + "subscribeTo": "订阅 {plan}", "subscribeToComfyCloud": "订阅 Comfy Cloud", "subscribeToRun": "订阅", "subscribeToRunFull": "订阅 Run", + "subscriptionRequiredMessage": "成员在云端运行工作流需要订阅", + "tierNameYearly": "{name} 年度", + "tiers": { + "creator": { + "name": "Creator" + }, + "founder": { + "name": "Founder's Edition" + }, + "pro": { + "name": "Pro" + }, + "standard": { + "name": "Standard" + } + }, "title": "订阅", "titleUnsubscribed": "订阅 Comfy Cloud", "totalCredits": "总积分", + "upgrade": "升级", + "upgradePlan": "升级订阅", + "upgradeTo": "升级到 {plan}", + "usdPerMonth": "USD / mo", + "videoEstimateExplanation": "这些预估基于 Wan Fun Control 生成 5 秒视频。", + "videoEstimateHelp": "这是什么?", + "videoEstimateLabel": "可使用 Wan Fun Control 模板生成 5 秒视频的数量", + "videoEstimateTryTemplate": "试试 Wan Fun Control template →", + "videoTemplateBasedCredits": "使用 Wan 2.2 图像转视频生成的视频", + "viewEnterprise": "查看企业", "viewMoreDetails": "查看更多详情", + "viewMoreDetailsPlans": "查看有关订阅和定价的更多信息", "viewUsageHistory": "查看使用历史", + "workspaceNotSubscribed": "此工作区未订阅", + "yearly": "年度", + "yearlyCreditsLabel": "总共年度积分", + "yearlyDiscount": "20% 减免", "yourPlanIncludes": "您的计划包括:" }, "tabMenu": { @@ -1757,8 +2435,14 @@ "duplicateTab": "复制标签", "removeFromBookmarks": "从书签中移除" }, + "templateWidgets": { + "sort": { + "searchPlaceholder": "搜索中..." + } + }, "templateWorkflows": { "activeFilters": "筛选条件:", + "allTemplates": "全部模板", "categories": "分类", "category": { "3D": "3D", @@ -1785,6 +2469,7 @@ "error": { "templateNotFound": "未找到模板 \"{templateName}\"" }, + "licenseFilter": "许可证", "loading": "正在加载模板...", "loadingMore": "正在加载更多模板...", "modelFilter": "模型筛选", @@ -1801,16 +2486,18 @@ "default": "默认", "modelSizeLowToHigh": "模型大小(从低到高)", "newest": "最新", + "popular": "热门", "recommended": "推荐", "searchPlaceholder": "搜索...", "vramLowToHigh": "VRAM 使用量(从低到高)" }, "sorting": "排序方式", "title": "从模板开始", + "useCaseFilter": "使用例", "useCasesSelected": "已选 {count} 个用例" }, "toastMessages": { - "cannotCreateSubgraph": "无法创建子图", + "cannotCreateSubgraph": "无法创建子工作流", "couldNotDetermineFileType": "无法确定文件类型", "dropFileError": "无法处理掉落的项目:{error}", "emptyCanvas": "画布为空", @@ -1821,7 +2508,7 @@ "failedExecutionPathResolution": "无法解析所选节点的路径", "failedToAccessBillingPortal": "访问账单门户失败:{error}", "failedToApplyTexture": "应用纹理失败", - "failedToConvertToSubgraph": "无法将项目转换为子图", + "failedToConvertToSubgraph": "无法将项目转换为子工作流", "failedToCreateCustomer": "创建客户失败:{error}", "failedToDownloadFile": "文件下载失败", "failedToExportModel": "无法将模型导出为 {format}", @@ -1835,9 +2522,22 @@ "failedToLoadModel": "无法加载3D模型", "failedToPurchaseCredits": "购买积分失败:{error}", "failedToQueue": "排队失败", + "failedToToggleCamera": "切换镜头失败", + "failedToToggleGrid": "切换网格失败", + "failedToUpdateBackgroundColor": "更新背景色失败", + "failedToUpdateBackgroundImage": "更新背景图像失败", + "failedToUpdateBackgroundRenderMode": "切换背景模式到 {mode} 失败", + "failedToUpdateEdgeThreshold": "更新边缘阈值失败", + "failedToUpdateFOV": "更新FOV失败", + "failedToUpdateLightIntensity": "更新光照强度失败", + "failedToUpdateMaterialMode": "更新材质模式失败", + "failedToUpdateUpDirection": "更新向上轴失败", + "failedToUploadBackgroundImage": "上传背景图像失败", "fileLoadError": "无法在 {fileName} 中找到工作流", + "fileTooLarge": "文件过大({size} MB)。最大支持大小为 {maxSize} MB", "fileUploadFailed": "文件上传失败", "interrupted": "执行已被中断", + "legacyMaskEditorDeprecated": "旧版遮罩编辑器已弃用,即将删除。", "migrateToLitegraphReroute": "将来的版本中将删除重定向节点。点击以迁移到litegraph-native重定向。", "modelLoadedSuccessfully": "3D模型加载成功", "no3dScene": "没有3D场景可以应用纹理", @@ -1864,12 +2564,14 @@ "selectUser": "选择用户" }, "userSettings": { + "accountSettings": "用户设置", "email": "电子邮件", "name": "名称", "notSet": "未设置", "provider": "登录方式", - "title": "用户设置", - "updatePassword": "更新密码" + "title": "我的用户设置", + "updatePassword": "更新密码", + "workspaceSettings": "工作区设置" }, "validation": { "descriptionRequired": "描述是必填的", @@ -1898,22 +2600,32 @@ "updateFrontend": "更新前端" }, "vueNodesBanner": { - "message": "节点外观焕然一新", + "desc": "– 更灵活的工作流,强力的新组件,为拓展性而生", + "title": "介绍 Nodes 2.0", "tryItOut": "试试看" }, "vueNodesMigration": { "button": "打开设置", "message": "是否偏好经典节点设计?" }, + "vueNodesMigrationMainMenu": { + "message": "在主菜单中随时切换回 Nodes 2.0" + }, "welcome": { "getStarted": "开始使用", "title": "欢迎使用 ComfyUI" }, "whatsNewPopup": { + "later": "Later", "learnMore": "了解更多", "noReleaseNotes": "暂无更新说明。" }, + "widgetFileUpload": { + "browseFiles": "浏览文件", + "dropPrompt": "将文件拖到此处或" + }, "widgets": { + "node2only": "仅限 Node 2.0", "selectModel": "选择模型", "uploadSelect": { "placeholder": "请选择...", @@ -1922,6 +2634,26 @@ "placeholderModel": "请选择模型...", "placeholderUnknown": "请选择媒体...", "placeholderVideo": "请选择视频..." + }, + "valueControl": { + "decrement": "递减值", + "decrementDesc": "数值-1或切换到上一个选项", + "editSettings": "改变控制设置", + "fixed": "固定值", + "fixedDesc": "数值不变", + "header": { + "after": "之后", + "before": "之前", + "postfix": "正在运行工作流:", + "prefix": "自动更新该值" + }, + "increment": "递增值", + "incrementDesc": "数值+1或切换到下一个选项", + "linkToGlobal": "连接到", + "linkToGlobalDesc": "唯一值连接到全局值控制设置", + "linkToGlobalSeed": "全局值", + "randomize": "随机值", + "randomizeDesc": "每次运行后随机化该值" } }, "workflowService": { @@ -1929,6 +2661,146 @@ "exportWorkflow": "导出工作流", "saveWorkflow": "保存工作流" }, + "workspace": { + "addedToWorkspace": "您已被加入 {workspaceName}", + "inviteAccepted": "邀请已接受", + "inviteFailed": "接受邀请失败", + "unsavedChanges": { + "message": "您有未保存的更改。是否要放弃这些更改并切换工作区?", + "title": "未保存的更改" + } + }, + "workspaceAuth": { + "errors": { + "accessDenied": "您无权访问此工作区", + "invalidFirebaseToken": "身份验证失败。请重新登录。", + "notAuthenticated": "您必须登录才能访问工作区", + "tokenExchangeFailed": "与工作区认证失败:{error}", + "workspaceNotFound": "未找到工作区" + } + }, + "workspacePanel": { + "createWorkspaceDialog": { + "create": "创建", + "message": "工作区让成员共享积分池。创建后您将成为所有者。", + "nameLabel": "工作区名称*", + "namePlaceholder": "请输入工作区名称", + "title": "创建新工作区" + }, + "dashboard": { + "placeholder": "仪表盘工作区设置" + }, + "deleteDialog": { + "message": "任何未使用的积分或未保存的资源都将丢失。此操作无法撤销。", + "messageWithName": "删除“{name}”?任何未使用的积分或未保存的资源都将丢失。此操作无法撤销。", + "title": "删除此工作区?" + }, + "editWorkspaceDialog": { + "nameLabel": "工作区名称", + "save": "保存", + "title": "编辑工作区详情" + }, + "invite": "邀请", + "inviteLimitReached": "您已达到最多 50 名成员的上限", + "inviteMember": "邀请成员", + "inviteMemberDialog": { + "createLink": "创建链接", + "linkCopied": "已复制", + "linkCopyFailed": "复制链接失败", + "linkStep": { + "copyLink": "复制链接", + "done": "完成", + "message": "请确保其账号使用的是该邮箱。", + "title": "将此链接发送给对方" + }, + "message": "创建一个可分享的邀请链接发送给他人", + "placeholder": "输入对方邮箱", + "title": "邀请他人加入此工作区" + }, + "leaveDialog": { + "leave": "离开", + "message": "除非联系工作区所有者,否则您将无法重新加入。", + "title": "离开此工作区?" + }, + "members": { + "actions": { + "copyLink": "复制邀请链接", + "removeMember": "移除成员", + "revokeInvite": "撤销邀请" + }, + "columns": { + "expiryDate": "过期日期", + "inviteDate": "邀请日期", + "joinDate": "加入日期" + }, + "createNewWorkspace": "创建新工作区。", + "membersCount": "{count}/50 名成员", + "noInvites": "暂无待处理邀请", + "noMembers": "暂无成员", + "pendingInvitesCount": "{count} 个待处理邀请", + "personalWorkspaceMessage": "您目前无法邀请其他成员加入您的个人工作区。如需添加成员,请", + "tabs": { + "active": "已激活", + "pendingCount": "待处理({count})" + } + }, + "menu": { + "deleteWorkspace": "删除工作区", + "deleteWorkspaceDisabledTooltip": "请先取消工作区的有效订阅", + "editWorkspace": "编辑工作区详情", + "leaveWorkspace": "离开工作区" + }, + "removeMemberDialog": { + "error": "移除成员失败", + "message": "该成员将被移出您的工作区。其已使用的积分不会被退还。", + "remove": "移除成员", + "success": "成员已移除", + "title": "移除该成员?" + }, + "revokeInviteDialog": { + "message": "该成员将无法再加入您的工作区,其邀请链接将失效。", + "revoke": "取消邀请", + "title": "取消邀请此人?" + }, + "tabs": { + "dashboard": "仪表盘", + "membersCount": "成员({count})", + "planCredits": "套餐与积分" + }, + "toast": { + "failedToCreateWorkspace": "创建工作区失败", + "failedToDeleteWorkspace": "删除工作区失败", + "failedToFetchWorkspaces": "加载工作区失败", + "failedToLeaveWorkspace": "离开工作区失败", + "failedToUpdateWorkspace": "更新工作区失败", + "workspaceCreated": { + "message": "订阅套餐,邀请队友,开始协作。", + "subscribe": "订阅", + "title": "工作区已创建" + }, + "workspaceDeleted": { + "message": "该工作区已被永久删除。", + "title": "工作区已删除" + }, + "workspaceLeft": { + "message": "您已退出该工作区。", + "title": "已退出工作区" + }, + "workspaceUpdated": { + "message": "工作区详情已保存。", + "title": "工作区已更新" + } + } + }, + "workspaceSwitcher": { + "createWorkspace": "创建新工作区", + "maxWorkspacesReached": "您最多只能拥有10个工作区。请删除一个以创建新工作区。", + "personal": "个人", + "roleMember": "成员", + "roleOwner": "所有者", + "subscribe": "订阅", + "switchWorkspace": "切换工作区" + }, "zoomControls": { "hideMinimap": "隐藏小地图", "label": "缩放控制", diff --git a/src/locales/zh/nodeDefs.json b/src/locales/zh/nodeDefs.json index dffe421bd..6bba16dc8 100644 --- a/src/locales/zh/nodeDefs.json +++ b/src/locales/zh/nodeDefs.json @@ -14,7 +14,7 @@ "tooltip": "控制扩散过程中的引导运行平均值,设置为0时禁用。" }, "norm_threshold": { - "name": "norm_threshold", + "name": "向量归一化", "tooltip": "将引导向量归一化到此值,设置为 0 时禁用归一化。" } }, @@ -39,6 +39,87 @@ "sigmas": { "name": "Sigmas" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "AddTextPrefix": { + "display_name": "文本前添加", + "inputs": { + "prefix": { + "name": "前缀", + "tooltip": "要添加的内容。" + }, + "texts": { + "name": "文本", + "tooltip": "需要被添加的文本。" + } + }, + "outputs": { + "0": { + "name": "文本", + "tooltip": "处理后的文本" + } + } + }, + "AddTextSuffix": { + "display_name": "文本后添加", + "inputs": { + "suffix": { + "name": "后缀", + "tooltip": "要添加的内容。" + }, + "texts": { + "name": "文本", + "tooltip": "需要被添加的文本。" + } + }, + "outputs": { + "0": { + "name": "文本", + "tooltip": "处理后的文本" + } + } + }, + "AdjustBrightness": { + "display_name": "调整亮度", + "inputs": { + "factor": { + "name": "系数", + "tooltip": "亮度系数。1.0表示无变化,<1.0表示更暗,>1.0表示更亮。" + }, + "images": { + "name": "图像", + "tooltip": "需要调整的图像" + } + }, + "outputs": { + "0": { + "name": "图像", + "tooltip": "调整后的图像" + } + } + }, + "AdjustContrast": { + "display_name": "调整对比度", + "inputs": { + "factor": { + "name": "系数", + "tooltip": "对比度系数。1.0表示无变化,<1.0表示更低,>1.0表示更高。" + }, + "images": { + "name": "图像", + "tooltip": "需要调整的图像" + } + }, + "outputs": { + "0": { + "name": "图像", + "tooltip": "调整后的图像" + } } }, "AlignYourStepsScheduler": { @@ -70,21 +151,31 @@ "name": "音量", "tooltip": "音量调整,单位为分贝 (dB)。0 = 无变化,+6 = 加倍,-6 = 减半,等等" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "AudioConcat": { - "description": "将 audio1 与 audio2 按指定方向连接。", + "description": "将 音频1 与 音频2 按指定方向连接。", "display_name": "音频拼接", "inputs": { "audio1": { - "name": "audio1" + "name": "音频1" }, "audio2": { "name": "音频2" }, "direction": { "name": "方向", - "tooltip": "是否在 audio1 之后或之前追加 audio2。" + "tooltip": "音频2 在 音频1 之前或之后。" + } + }, + "outputs": { + "0": { + "tooltip": null } } }, @@ -105,10 +196,10 @@ } }, "AudioEncoderLoader": { - "display_name": "音频编码器加载器", + "display_name": "加载音频编码器", "inputs": { "audio_encoder_name": { - "name": "音频编码器名称" + "name": "音频编码器" } }, "outputs": { @@ -118,11 +209,11 @@ } }, "AudioMerge": { - "description": "通过叠加两个音频轨道的波形来合并它们。", + "description": "叠加 音频1 和 音频2 轨道的波形。", "display_name": "音频合并", "inputs": { "audio1": { - "name": "audio1" + "name": "音频1" }, "audio2": { "name": "音频2" @@ -131,6 +222,11 @@ "name": "合并方法", "tooltip": "用于组合音频波形的方法。" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BasicGuider": { @@ -142,6 +238,11 @@ "model": { "name": "模型" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BasicScheduler": { @@ -159,16 +260,60 @@ "steps": { "name": "步数" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchImagesNode": { + "display_name": "批量图像", + "inputs": { + "images": { + "name": "图像" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchLatentsNode": { + "display_name": "批量latent", + "inputs": { + "latents": { + "name": "latent" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchMasksNode": { + "display_name": "批量mask", + "inputs": { + "masks": { + "name": "mask" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "BetaSamplingScheduler": { "display_name": "Beta采样调度器", "inputs": { "alpha": { - "name": "阿尔法" + "name": "alpha" }, "beta": { - "name": "贝塔" + "name": "beta" }, "model": { "name": "模型" @@ -176,6 +321,73 @@ "steps": { "name": "步数" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BriaImageEditNode": { + "description": "使用 Bria 最新模型编辑图像", + "display_name": "Bria 图像编辑", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "guidance_scale": { + "name": "guidance_scale", + "tooltip": "数值越高,图像越贴合 prompt。" + }, + "image": { + "name": "image" + }, + "mask": { + "name": "mask", + "tooltip": "如未指定,编辑将应用于整个图像。" + }, + "model": { + "name": "model" + }, + "moderation": { + "name": "moderation", + "tooltip": "内容审核设置" + }, + "moderation_prompt_content_moderation": { + "name": "prompt_content_moderation" + }, + "moderation_visual_input_moderation": { + "name": "visual_input_moderation" + }, + "moderation_visual_output_moderation": { + "name": "visual_output_moderation" + }, + "negative_prompt": { + "name": "negative_prompt" + }, + "prompt": { + "name": "prompt", + "tooltip": "编辑图像的指令" + }, + "seed": { + "name": "seed" + }, + "steps": { + "name": "steps" + }, + "structured_prompt": { + "name": "structured_prompt", + "tooltip": "包含结构化编辑提示的 JSON 字符串。使用此项可实现更精确、可编程的控制,替代常规 prompt。" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "structured_prompt", + "tooltip": null + } } }, "ByteDanceFirstLastFrameNode": { @@ -201,13 +413,16 @@ "name": "第一帧", "tooltip": "用于视频的第一帧。" }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "此参数仅对 seedance-1-5-pro 模型有效,其他模型将被忽略。" + }, "last_frame": { "name": "最后一帧", "tooltip": "用于视频的最后一帧。" }, "model": { - "name": "模型", - "tooltip": "模型名称" + "name": "模型" }, "prompt": { "name": "提示", @@ -248,8 +463,7 @@ "tooltip": "要编辑的基础图像" }, "model": { - "name": "模型", - "tooltip": "模型名称" + "name": "模型" }, "prompt": { "name": "提示", @@ -286,8 +500,7 @@ "tooltip": "图像的自定义高度。仅当 `size_preset` 设置为 `Custom` 时该值才生效" }, "model": { - "name": "模型", - "tooltip": "模型名称" + "name": "模型" }, "prompt": { "name": "提示", @@ -336,8 +549,7 @@ "tooltip": "一到四张图片。" }, "model": { - "name": "模型", - "tooltip": "模型名称" + "name": "模型" }, "prompt": { "name": "提示", @@ -381,13 +593,16 @@ "name": "时长", "tooltip": "输出视频的时长(以秒为单位)。" }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "此参数仅对 seedance-1-5-pro 模型有效,其他模型将被忽略。" + }, "image": { "name": "图片", "tooltip": "用于视频的第一帧。" }, "model": { - "name": "模型", - "tooltip": "模型名称" + "name": "模型" }, "prompt": { "name": "提示", @@ -489,9 +704,12 @@ "name": "时长", "tooltip": "输出视频的时长(秒)。" }, + "generate_audio": { + "name": "generate_audio", + "tooltip": "此参数仅对 seedance-1-5-pro 模型有效,其他模型将被忽略。" + }, "model": { - "name": "模型", - "tooltip": "模型名称" + "name": "模型" }, "prompt": { "name": "提示", @@ -531,6 +749,11 @@ "positive": { "name": "正面条件" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "CFGNorm": { @@ -545,7 +768,7 @@ }, "outputs": { "0": { - "name": "修补模型", + "name": "模型", "tooltip": null } } @@ -554,12 +777,12 @@ "display_name": "CFGZeroStar", "inputs": { "model": { - "name": "model" + "name": "模型" } }, "outputs": { "0": { - "name": "patched_model", + "name": "模型", "tooltip": null } } @@ -568,7 +791,7 @@ "display_name": "CLIP注意力相乘", "inputs": { "clip": { - "name": "clip" + "name": "CLIP" }, "k": { "name": "k" @@ -685,7 +908,7 @@ } }, "CLIPTextEncodeControlnet": { - "display_name": "CLIP文本编码ControlNet", + "display_name": "CLIP文本编码(ControlNet)", "inputs": { "clip": { "name": "clip" @@ -704,7 +927,7 @@ } }, "CLIPTextEncodeFlux": { - "display_name": "CLIP文本编码Flux", + "display_name": "CLIP文本编码(Flux)", "inputs": { "clip": { "name": "clip" @@ -726,7 +949,7 @@ } }, "CLIPTextEncodeHiDream": { - "display_name": "CLIPTextEncodeHiDream", + "display_name": "CLIP文本编码(HiDream)", "inputs": { "clip": { "name": "clip" @@ -751,7 +974,7 @@ } }, "CLIPTextEncodeHunyuanDiT": { - "display_name": "CLIP文本编码混元DiT", + "display_name": "CLIP文本编码(混元DiT)", "inputs": { "bert": { "name": "bert" @@ -769,20 +992,39 @@ } } }, + "CLIPTextEncodeKandinsky5": { + "display_name": "CLIP文本编码(Kandinsky5)", + "inputs": { + "clip": { + "name": "clip" + }, + "clip_l": { + "name": "clip_l" + }, + "qwen25_7b": { + "name": "qwen25_7b" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "CLIPTextEncodeLumina2": { "description": "使用CLIP模型将系统提示和用户提示编码成可以用来引导扩散模型生成特定图像的嵌入。", - "display_name": "Lumina2的CLIP文本编码", + "display_name": "CLIP文本编码(Lumina2)", "inputs": { "clip": { "name": "clip", "tooltip": "用于编码文本的CLIP模型。" }, "system_prompt": { - "name": "system_prompt", + "name": "系统提示词", "tooltip": "Lumina2提供两种类型的系统提示:优越:你是一个设计来根据文本提示或用户提示生成优越图像的助手,这些图像具有基于文本提示的优越度的图像-文本对齐。对齐:你是一个设计来根据文本提示生成高质量图像的助手,这些图像具有基于文本提示的最高度的图像-文本对齐。" }, "user_prompt": { - "name": "user_prompt", + "name": "提示词", "tooltip": "需要编码的文本。" } }, @@ -794,7 +1036,7 @@ }, "CLIPTextEncodePixArtAlpha": { "description": "编码文本并设置PixArt Alpha的分辨率条件。不适用于PixArt Sigma。", - "display_name": "CLIPTextEncodePixArtAlpha", + "display_name": "CLIP文本编码(PixArtAlpha)", "inputs": { "clip": { "name": "剪辑" @@ -816,7 +1058,7 @@ } }, "CLIPTextEncodeSD3": { - "display_name": "CLIP文本编码SD3", + "display_name": "CLIP文本编码(SD3)", "inputs": { "clip": { "name": "clip" @@ -841,7 +1083,7 @@ } }, "CLIPTextEncodeSDXL": { - "display_name": "CLIP文本编码SDXL", + "display_name": "CLIP文本编码(SDXL)", "inputs": { "clip": { "name": "clip" @@ -878,7 +1120,7 @@ } }, "CLIPTextEncodeSDXLRefiner": { - "display_name": "CLIP文本编码SDXL精炼器", + "display_name": "CLIP文本编码(SDXLRefiner)", "inputs": { "ascore": { "name": "美学分数" @@ -959,6 +1201,29 @@ } } }, + "CenterCropImages": { + "display_name": "裁剪图像(中心)", + "inputs": { + "height": { + "name": "高度", + "tooltip": "裁剪框高度" + }, + "images": { + "name": "图像", + "tooltip": "需要调整的图像" + }, + "width": { + "name": "宽度", + "tooltip": "裁剪框宽度" + } + }, + "outputs": { + "0": { + "name": "图像", + "tooltip": "调整后的图像" + } + } + }, "CheckpointLoader": { "display_name": "Ckeckpoint加载器(已弃用)", "inputs": { @@ -1095,6 +1360,26 @@ } } }, + "ComfySwitchNode": { + "display_name": "切换", + "inputs": { + "on_false": { + "name": "为假时" + }, + "on_true": { + "name": "为真时" + }, + "switch": { + "name": "切换" + } + }, + "outputs": { + "0": { + "name": "输出", + "tooltip": null + } + } + }, "ConditioningAverage": { "display_name": "条件平均", "inputs": { @@ -1329,10 +1614,12 @@ }, "outputs": { "0": { - "name": "正面条件" + "name": "正面条件", + "tooltip": null }, "1": { - "name": "负面条件" + "name": "负面条件", + "tooltip": null } } }, @@ -1391,6 +1678,10 @@ "name": "维度", "tooltip": "应用上下文窗口的维度。" }, + "freenoise": { + "name": "Freenoise", + "tooltip": "是否应用 Freenoise,优化窗口融合。" + }, "fuse_method": { "name": "融合方法", "tooltip": "用于融合上下文窗口的方法。" @@ -1550,7 +1841,7 @@ } }, "CosmosImageToVideoLatent": { - "display_name": "Cosmos图像到视频潜在", + "display_name": "图像到视频Latent(Cosmos)", "inputs": { "batch_size": { "name": "批量大小" @@ -1581,10 +1872,10 @@ } }, "CosmosPredict2ImageToVideoLatent": { - "display_name": "CosmosPredict2ImageToVideoLatent", + "display_name": "图像到视频Latent(CosmosPredict2)", "inputs": { "batch_size": { - "name": "batch_size" + "name": "批次大小" }, "end_image": { "name": "结束图像" @@ -1791,8 +2082,32 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, + "CustomCombo": { + "display_name": "自定义组合", + "inputs": { + "choice": { + "name": "选择" + }, + "index": { + }, + "option1": { + } + }, + "outputs": [ + null, + { + "name": "索引", + "tooltip": null + } + ] + }, "DiffControlNetLoader": { "display_name": "加载ControlNet模型(diff)", "inputs": { @@ -1829,7 +2144,12 @@ } }, "DisableNoise": { - "display_name": "禁用噪波" + "display_name": "禁用噪波", + "outputs": { + "0": { + "tooltip": null + } + } }, "DualCFGGuider": { "display_name": "双CFG引导器", @@ -1855,6 +2175,11 @@ "style": { "name": "样式" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "DualCLIPLoader": { @@ -1880,7 +2205,7 @@ "display_name": "EasyCache", "inputs": { "end_percent": { - "name": "end_percent", + "name": "结束位置", "tooltip": "结束使用 EasyCache 的相对采样步数。" }, "model": { @@ -1888,15 +2213,15 @@ "tooltip": "要添加 EasyCache 的模型。" }, "reuse_threshold": { - "name": "reuse_threshold", + "name": "重用阈值", "tooltip": "重用缓存步骤的阈值。" }, "start_percent": { - "name": "start_percent", + "name": "开始位置", "tooltip": "开始使用 EasyCache 的相对采样步数。" }, "verbose": { - "name": "verbose", + "name": "调试信息", "tooltip": "是否记录详细信息。" } }, @@ -1907,11 +2232,11 @@ } }, "EmptyAceStepLatentAudio": { - "display_name": "EmptyAceStepLatentAudio", + "display_name": "空Latent音频(AceStep)", "inputs": { "batch_size": { - "name": "batch_size", - "tooltip": "批次中的潜在图像数量。" + "name": "批次大小", + "tooltip": "批次中的 Latent 数量。" }, "seconds": { "name": "秒数" @@ -1927,24 +2252,29 @@ "display_name": "空音频", "inputs": { "channels": { - "name": "channels", + "name": "通道", "tooltip": "音频通道数(1 为单声道,2 为立体声)。" }, "duration": { - "name": "duration", + "name": "长度", "tooltip": "空音频片段的持续时间(秒)" }, "sample_rate": { - "name": "sample_rate", + "name": "采样率", "tooltip": "空音频片段的采样率。" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyChromaRadianceLatentImage": { - "display_name": "EmptyChromaRadianceLatentImage", + "display_name": "空Latent图像(ChromaRadiance)", "inputs": { "batch_size": { - "name": "batch_size" + "name": "批次大小" }, "height": { "name": "高度" @@ -1960,7 +2290,7 @@ } }, "EmptyCosmosLatentVideo": { - "display_name": "空的Cosmos潜在视频", + "display_name": "空Latent视频(Cosmos)", "inputs": { "batch_size": { "name": "批量大小" @@ -1981,12 +2311,31 @@ } } }, - "EmptyHunyuanImageLatent": { - "display_name": "EmptyHunyuanImageLatent", + "EmptyFlux2LatentImage": { + "display_name": "空Latent图像(Flux2)", "inputs": { "batch_size": { "name": "batch_size" }, + "height": { + "name": "height" + }, + "width": { + "name": "width" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "EmptyHunyuanImageLatent": { + "display_name": "空Latent图像(Hunyuan)", + "inputs": { + "batch_size": { + "name": "批次大小" + }, "height": { "name": "高度" }, @@ -2001,7 +2350,7 @@ } }, "EmptyHunyuanLatentVideo": { - "display_name": "空Latent视频(混元)", + "display_name": "空Latent视频(Hunyuan)", "inputs": { "batch_size": { "name": "批量大小" @@ -2022,6 +2371,28 @@ } } }, + "EmptyHunyuanVideo15Latent": { + "display_name": "空Latent视频(Hunyuan1.5)", + "inputs": { + "batch_size": { + "name": "批次大小" + }, + "height": { + "name": "高度" + }, + "length": { + "name": "时长" + }, + "width": { + "name": "宽度" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptyImage": { "display_name": "空图像", "inputs": { @@ -2071,18 +2442,28 @@ "seconds": { "name": "秒" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyLatentHunyuan3Dv2": { - "display_name": "EmptyLatentHunyuan3Dv2", + "display_name": "空Latent图像(Hunyuan3Dv2)", "inputs": { "batch_size": { - "name": "批量大小", - "tooltip": "批量中的潜在图像数量。" + "name": "批次大小", + "tooltip": "批次中的Latent数量。" }, "resolution": { "name": "分辨率" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "EmptyLatentImage": { @@ -2130,6 +2511,28 @@ } } }, + "EmptyQwenImageLayeredLatentImage": { + "display_name": "空Latent图像(QwenImageLayerd)", + "inputs": { + "batch_size": { + "name": "批次大小" + }, + "height": { + "name": "高度" + }, + "layers": { + "name": "图层" + }, + "width": { + "name": "宽度" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "EmptySD3LatentImage": { "display_name": "空Latent图像(SD3)", "inputs": { @@ -2150,13 +2553,13 @@ } }, "Epsilon Scaling": { - "display_name": "Epsilon Scaling", + "display_name": "Epsilon缩放", "inputs": { "model": { "name": "模型" }, "scaling_factor": { - "name": "scaling_factor" + "name": "系数" } }, "outputs": { @@ -2172,15 +2575,20 @@ "name": "最大Sigma" }, "sigma_min": { - "name": "最新Sigma" + "name": "最小Sigma" }, "steps": { "name": "步数" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ExtendIntermediateSigmas": { - "display_name": "ExtendIntermediateSigmas", + "display_name": "插值扩展Sigmas", "inputs": { "end_at_sigma": { "name": "结束 sigma" @@ -2189,7 +2597,7 @@ "name": "sigmas" }, "spacing": { - "name": "间距" + "name": "间距方式" }, "start_at_sigma": { "name": "起始 sigma" @@ -2197,6 +2605,11 @@ "steps": { "name": "步数" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FeatherMask": { @@ -2217,6 +2630,11 @@ "top": { "name": "顶" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FlipSigmas": { @@ -2225,14 +2643,110 @@ "sigmas": { "name": "sigmas" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2MaxImageNode": { + "description": "根据提示词和分辨率同步生成图像。", + "display_name": "Flux.2 [max] 图像", + "inputs": { + "control_after_generate": { + "name": "生成后控制" + }, + "height": { + "name": "高度" + }, + "images": { + "name": "图像", + "tooltip": "参考图像(最多9张)" + }, + "prompt": { + "name": "提示词", + "tooltip": "用于生成或编辑的提示词" + }, + "prompt_upsampling": { + "name": "提示词上采样", + "tooltip": "选择启用提示词采样,启用后会自动修改提示词生成更具创意的内容。" + }, + "seed": { + "name": "随机种", + "tooltip": "创建噪波的随机种" + }, + "width": { + "name": "宽度" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2ProImageNode": { + "description": "根据提示词和分辨率同步生成图像。", + "display_name": "Flux.2 [pro] 图像", + "inputs": { + "control_after_generate": { + "name": "生成后控制" + }, + "height": { + "name": "高度" + }, + "images": { + "name": "图像", + "tooltip": "参考图像(最多9张)" + }, + "prompt": { + "name": "提示词", + "tooltip": "用于生成或编辑的提示词" + }, + "prompt_upsampling": { + "name": "提示词上采样", + "tooltip": "选择启用提示词采样,启用后会自动修改提示词生成更具创意的内容。" + }, + "seed": { + "name": "随机种", + "tooltip": "创建噪波的随机种" + }, + "width": { + "name": "宽度" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Flux2Scheduler": { + "display_name": "Flux2调度器", + "inputs": { + "height": { + "name": "高度" + }, + "steps": { + "name": "步数" + }, + "width": { + "name": "宽度" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FluxDisableGuidance": { - "description": "此节点完全禁用Flux和类似Flux模型上的指导嵌入", + "description": "此节点完全禁用Flux和类似Flux模型上的引导嵌入", "display_name": "Flux禁用指导", "inputs": { "conditioning": { - "name": "conditioning" + "name": "条件" } }, "outputs": { @@ -2258,11 +2772,11 @@ } }, "FluxKontextImageScale": { - "description": "此节点将图像调整为更适合 flux kontext 的尺寸。", - "display_name": "FluxKontextImageScale", + "description": "将图像调整为更适合 Flux Kontext 的尺寸。", + "display_name": "图像缩放为FluxKontext", "inputs": { "image": { - "name": "image" + "name": "图像" } }, "outputs": { @@ -2273,11 +2787,11 @@ }, "FluxKontextMaxImageNode": { "description": "使用Flux.1 Kontext [max]通过API基于提示词和宽高比编辑图像。", - "display_name": "Flux.1 Kontext [max] Image", + "display_name": "Flux.1 Kontext [max] 图像", "inputs": { "aspect_ratio": { "name": "宽高比", - "tooltip": "图像宽高比;必须在1:4到4:1之间。" + "tooltip": "图像宽高比;必须在1:4和4:1之间。" }, "control_after_generate": { "name": "生成后控制" @@ -2437,7 +2951,7 @@ "name": "图像" }, "mask": { - "name": "mask" + "name": "遮罩" }, "prompt": { "name": "提示词", @@ -2508,18 +3022,18 @@ "display_name": "FreSca", "inputs": { "freq_cutoff": { - "name": "freq_cutoff", + "name": "低频阈值", "tooltip": "围绕中心被视为低频的频率索引数量" }, "model": { - "name": "model" + "name": "模型" }, "scale_high": { - "name": "scale_high", + "name": "高频缩放", "tooltip": "高频分量的缩放因子" }, "scale_low": { - "name": "scale_low", + "name": "低频缩放", "tooltip": "低频分量的缩放因子" } }, @@ -2547,6 +3061,11 @@ "s2": { "name": "s2" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "FreeU_V2": { @@ -2567,6 +3086,11 @@ "s2": { "name": "s2" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "GITSScheduler": { @@ -2600,7 +3124,7 @@ "display_name": "GLIGEN文本框应用", "inputs": { "clip": { - "name": "CLIPCLIP" + "name": "CLIP" }, "conditioning_to": { "name": "条件到" @@ -2625,9 +3149,61 @@ } } }, + "GeminiImage2Node": { + "description": "通过Google API编辑图像。", + "display_name": "Nano Banana Pro(Google Gemini 图像)", + "inputs": { + "aspect_ratio": { + "name": "宽高比", + "tooltip": "自动:会自动匹配输入图像的宽高比,或如果没有提供图像,则生成1:1的正方形。" + }, + "control_after_generate": { + "name": "生成后控制" + }, + "files": { + "name": "文件", + "tooltip": "模型上下文文件。接受来自 Gemini输入文件 节点的输入。" + }, + "images": { + "name": "图像", + "tooltip": "参考图像。如果要使用多个参考图,使用 图像批次 节点(最多14张)。" + }, + "model": { + "name": "模型" + }, + "prompt": { + "name": "提示词", + "tooltip": "使用文本描述要生成的图像或要应用的编辑的内容。包括模型应该遵循的任何约束、样式或细节。" + }, + "resolution": { + "name": "分辨率", + "tooltip": "目标分辨率。2K/4K 会使用 Gemini 放大生成。" + }, + "response_modalities": { + "name": "响应模态", + "tooltip": "选择 图像 仅生成图像,选择 图像+文本 会生成图像和文本。" + }, + "seed": { + "name": "随机种", + "tooltip": "为特定值时,模型将尽可能提供相同的结果。但不保证确定一致的输出。此外,即使使用相同的种子值,更改模型或参数设置(例如温度)也会导致结果变化。默认使用随机值。" + }, + "system_prompt": { + "name": "系统提示词", + "tooltip": "决定 AI 行为的基本指令。" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "tooltip": null + } + } + }, "GeminiImageNode": { "description": "通过Google API同步编辑图像。", - "display_name": "Google Gemini 图像", + "display_name": "Nano Banana (Google Gemini 图像)", "inputs": { "aspect_ratio": { "name": "宽高比", @@ -2652,9 +3228,17 @@ "name": "提示词", "tooltip": "用于生成的文本提示词" }, + "response_modalities": { + "name": "回复模态", + "tooltip": "选择“图像”仅输出图像,选择“图像+文本”输出图像和文本。" + }, "seed": { "name": "种子", "tooltip": "当种子固定为特定值时,模型会尽力为重复请求提供相同的响应。不能保证确定性输出。此外,更改模型或参数设置(如温度)即使使用相同的种子值也可能导致响应变化。默认使用随机种子值。" + }, + "system_prompt": { + "name": "系统提示词", + "tooltip": "决定 AI 行为的基本指令。" } }, "outputs": { @@ -2671,7 +3255,7 @@ "display_name": "Gemini 输入文件", "inputs": { "GEMINI_INPUT_FILES": { - "name": "GEMINI_INPUT_FILES", + "name": "Gemini 输入文件", "tooltip": "与此节点加载的文件批量组合的可选附加文件。允许链式连接输入文件,以便单个消息可包含多个输入文件。" }, "file": { @@ -2716,6 +3300,10 @@ "name": "种子", "tooltip": "当种子固定为特定值时,模型会尽力为重复请求提供相同的响应。不保证确定性输出。此外,更改模型或参数设置(如温度)即使使用相同的种子值也可能导致响应变化。默认使用随机种子值。" }, + "system_prompt": { + "name": "系统提示词", + "tooltip": "决定 AI 行为的基本指令。" + }, "video": { "name": "视频", "tooltip": "用作模型上下文的可选视频。" @@ -2727,6 +3315,72 @@ } } }, + "GenerateTracks": { + "display_name": "生成轨道", + "inputs": { + "bezier": { + "name": "贝塞尔曲线", + "tooltip": "贝塞尔曲线路径,使用中点作为控制点。" + }, + "end_x": { + "name": "结束点X", + "tooltip": "结束点的规格化X坐标(0-1)。" + }, + "end_y": { + "name": "结束点Y", + "tooltip": "结束点的规格化Y坐标(0-1)。" + }, + "height": { + "name": "高度" + }, + "interpolation": { + "name": "插值", + "tooltip": "控制轨迹沿着路径移动的时间/速度。" + }, + "mid_x": { + "name": "中间点Y", + "tooltip": "中间点的规格化Y坐标(0-1)。" + }, + "mid_y": { + "name": "中间点Y", + "tooltip": "中间点的规格化Y坐标(0-1)。" + }, + "num_frames": { + "name": "帧数" + }, + "num_tracks": { + "name": "轨迹数" + }, + "start_x": { + "name": "起始点X", + "tooltip": "起始点的规格化X坐标(0-1)。" + }, + "start_y": { + "name": "起始点Y", + "tooltip": "起始点的规格化Y坐标(0-1)。" + }, + "track_mask": { + "name": "轨道遮罩", + "tooltip": "遮罩,用于标记可见帧" + }, + "track_spread": { + "name": "轨道分布", + "tooltip": "轨道间的规格化距离。垂直于轨迹运动方向展开。" + }, + "width": { + "name": "宽度" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "轨迹长度", + "tooltip": null + } + } + }, "GetImageSize": { "description": "返回图像的宽度和高度,并原样传递图像。", "display_name": "获取图像尺寸", @@ -2737,23 +3391,26 @@ }, "outputs": { "0": { - "name": "宽度" + "name": "宽度", + "tooltip": null }, "1": { - "name": "高度" + "name": "高度", + "tooltip": null }, "2": { - "name": "批处理大小" + "name": "批处理大小", + "tooltip": null } } }, "GetVideoComponents": { - "description": "提取视频中的所有组件:帧、音频和帧率。", - "display_name": "获取视频组件", + "description": "提取视频中的所有元素:帧、音频和帧率。", + "display_name": "获取视频元素", "inputs": { "video": { "name": "视频", - "tooltip": "要提取组件的视频。" + "tooltip": "要提取元素的视频。" } }, "outputs": { @@ -2783,10 +3440,15 @@ "tapered_corners": { "name": "倒角" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Hunyuan3Dv2Conditioning": { - "display_name": "Hunyuan3Dv2Conditioning", + "display_name": "Hunyuan3Dv2条件", "inputs": { "clip_vision_output": { "name": "clip视觉输出" @@ -2794,15 +3456,17 @@ }, "outputs": { "0": { - "name": "正向" + "name": "正向", + "tooltip": null }, "1": { - "name": "反向" + "name": "反向", + "tooltip": null } } }, "Hunyuan3Dv2ConditioningMultiView": { - "display_name": "Hunyuan3Dv2ConditioningMultiView", + "display_name": "Hunyuan3Dv2条件多视角", "inputs": { "back": { "name": "后" @@ -2819,15 +3483,17 @@ }, "outputs": { "0": { - "name": "正向" + "name": "正向", + "tooltip": null }, "1": { - "name": "反向" + "name": "反向", + "tooltip": null } } }, "HunyuanImageToVideo": { - "display_name": "Hunyuan图像到视频", + "display_name": "图像到视频(Hunyuan)", "inputs": { "batch_size": { "name": "批量大小" @@ -2860,7 +3526,7 @@ "tooltip": null }, "1": { - "name": "潜在空间", + "name": "Latent", "tooltip": null } } @@ -2869,7 +3535,7 @@ "display_name": "HunyuanRefinerLatent", "inputs": { "latent": { - "name": "潜在" + "name": "Latent" }, "negative": { "name": "负面" @@ -2896,6 +3562,120 @@ } } }, + "HunyuanVideo15ImageToVideo": { + "display_name": "图像到视频(Hunyuan Video 15 )", + "inputs": { + "batch_size": { + "name": "批次大小" + }, + "clip_vision_output": { + "name": "CLIP视觉输出" + }, + "height": { + "name": "高度" + }, + "length": { + "name": "帧数" + }, + "negative": { + "name": "负面条件" + }, + "positive": { + "name": "正面条件" + }, + "start_image": { + "name": "图像" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "宽度" + } + }, + "outputs": { + "0": { + "name": "正面条件", + "tooltip": null + }, + "1": { + "name": "负面条件", + "tooltip": null + }, + "2": { + "name": "latent", + "tooltip": null + } + } + }, + "HunyuanVideo15LatentUpscaleWithModel": { + "display_name": "Hunyuan Video 15 Latent 使用模型放大", + "inputs": { + "crop": { + "name": "裁剪" + }, + "height": { + "name": "高度" + }, + "model": { + "name": "模型" + }, + "samples": { + "name": "Latent" + }, + "upscale_method": { + "name": "放大方法" + }, + "width": { + "name": "宽度" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "HunyuanVideo15SuperResolution": { + "display_name": "Hunyuan Video 15超分辨率", + "inputs": { + "clip_vision_output": { + "name": "CLIP视觉输出" + }, + "latent": { + "name": "latent" + }, + "negative": { + "name": "负面条件" + }, + "noise_augmentation": { + "name": "噪波增强" + }, + "positive": { + "name": "正面条件" + }, + "start_image": { + "name": "图像" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "正面条件", + "tooltip": null + }, + "1": { + "name": "负面条件", + "tooltip": null + }, + "2": { + "name": "Latent", + "tooltip": null + } + } + }, "HyperTile": { "display_name": "超分块HyperTile", "inputs": { @@ -2945,29 +3725,29 @@ "display_name": "Ideogram V1", "inputs": { "aspect_ratio": { - "name": "aspect_ratio", + "name": "宽高比", "tooltip": "图像生成的宽高比。" }, "control_after_generate": { - "name": "control after generate" + "name": "生成后控制" }, "magic_prompt_option": { - "name": "magic_prompt_option", + "name": "Magic Prompt", "tooltip": "确定生成时是否使用 MagicPrompt" }, "negative_prompt": { - "name": "negative_prompt", + "name": "负面提示词", "tooltip": "描述需要从图像中排除的内容" }, "num_images": { - "name": "num_images" + "name": "图像数量" }, "prompt": { - "name": "prompt", + "name": "提示词", "tooltip": "用于图像生成的提示词" }, "seed": { - "name": "seed" + "name": "随机种" }, "turbo": { "name": "turbo", @@ -2985,36 +3765,36 @@ "display_name": "Ideogram V2", "inputs": { "aspect_ratio": { - "name": "aspect_ratio", + "name": "宽高比", "tooltip": "图像生成的宽高比。如果分辨率未设置为 AUTO,则此项无效。" }, "control_after_generate": { - "name": "control after generate" + "name": "生成后控制" }, "magic_prompt_option": { - "name": "magic_prompt_option", + "name": "Magic Prompt", "tooltip": "确定生成时是否使用 MagicPrompt" }, "negative_prompt": { - "name": "negative_prompt", + "name": "负面提示词", "tooltip": "描述图像中需要排除的内容" }, "num_images": { - "name": "num_images" + "name": "图像数量" }, "prompt": { - "name": "prompt", + "name": "提示词", "tooltip": "用于图像生成的提示词" }, "resolution": { - "name": "resolution", + "name": "分辨率", "tooltip": "图像生成的分辨率。如果未设置为 AUTO,则会覆盖 aspect_ratio 设置。" }, "seed": { - "name": "seed" + "name": "随机种" }, "style_type": { - "name": "style_type", + "name": "风格", "tooltip": "生成的风格类型(仅限 V2)" }, "turbo": { @@ -3033,7 +3813,7 @@ "display_name": "Ideogram V3", "inputs": { "aspect_ratio": { - "name": "aspect_ratio", + "name": "宽高比", "tooltip": "图像生成的宽高比。如果分辨率未设置为自动,则忽略此项。" }, "character_image": { @@ -3045,37 +3825,37 @@ "tooltip": "角色参考图像的可选遮罩。" }, "control_after_generate": { - "name": "control after generate" + "name": "生成后控制" }, "image": { - "name": "image", + "name": "图像", "tooltip": "用于图像编辑的可选参考图片。" }, "magic_prompt_option": { - "name": "magic_prompt_option", + "name": "Magic Prompt", "tooltip": "决定生成时是否使用 MagicPrompt" }, "mask": { - "name": "mask", + "name": "遮罩", "tooltip": "用于修复的可选 mask(白色区域将被替换)" }, "num_images": { - "name": "num_images" + "name": "图像数量" }, "prompt": { - "name": "prompt", + "name": "提示词", "tooltip": "用于图像生成或编辑的提示词" }, "rendering_speed": { - "name": "rendering_speed", + "name": "生成速度", "tooltip": "控制生成速度与质量之间的权衡" }, "resolution": { - "name": "resolution", + "name": "分辨率", "tooltip": "图像生成的分辨率。如果未设置为自动,则覆盖 aspect_ratio 设置。" }, "seed": { - "name": "seed" + "name": "随机种" } }, "outputs": { @@ -3100,6 +3880,11 @@ "strength": { "name": "强度" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageBatch": { @@ -3163,6 +3948,26 @@ "image": { "name": "图像" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageCompare": { + "description": "通过滑块并排比较两张图像。", + "display_name": "图像对比", + "inputs": { + "compare_view": { + "name": "compare_view" + }, + "image_a": { + "name": "image_a" + }, + "image_b": { + "name": "image_b" + } } }, "ImageCompositeMasked": { @@ -3186,6 +3991,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageCrop": { @@ -3206,6 +4016,30 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageDeduplication": { + "display_name": "图像去重", + "inputs": { + "images": { + "name": "图像", + "tooltip": "需要处理的图像序列。" + }, + "similarity_threshold": { + "name": "相似度阈值", + "tooltip": "相似阈值(0-1)。越高表示越相似。超过该值的图像则认定为重复图像。" + } + }, + "outputs": { + "0": { + "name": "图像", + "tooltip": "处理后的图像" + } } }, "ImageFlip": { @@ -3217,6 +4051,11 @@ "image": { "name": "图像" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageFromBatch": { @@ -3231,6 +4070,42 @@ "length": { "name": "长度" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ImageGrid": { + "display_name": "图像表格", + "inputs": { + "cell_height": { + "name": "单元格高度", + "tooltip": "表格中每个单元格的高度。" + }, + "cell_width": { + "name": "单元格宽度", + "tooltip": "表格中每个单元格的宽度。" + }, + "columns": { + "name": "列数", + "tooltip": "表格的列数。" + }, + "images": { + "name": "图像", + "tooltip": "需要处理的图像序列。" + }, + "padding": { + "name": "间距", + "tooltip": "图像间距" + } + }, + "outputs": { + "0": { + "name": "图像", + "tooltip": "处理后的图像" + } } }, "ImageInvert": { @@ -3309,7 +4184,7 @@ } }, "ImageRGBToYUV": { - "display_name": "ImageRGBToYUV", + "display_name": "图像RGB到YUV", "inputs": { "image": { "name": "图像" @@ -3339,6 +4214,11 @@ "rotation": { "name": "旋转" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageScale": { @@ -3387,6 +4267,11 @@ "upscale_method": { "name": "放大方法" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageScaleToTotalPixels": { @@ -3398,6 +4283,9 @@ "megapixels": { "name": "像素数量" }, + "resolution_steps": { + "name": "分辨率步数" + }, "upscale_method": { "name": "缩放算法" } @@ -3452,6 +4340,11 @@ "spacing_width": { "name": "间距宽度" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageToMask": { @@ -3463,6 +4356,11 @@ "image": { "name": "图像" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "ImageUpscaleWithModel": { @@ -3482,7 +4380,7 @@ } }, "ImageYUVToRGB": { - "display_name": "ImageYUVToRGB", + "display_name": "图像YUV到RGB", "inputs": { "U": { "name": "U" @@ -3572,6 +4470,29 @@ "mask": { "name": "遮罩" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "JoinAudioChannels": { + "description": "将左声道和右声道的单声道音频合成为立体声音频。", + "display_name": "音频通道合并", + "inputs": { + "audio_left": { + "name": "audio_left" + }, + "audio_right": { + "name": "audio_right" + } + }, + "outputs": { + "0": { + "name": "audio", + "tooltip": null + } } }, "JoinImageWithAlpha": { @@ -3697,6 +4618,58 @@ "sampler_name": { "name": "采样器名称" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Kandinsky5ImageToVideo": { + "display_name": "图像到视频(Kandinsky5)", + "inputs": { + "batch_size": { + "name": "批次大小" + }, + "height": { + "name": "高度 " + }, + "length": { + "name": "时长" + }, + "negative": { + "name": "负面条件" + }, + "positive": { + "name": "正面条件" + }, + "start_image": { + "name": "初始图象" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "宽度" + } + }, + "outputs": { + "0": { + "name": "正面条件", + "tooltip": null + }, + "1": { + "name": "负面条件", + "tooltip": null + }, + "2": { + "name": "Latent", + "tooltip": "空视频Latent" + }, + "3": { + "name": "条件Latent", + "tooltip": "纯净编码的 Latent,用于替代纯噪波 Latent 输出。" + } } }, "KarrasScheduler": { @@ -3706,14 +4679,19 @@ "name": "rho" }, "sigma_max": { - "name": "sigma_max" + "name": "最大Sigma" }, "sigma_min": { - "name": "sigma_min" + "name": "最小Sigma" }, "steps": { "name": "步数" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "KlingCameraControlI2VNode": { @@ -3721,25 +4699,25 @@ "display_name": "Kling 图像转视频(摄像机控制)", "inputs": { "aspect_ratio": { - "name": "aspect_ratio" + "name": "宽高比" }, "camera_control": { - "name": "camera_control", + "name": "镜头控制", "tooltip": "可通过 Kling Camera Controls 节点创建。控制视频生成过程中的摄像机运动和动作。" }, "cfg_scale": { - "name": "cfg_scale" + "name": "CFG" }, "negative_prompt": { - "name": "negative_prompt", + "name": "负面提示词", "tooltip": "反向文本提示" }, "prompt": { - "name": "prompt", + "name": "提示词", "tooltip": "正向文本提示" }, "start_frame": { - "name": "start_frame", + "name": "起始帧", "tooltip": "参考图像 - URL 或 Base64 编码字符串,不能超过 10MB,分辨率不低于 300*300 像素,宽高比在 1:2.5 ~ 2.5:1 之间。Base64 不应包含 data:image 前缀。" } }, @@ -3748,11 +4726,11 @@ "tooltip": null }, "1": { - "name": "video_id", + "name": "视频ID", "tooltip": null }, "2": { - "name": "duration", + "name": "时长", "tooltip": null } } @@ -3762,21 +4740,21 @@ "display_name": "Kling 文本转视频(摄像机控制)", "inputs": { "aspect_ratio": { - "name": "aspect_ratio" + "name": "宽高比" }, "camera_control": { - "name": "camera_control", + "name": "镜头控制", "tooltip": "可通过 Kling Camera Controls 节点创建。控制视频生成过程中的摄像机运动和移动。" }, "cfg_scale": { - "name": "cfg_scale" + "name": "CFG" }, "negative_prompt": { - "name": "negative_prompt", + "name": "负面提示词", "tooltip": "反向文本提示" }, "prompt": { - "name": "prompt", + "name": "提示词", "tooltip": "正向文本提示" } }, @@ -3785,11 +4763,11 @@ "tooltip": null }, "1": { - "name": "video_id", + "name": "视频ID", "tooltip": null }, "2": { - "name": "duration", + "name": "时长", "tooltip": null } } @@ -3799,63 +4777,63 @@ "display_name": "Kling 相机控制", "inputs": { "camera_control_type": { - "name": "camera_control_type" + "name": "镜头类型" }, "horizontal_movement": { - "name": "horizontal_movement", + "name": "水平移动", "tooltip": "控制相机在水平轴(x 轴)上的移动。负值表示向左,正值表示向右。" }, "pan": { - "name": "pan", + "name": "垂直旋转", "tooltip": "控制相机在垂直平面(x 轴)上的旋转。负值表示向下旋转,正值表示向上旋转。" }, "roll": { - "name": "roll", + "name": "滚转", "tooltip": "控制相机的滚转量(z 轴)。负值表示逆时针,正值表示顺时针。" }, "tilt": { - "name": "tilt", + "name": "水平旋转", "tooltip": "控制相机在水平平面(y 轴)上的旋转。负值表示向左旋转,正值表示向右旋转。" }, "vertical_movement": { - "name": "vertical_movement", + "name": "垂直移动", "tooltip": "控制相机在垂直轴(y 轴)上的移动。负值表示向下,正值表示向上。" }, "zoom": { - "name": "zoom", + "name": "变焦", "tooltip": "控制相机焦距的变化。负值表示视野变窄,正值表示视野变宽。" } }, "outputs": { "0": { - "name": "camera_control", + "name": "镜头控制", "tooltip": null } } }, "KlingDualCharacterVideoEffectNode": { - "description": "根据 effect_scene 在生成视频时实现不同的特效。第一张图片将被放置在合成画面的左侧,第二张图片在右侧。", + "description": "根据 效果 在生成视频时实现不同的特效。第一张图片将被放置在合成画面的左侧,第二张图片在右侧。", "display_name": "Kling 双角色视频特效", "inputs": { "duration": { - "name": "duration" + "name": "时长" }, "effect_scene": { - "name": "effect_scene" + "name": "效果" }, "image_left": { - "name": "image_left", + "name": "左侧图像", "tooltip": "左侧图片" }, "image_right": { - "name": "image_right", + "name": "右侧图像", "tooltip": "右侧图片" }, "mode": { - "name": "mode" + "name": "模式" }, "model_name": { - "name": "model_name" + "name": "模型" } }, "outputs": { @@ -3863,40 +4841,39 @@ "tooltip": null }, "1": { - "name": "duration", + "name": "时长", "tooltip": null } } }, "KlingImage2VideoNode": { - "description": "Kling 图像转视频节点", "display_name": "Kling 图像转视频", "inputs": { "aspect_ratio": { - "name": "aspect_ratio" + "name": "宽高比" }, "cfg_scale": { - "name": "cfg_scale" + "name": "CFG" }, "duration": { - "name": "duration" + "name": "时长" }, "mode": { - "name": "mode" + "name": "模式" }, "model_name": { - "name": "model_name" + "name": "模型" }, "negative_prompt": { - "name": "negative_prompt", + "name": "负面提示词", "tooltip": "反向文本提示" }, "prompt": { - "name": "prompt", + "name": "提示词", "tooltip": "正向文本提示" }, "start_frame": { - "name": "start_frame", + "name": "起始帧", "tooltip": "参考图像 - URL 或 Base64 编码字符串,不能超过 10MB,分辨率不少于 300*300 像素,宽高比在 1:2.5 ~ 2.5:1 之间。Base64 不应包含 data:image 前缀。" } }, @@ -3905,11 +4882,11 @@ "tooltip": null }, "1": { - "name": "video_id", + "name": "视频ID", "tooltip": null }, "2": { - "name": "duration", + "name": "时长", "tooltip": null } } @@ -3919,35 +4896,35 @@ "display_name": "Kling 图像生成", "inputs": { "aspect_ratio": { - "name": "aspect_ratio" + "name": "宽高比" }, "human_fidelity": { - "name": "human_fidelity", + "name": "主体参考强度", "tooltip": "主体参考相似度" }, "image": { - "name": "image" + "name": "图像" }, "image_fidelity": { - "name": "image_fidelity", + "name": "图像参考强度", "tooltip": "用户上传图像的参考强度" }, "image_type": { - "name": "image_type" + "name": "图像类型" }, "model_name": { - "name": "model_name" + "name": "模型" }, "n": { - "name": "n", + "name": "图像数量", "tooltip": "生成图像数量" }, "negative_prompt": { - "name": "negative_prompt", + "name": "负面提示词", "tooltip": "反向文本提示" }, "prompt": { - "name": "prompt", + "name": "提示词", "tooltip": "正向文本提示" } }, @@ -3957,6 +4934,35 @@ } } }, + "KlingImageToVideoWithAudio": { + "display_name": "Kling 图像到视频音频", + "inputs": { + "duration": { + "name": "时长(秒)" + }, + "generate_audio": { + "name": "生成音频" + }, + "mode": { + "name": "模式" + }, + "model_name": { + "name": "模型" + }, + "prompt": { + "name": "提示词", + "tooltip": "正面文本提示词。" + }, + "start_frame": { + "name": "起始帧" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingLipSyncAudioToVideoNode": { "description": "Kling 音频驱动视频口型同步节点。将视频文件中的嘴部动作与音频文件的音频内容进行同步。", "display_name": "Kling 音频驱动视频口型同步", @@ -4018,22 +5024,243 @@ } } }, + "KlingMotionControl": { + "display_name": "Kling 动作控制", + "inputs": { + "character_orientation": { + "name": "参考方向", + "tooltip": "控制角色的朝向/方向。\nv视频:动作,表情,相机移动和方向遵循运动参考视频(其他细节通过提示)。\n图像:运动和表情仍然遵循运动参考视频,但角色方向匹配参考图像(通过提示相机/其他细节)" + }, + "keep_original_sound": { + "name": "保留原音频" + }, + "mode": { + "name": "模式" + }, + "prompt": { + "name": "提示词" + }, + "reference_image": { + "name": "参考图像" + }, + "reference_video": { + "name": "参考视频", + "tooltip": "动作或表情的参考视频。\n持续时间取决于参考类型:\n图像:3-10 秒\n视频:3-30 秒" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProEditVideoNode": { + "description": "使用 Kling 最新模型编辑视频。", + "display_name": "Kling Omni 编辑视频 (Pro)", + "inputs": { + "keep_original_sound": { + "name": "保留原音频" + }, + "model_name": { + "name": "模型" + }, + "prompt": { + "name": "提示词", + "tooltip": "描述视频内容的文本提示词。可以同时包括正面负面描述。" + }, + "reference_images": { + "name": "参考图像", + "tooltip": "最多 4 个参考图像" + }, + "resolution": { + "name": "分辨率" + }, + "video": { + "name": "视频", + "tooltip": "需要编辑的视频,输出视频的时长和输入视频相同。" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProFirstLastFrameNode": { + "description": "使用 Kling 最新模型和起始帧、可选的结束帧或参考图像。", + "display_name": "Kling Omni 首尾帧到视频 (Pro)", + "inputs": { + "duration": { + "name": "时长" + }, + "end_frame": { + "name": "结束帧", + "tooltip": "可选的视频结束帧。不能和“参考图像”同时使用。" + }, + "first_frame": { + "name": "起始帧" + }, + "model_name": { + "name": "模型" + }, + "prompt": { + "name": "提示词", + "tooltip": "描述视频内容的文本提示词。可以同时包括正面负面描述。" + }, + "reference_images": { + "name": "参考图像", + "tooltip": "最多 6 个参考图像" + }, + "resolution": { + "name": "分辨率" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageNode": { + "description": "使用 Kling 最新模型生成或编辑图像。", + "display_name": "Kling Omni 图像 (Pro)", + "inputs": { + "aspect_ratio": { + "name": "宽高比" + }, + "model_name": { + "name": "模型" + }, + "prompt": { + "name": "提示词", + "tooltip": "描述图像内容的文本提示词。可以同时包括正面负面描述。" + }, + "reference_images": { + "name": "参考图像", + "tooltip": "最多 10 个参考图像" + }, + "resolution": { + "name": "分辨率" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProImageToVideoNode": { + "description": "使用 Kling 最新模型基于最多 7 个参考图生成视频。", + "display_name": "Kling Omni 图像到视频 (Pro)", + "inputs": { + "aspect_ratio": { + "name": "宽高比" + }, + "duration": { + "name": "时长" + }, + "model_name": { + "name": "模型" + }, + "prompt": { + "name": "提示词", + "tooltip": "描述图像内容的文本提示词。可以同时包括正面负面描述。" + }, + "reference_images": { + "name": "参考图像", + "tooltip": "最多 7 个参考图像" + }, + "resolution": { + "name": "分辨率" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProTextToVideoNode": { + "description": "使用 Kling 最新模型和提示词生成视频。", + "display_name": "Kling Omni 文本到视频 (Pro)", + "inputs": { + "aspect_ratio": { + "name": "宽高比" + }, + "duration": { + "name": "时长" + }, + "model_name": { + "name": "模型" + }, + "prompt": { + "name": "提示词", + "tooltip": "描述图像内容的文本提示词。可以同时包括正面负面描述。" + }, + "resolution": { + "name": "分辨率" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "KlingOmniProVideoToVideoNode": { + "description": "使用 Kling 最新模型基于最多 4 个参考图编辑视频。", + "display_name": "Kling Omni 视频到视频 (Pro)", + "inputs": { + "aspect_ratio": { + "name": "宽高比" + }, + "duration": { + "name": "时长" + }, + "keep_original_sound": { + "name": "保留原音频" + }, + "model_name": { + "name": "模型" + }, + "prompt": { + "name": "提示词", + "tooltip": "描述图像内容的文本提示词。可以同时包括正面负面描述。" + }, + "reference_images": { + "name": "参考图像", + "tooltip": "最多 4 个参考图像" + }, + "reference_video": { + "name": "参考视频", + "tooltip": "用于参考的视频。" + }, + "resolution": { + "name": "分辨率" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "KlingSingleImageVideoEffectNode": { - "description": "根据 effect_scene 在生成视频时实现不同的特效。", + "description": "根据 效果 在生成视频时实现不同的特效。", "display_name": "Kling 视频特效", "inputs": { "duration": { - "name": "duration" + "name": "时长" }, "effect_scene": { - "name": "effect_scene" + "name": "效果" }, "image": { - "name": "image", + "name": "图像3", "tooltip": "参考图片。URL 或 Base64 编码字符串(不含 data:image 前缀)。文件大小不得超过 10MB,分辨率不低于 300*300 像素,宽高比在 1:2.5 ~ 2.5:1 之间。" }, "model_name": { - "name": "model_name" + "name": "模型" } }, "outputs": { @@ -4041,11 +5268,11 @@ "tooltip": null }, "1": { - "name": "video_id", + "name": "视频ID", "tooltip": null }, "2": { - "name": "duration", + "name": "时长", "tooltip": null } } @@ -4055,29 +5282,29 @@ "display_name": "Kling 起止帧生成视频", "inputs": { "aspect_ratio": { - "name": "aspect_ratio" + "name": "宽高比" }, "cfg_scale": { - "name": "cfg_scale" + "name": "CFG" }, "end_frame": { "name": "end_frame", "tooltip": "参考图像 - 结束帧控制。URL 或 Base64 编码字符串,不能超过 10MB,分辨率不低于 300*300 像素。Base64 不应包含 data:image 前缀。" }, "mode": { - "name": "mode", + "name": "模式", "tooltip": "用于视频生成的配置,格式为:mode / duration / model_name。" }, "negative_prompt": { - "name": "negative_prompt", + "name": "负面提示词", "tooltip": "反向文本提示" }, "prompt": { - "name": "prompt", + "name": "提示词", "tooltip": "正向文本提示" }, "start_frame": { - "name": "start_frame", + "name": "起始帧", "tooltip": "参考图像 - URL 或 Base64 编码字符串,不能超过 10MB,分辨率不低于 300*300 像素,宽高比在 1:2.5 ~ 2.5:1 之间。Base64 不应包含 data:image 前缀。" } }, @@ -4086,11 +5313,11 @@ "tooltip": null }, "1": { - "name": "video_id", + "name": "视频ID", "tooltip": null }, "2": { - "name": "duration", + "name": "时长", "tooltip": null } } @@ -4100,21 +5327,21 @@ "display_name": "Kling 文本转视频", "inputs": { "aspect_ratio": { - "name": "aspect_ratio" + "name": "宽高比" }, "cfg_scale": { - "name": "cfg_scale" + "name": "CFG" }, "mode": { - "name": "mode", + "name": "模式", "tooltip": "用于视频生成的配置,格式为:mode / duration / model_name。" }, "negative_prompt": { - "name": "negative_prompt", + "name": "负面提示词", "tooltip": "反向文本提示" }, "prompt": { - "name": "prompt", + "name": "提示词", "tooltip": "正向文本提示" } }, @@ -4123,11 +5350,40 @@ "tooltip": null }, "1": { - "name": "video_id", + "name": "视频ID", "tooltip": null }, "2": { - "name": "duration", + "name": "时长", + "tooltip": null + } + } + }, + "KlingTextToVideoWithAudio": { + "display_name": "Kling 文本到视频音频", + "inputs": { + "aspect_ratio": { + "name": "宽高比" + }, + "duration": { + "name": "时长" + }, + "generate_audio": { + "name": "生成音频" + }, + "mode": { + "name": "模式" + }, + "model_name": { + "name": "模型" + }, + "prompt": { + "name": "提示词", + "tooltip": "正面文本提示词。" + } + }, + "outputs": { + "0": { "tooltip": null } } @@ -4137,18 +5393,18 @@ "display_name": "Kling 视频扩展", "inputs": { "cfg_scale": { - "name": "cfg_scale" + "name": "CFG" }, "negative_prompt": { - "name": "negative_prompt", + "name": "负面提示词", "tooltip": "用于在扩展视频中避免出现的元素的负向文本提示" }, "prompt": { - "name": "prompt", + "name": "提示词", "tooltip": "用于引导视频扩展的正向文本提示" }, "video_id": { - "name": "video_id", + "name": "视频ID", "tooltip": "要扩展的视频 ID。支持由文本转视频、图像转视频以及之前的视频扩展操作生成的视频。扩展后总时长不能超过 3 分钟。" } }, @@ -4157,11 +5413,11 @@ "tooltip": null }, "1": { - "name": "video_id", + "name": "视频ID", "tooltip": null }, "2": { - "name": "duration", + "name": "时长", "tooltip": null } } @@ -4171,13 +5427,33 @@ "display_name": "Kling 虚拟试穿", "inputs": { "cloth_image": { - "name": "cloth_image" + "name": "服装" }, "human_image": { - "name": "human_image" + "name": "主体" }, "model_name": { - "name": "model_name" + "name": "模型" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "LTXAVTextEncoderLoader": { + "description": "[Recipes]\n\nltxav: gemma 3 12B", + "display_name": "LTXV音频文本编码器加载器", + "inputs": { + "ckpt_name": { + "name": "ckpt名称" + }, + "device": { + "name": "设备" + }, + "text_encoder": { + "name": "文本编码器" } }, "outputs": { @@ -4228,6 +5504,76 @@ } } }, + "LTXVAudioVAEDecode": { + "display_name": "LTXV音频VAE解码", + "inputs": { + "audio_vae": { + "name": "音频VAE", + "tooltip": "用于解码latent的音频VAE模型。" + }, + "samples": { + "name": "样本", + "tooltip": "要解码的latent。" + } + }, + "outputs": { + "0": { + "name": "音频", + "tooltip": null + } + } + }, + "LTXVAudioVAEEncode": { + "display_name": "LTXV音频VAE编码", + "inputs": { + "audio": { + "name": "音频", + "tooltip": "要编码的音频。" + }, + "audio_vae": { + "name": "音频VAE", + "tooltip": "用于编码的音频VAE模型。" + } + }, + "outputs": { + "0": { + "name": "音频latent", + "tooltip": null + } + } + }, + "LTXVAudioVAELoader": { + "display_name": "LTXV音频VAE加载器", + "inputs": { + "ckpt_name": { + "name": "ckpt名称", + "tooltip": "要加载的音频VAE检查点。" + } + }, + "outputs": { + "0": { + "name": "音频VAE", + "tooltip": null + } + } + }, + "LTXVConcatAVLatent": { + "display_name": "LTXVConcatAVLatent", + "inputs": { + "audio_latent": { + "name": "audio_latent" + }, + "video_latent": { + "name": "video_latent" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, "LTXVConditioning": { "display_name": "LTXV条件", "inputs": { @@ -4280,8 +5626,35 @@ } } }, + "LTXVEmptyLatentAudio": { + "display_name": "LTXV 空音频潜空间", + "inputs": { + "audio_vae": { + "name": "音频VAE", + "tooltip": "用于获取配置信息的音频VAE模型。" + }, + "batch_size": { + "name": "批量大小", + "tooltip": "批次中潜音频样本的数量。" + }, + "frame_rate": { + "name": "帧率", + "tooltip": "每秒的帧数。" + }, + "frames_number": { + "name": "帧数", + "tooltip": "帧的数量。" + } + }, + "outputs": { + "0": { + "name": "潜空间", + "tooltip": null + } + } + }, "LTXVImgToVideo": { - "display_name": "LTXV图像到视频", + "display_name": "图像到视频(LTXV)", "inputs": { "batch_size": { "name": "批量大小" @@ -4326,6 +5699,47 @@ } } }, + "LTXVImgToVideoInplace": { + "display_name": "LTXV图像转视频(原地)", + "inputs": { + "bypass": { + "name": "跳过", + "tooltip": "跳过条件处理。" + }, + "image": { + "name": "图像" + }, + "latent": { + "name": "潜空间" + }, + "strength": { + "name": "强度" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "潜空间", + "tooltip": null + } + } + }, + "LTXVLatentUpsampler": { + "display_name": "LTXV潜空间上采样器", + "inputs": { + "samples": { + "name": "样本" + }, + "upscale_model": { + "name": "放大模型" + }, + "vae": { + "name": "vae" + } + } + }, "LTXVPreprocess": { "display_name": "LTXV预处理", "inputs": { @@ -4374,6 +5788,25 @@ } } }, + "LTXVSeparateAVLatent": { + "description": "LTXV分离音视频潜空间", + "display_name": "LTXV分离音视频潜空间", + "inputs": { + "av_latent": { + "name": "音视频潜空间" + } + }, + "outputs": { + "0": { + "name": "video_latent", + "tooltip": null + }, + "1": { + "name": "audio_latent", + "tooltip": null + } + } + }, "LaplaceScheduler": { "display_name": "Laplace调度器", "inputs": { @@ -4392,6 +5825,11 @@ "steps": { "name": "步数" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LatentAdd": { @@ -4529,6 +5967,11 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LatentConcat": { @@ -4592,6 +6035,25 @@ } } }, + "LatentCutToBatch": { + "display_name": "Latent切割", + "inputs": { + "dim": { + "name": "维度" + }, + "samples": { + "name": "Latent" + }, + "slice_size": { + "name": "碎片大小" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LatentFlip": { "display_name": "翻转Latent", "inputs": { @@ -4745,6 +6207,19 @@ } } }, + "LatentUpscaleModelLoader": { + "display_name": "加载Latent放大模型", + "inputs": { + "model_name": { + "name": "模型" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "LazyCache": { "description": "EasyCache 的自制版本 - 更'简单'的 EasyCache 实现。总体效果不如 EasyCache,但在某些罕见情况下表现更好,并且与 ComfyUI 中的所有内容具有通用兼容性。", "display_name": "惰性缓存", @@ -4792,45 +6267,7 @@ }, "upload 3d model": { }, - "width": { - "name": "宽度" - } - }, - "outputs": { - "0": { - "name": "image" - }, - "1": { - "name": "mask" - }, - "2": { - "name": "mesh_path" - }, - "3": { - "name": "normal" - }, - "4": { - "name": "lineart" - }, - "5": { - "name": "camera_info" - }, - "6": { - "name": "录制视频" - } - } - }, - "Load3DAnimation": { - "display_name": "加载3D动画", - "inputs": { - "height": { - "name": "高度" - }, - "image": { - "name": "图像" - }, - "model_file": { - "name": "模型文件" + "upload extra resources": { }, "width": { "name": "宽度" @@ -4838,22 +6275,28 @@ }, "outputs": { "0": { - "name": "图像" + "name": "图像", + "tooltip": null }, "1": { - "name": "遮罩" + "name": "遮罩", + "tooltip": null }, "2": { - "name": "mesh_path" + "name": "网格路径", + "tooltip": null }, "3": { - "name": "法线" + "name": "法向", + "tooltip": null }, "4": { - "name": "相机信息" + "name": "线条", + "tooltip": null }, "5": { - "name": "录制视频" + "name": "相机信息", + "tooltip": null } } }, @@ -4869,6 +6312,11 @@ "upload": { "name": "选择文件上传" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "LoadImage": { @@ -4882,6 +6330,21 @@ } } }, + "LoadImageDataSetFromFolder": { + "display_name": "加载图像数据集", + "inputs": { + "folder": { + "name": "文件夹", + "tooltip": "图像数据集的文件夹" + } + }, + "outputs": { + "0": { + "name": "图像", + "tooltip": "已加载的图像序列" + } + } + }, "LoadImageMask": { "display_name": "加载图像(作为遮罩)", "inputs": { @@ -4900,6 +6363,8 @@ "description": "从输出文件夹加载图像。当点击刷新按钮时,节点将更新图像列表并自动选择第一张图像,便于轻松迭代。", "display_name": "加载图像(来自输出)", "inputs": { + "Auto-refresh after generation": { + }, "image": { "name": "图像" }, @@ -4910,41 +6375,22 @@ } } }, - "LoadImageSetFromFolderNode": { - "description": "从目录加载一批图像用于训练。", - "display_name": "从文件夹加载图像数据集", + "LoadImageTextDataSetFromFolder": { + "display_name": "加载图像和文本数据集", "inputs": { "folder": { "name": "文件夹", - "tooltip": "要从中加载图像的文件夹。" - }, - "resize_method": { - "name": "调整大小方法" + "tooltip": "图像数据集的文件夹" } - } - }, - "LoadImageTextSetFromFolderNode": { - "description": "从目录加载一批图像和标题用于训练。", - "display_name": "从文件夹加载图像和文本数据集", - "inputs": { - "clip": { - "name": "CLIP 模型", - "tooltip": "用于编码文本的 CLIP 模型。" + }, + "outputs": { + "0": { + "name": "图像", + "tooltip": "已加载的图像序列" }, - "folder": { - "name": "文件夹", - "tooltip": "要从中加载图像的文件夹。" - }, - "height": { - "name": "高度", - "tooltip": "调整图像的目标高度。-1 表示使用原始高度。" - }, - "resize_method": { - "name": "调整大小方法" - }, - "width": { - "name": "宽度", - "tooltip": "调整图像的目标宽度。-1 表示使用原始宽度。" + "1": { + "name": "文本", + "tooltip": "已加载的标注文本" } } }, @@ -4956,6 +6402,25 @@ } } }, + "LoadTrainingDataset": { + "display_name": "加载训练数据集", + "inputs": { + "folder_name": { + "name": "文件夹名", + "tooltip": "包含训练数据集的文件夹名(在 输出 文件夹内)。" + } + }, + "outputs": { + "0": { + "name": "Latent", + "tooltip": "Latent 列表" + }, + "1": { + "name": "条件", + "tooltip": "条件列表" + } + } + }, "LoadVideo": { "display_name": "加载视频", "inputs": { @@ -4977,7 +6442,7 @@ "display_name": "加载LoRA", "inputs": { "clip": { - "name": "CLIPCLIP", + "name": "CLIP", "tooltip": "LoRA 将应用于的 CLIP 模型。" }, "lora_name": { @@ -5027,7 +6492,6 @@ } }, "LoraModelLoader": { - "description": "从训练 LoRA 节点加载训练好的 LoRA 权重。", "display_name": "加载 LoRA 模型", "inputs": { "lora": { @@ -5045,6 +6509,7 @@ }, "outputs": { "0": { + "name": "模型", "tooltip": "修改后的扩散模型。" } } @@ -5075,19 +6540,20 @@ } }, "LossGraphNode": { - "description": "绘制损失图并将其保存到输出目录。", "display_name": "绘制损失图", "inputs": { "filename_prefix": { - "name": "文件名前缀" + "name": "文件名前缀", + "tooltip": "保存损失图的文件名前缀" }, "loss": { - "name": "损失" + "name": "损失", + "tooltip": "训练节点的损失图" } } }, "LotusConditioning": { - "display_name": "LotusConditioning", + "display_name": "Lotus条件", "outputs": { "0": { "name": "条件", @@ -5097,7 +6563,7 @@ }, "LtxvApiImageToVideo": { "description": "基于起始图像生成专业品质视频,可自定义时长和分辨率。", - "display_name": "LTXV 图像转视频", + "display_name": "LTXV 图像到视频", "inputs": { "duration": { "name": "时长" @@ -5131,7 +6597,7 @@ }, "LtxvApiTextToVideo": { "description": "可自定义时长和分辨率的专业品质视频。", - "display_name": "LTXV 文本转视频", + "display_name": "LTXV 文本到视频", "inputs": { "duration": { "name": "时长" @@ -5141,7 +6607,7 @@ }, "generate_audio": { "name": "生成音频", - "tooltip": "为 true 时,生成的视频将包含与场景匹配的 AI 生成音频。" + "tooltip": "生成的视频将包含与场景匹配的 AI 生成音频。" }, "model": { "name": "模型" @@ -5164,25 +6630,25 @@ "display_name": "Luma 概念", "inputs": { "concept1": { - "name": "concept1" + "name": "概念1" }, "concept2": { - "name": "concept2" + "name": "概念2" }, "concept3": { - "name": "concept3" + "name": "概念3" }, "concept4": { - "name": "concept4" + "name": "概念4" }, "luma_concepts": { - "name": "luma_concepts", + "name": "Luma概念", "tooltip": "可选的相机概念,将添加到此处选择的概念中。" } }, "outputs": { "0": { - "name": "luma_concepts", + "name": "Luma概念", "tooltip": null } } @@ -5192,24 +6658,24 @@ "display_name": "Luma 图像到图像", "inputs": { "control_after_generate": { - "name": "control after generate" + "name": "生成后控制" }, "image": { - "name": "image" + "name": "图像" }, "image_weight": { - "name": "image_weight", + "name": "图像权重", "tooltip": "图像权重;越接近 1.0,图像被修改的程度越小。" }, "model": { - "name": "model" + "name": "模型" }, "prompt": { - "name": "prompt", + "name": "提示词", "tooltip": "用于图像生成的提示词" }, "seed": { - "name": "seed", + "name": "随机种", "tooltip": "用于决定节点是否重新运行的种子;无论种子如何,实际结果都是非确定性的。" } }, @@ -5285,7 +6751,7 @@ "name": "循环" }, "luma_concepts": { - "name": "luma_concepts", + "name": "Luma概念", "tooltip": "可选的 Camera Concepts,通过 Luma Concepts 节点控制相机运动。" }, "model": { @@ -5318,7 +6784,7 @@ "tooltip": "用作参考的图片。" }, "luma_ref": { - "name": "luma_ref" + "name": "Luma参考" }, "weight": { "name": "权重", @@ -5327,7 +6793,7 @@ }, "outputs": { "0": { - "name": "luma_ref", + "name": "Luma参考", "tooltip": null } } @@ -5349,7 +6815,7 @@ "name": "循环" }, "luma_concepts": { - "name": "luma_concepts", + "name": "Luma概念", "tooltip": "可选的相机概念,通过 Luma Concepts 节点控制相机运动。" }, "model": { @@ -5388,6 +6854,50 @@ } } }, + "MakeTrainingDataset": { + "display_name": "制作训练数据集", + "inputs": { + "clip": { + "name": "CLIP", + "tooltip": "用于编码文本的 CLIP 模型" + }, + "images": { + "name": "图像", + "tooltip": "需要编码的图像序列。" + }, + "texts": { + "name": "文本", + "tooltip": "标注文本序列。数量可以是n(匹配图像),1(重复使用),或者省略(使用空字符串)。" + }, + "vae": { + "name": "VAE", + "tooltip": "用于编码图像的 VAE 模型" + } + }, + "outputs": { + "0": { + "name": "Latent", + "tooltip": "Latent 字典序列" + }, + "1": { + "name": "条件", + "tooltip": "条件字典序列" + } + } + }, + "ManualSigmas": { + "display_name": "自定义Sigmas", + "inputs": { + "sigmas": { + "name": "sigmas" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "MaskComposite": { "display_name": "合成遮罩", "inputs": { @@ -5406,14 +6916,19 @@ "y": { "name": "y" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "MaskPreview": { "description": "将输入图像保存到您的 ComfyUI 输出目录。", - "display_name": "MaskPreview", + "display_name": "预览遮罩", "inputs": { "mask": { - "name": "mask" + "name": "遮罩" } } }, @@ -5423,6 +6938,315 @@ "mask": { "name": "遮罩" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "MergeImageLists": { + "display_name": "融合图像序列", + "inputs": { + "images": { + "name": "图像", + "tooltip": "需要处理的图像序列。" + } + }, + "outputs": { + "0": { + "name": "图像", + "tooltip": "已处理的图像" + } + } + }, + "MergeTextLists": { + "display_name": "融合文本序列", + "inputs": { + "texts": { + "name": "文本", + "tooltip": "需要处理的文本序列" + } + }, + "outputs": { + "0": { + "name": "文本", + "tooltip": "已处理的文本" + } + } + }, + "MeshyAnimateModelNode": { + "description": "为已绑定骨骼的角色应用特定动画动作。", + "display_name": "Meshy:动画模型", + "inputs": { + "action_id": { + "name": "action_id", + "tooltip": "访问 https://docs.meshy.ai/en/api/animation-library 查看可用值列表。" + }, + "rig_task_id": { + "name": "rig_task_id" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + } + } + }, + "MeshyImageToModelNode": { + "display_name": "Meshy:图像转模型", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "image": { + "name": "image" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "指定生成模型的姿态模式。" + }, + "seed": { + "name": "seed", + "tooltip": "Seed 控制节点是否重新运行;无论 seed 如何,结果都是非确定性的。" + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "设置为 false 时,返回未处理的三角网格。" + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "should_texture": { + "name": "should_texture", + "tooltip": "决定是否生成纹理。设置为 false 跳过纹理阶段,返回无纹理的网格。" + }, + "should_texture_enable_pbr": { + "name": "enable_pbr" + }, + "should_texture_texture_prompt": { + "name": "texture_prompt" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyMultiImageToModelNode": { + "display_name": "Meshy:多图像转模型", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "images": { + "name": "images" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "指定生成模型的姿态模式。" + }, + "seed": { + "name": "seed", + "tooltip": "Seed 控制节点是否重新运行;无论 seed 如何,结果都是非确定性的。" + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "设置为 false 时,返回未处理的三角网格。" + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "should_texture": { + "name": "should_texture", + "tooltip": "决定是否生成纹理。设置为 false 跳过纹理阶段,返回无纹理的网格。" + }, + "should_texture_enable_pbr": { + "name": "enable_pbr" + }, + "should_texture_texture_prompt": { + "name": "texture_prompt" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRefineNode": { + "description": "对先前创建的草稿模型进行精细化处理。", + "display_name": "Meshy:精细化草稿模型", + "inputs": { + "enable_pbr": { + "name": "enable_pbr", + "tooltip": "在基础色之外生成PBR贴图(金属度、粗糙度、法线)。注意:使用雕塑风格时应设置为false,因为雕塑风格会生成其专属的PBR贴图。" + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "model": { + "name": "model" + }, + "texture_image": { + "name": "texture_image", + "tooltip": "‘texture_image’和‘texture_prompt’只能二选一。" + }, + "texture_prompt": { + "name": "texture_prompt", + "tooltip": "提供文本提示以引导材质生成。最多600个字符。不能与'texture_image'同时使用。" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyRigModelNode": { + "description": "以标准格式提供带骨骼的角色模型。自动骨骼绑定目前不适用于无贴图网格、非类人资产或肢体结构不清晰的类人资产。", + "display_name": "Meshy:骨骼绑定模型", + "inputs": { + "height_meters": { + "name": "height_meters", + "tooltip": "角色模型的大致高度(米),有助于缩放和骨骼绑定的准确性。" + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "texture_image": { + "name": "texture_image", + "tooltip": "模型的UV展开基础色贴图。" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "rig_task_id", + "tooltip": null + } + } + }, + "MeshyTextToModelNode": { + "display_name": "Meshy:文本生成模型", + "inputs": { + "control_after_generate": { + "name": "control after generate" + }, + "model": { + "name": "model" + }, + "pose_mode": { + "name": "pose_mode", + "tooltip": "指定生成模型的姿态模式。" + }, + "prompt": { + "name": "prompt" + }, + "seed": { + "name": "seed", + "tooltip": "Seed用于控制节点是否重新运行;无论seed如何,结果都是非确定性的。" + }, + "should_remesh": { + "name": "should_remesh", + "tooltip": "设置为false时,返回未处理的三角网格。" + }, + "should_remesh_target_polycount": { + "name": "target_polycount" + }, + "should_remesh_topology": { + "name": "topology" + }, + "style": { + "name": "style" + }, + "symmetry_mode": { + "name": "symmetry_mode" + } + }, + "outputs": { + "0": { + "name": "model_file", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } + } + }, + "MeshyTextureNode": { + "display_name": "Meshy:纹理模型", + "inputs": { + "enable_original_uv": { + "name": "启用原始UV", + "tooltip": "使用模型的原始UV,而不是生成新的UV。启用后,Meshy会保留上传模型中已有的纹理。如果模型没有原始UV,输出质量可能会有所下降。" + }, + "image_style": { + "name": "图像风格", + "tooltip": "用于引导纹理生成的2D图像。不能与“text_style_prompt”同时使用。" + }, + "meshy_task_id": { + "name": "meshy_task_id" + }, + "model": { + "name": "模型" + }, + "pbr": { + "name": "PBR" + }, + "text_style_prompt": { + "name": "文本风格提示", + "tooltip": "用文本描述你想要的物体纹理风格。最多600个字符。不能与“image_style”同时使用。" + } + }, + "outputs": { + "0": { + "name": "模型文件", + "tooltip": null + }, + "1": { + "name": "meshy_task_id", + "tooltip": null + } } }, "MinimaxHailuoVideoNode": { @@ -5468,22 +7292,22 @@ "display_name": "MiniMax 图像转视频", "inputs": { "control_after_generate": { - "name": "control after generate" + "name": "生成后控制" }, "image": { - "name": "image", + "name": "图像", "tooltip": "用于视频生成首帧的图像" }, "model": { - "name": "model", + "name": "模型", "tooltip": "用于视频生成的模型" }, "prompt_text": { - "name": "prompt_text", + "name": "提示词", "tooltip": "用于引导视频生成的文本提示" }, "seed": { - "name": "seed", + "name": "随机种", "tooltip": "用于生成噪声的随机种子。" } }, @@ -5498,18 +7322,18 @@ "display_name": "MiniMax 文本转视频", "inputs": { "control_after_generate": { - "name": "control after generate" + "name": "生成后控制" }, "model": { - "name": "model", + "name": "模型", "tooltip": "用于视频生成的模型" }, "prompt_text": { - "name": "prompt_text", + "name": "提示词", "tooltip": "用于引导视频生成的文本提示" }, "seed": { - "name": "seed", + "name": "随机种", "tooltip": "用于生成噪声的随机种子。" } }, @@ -5702,7 +7526,7 @@ } }, "ModelMergeCosmos14B": { - "display_name": "ModelMergeCosmos14B", + "display_name": "模型融合(Cosmos14B)", "inputs": { "affline_norm_": { "name": "仿射规范化." @@ -5839,7 +7663,7 @@ } }, "ModelMergeCosmos7B": { - "display_name": "ModelMergeCosmos7B", + "display_name": "模型融合(COsmos7B)", "inputs": { "affline_norm_": { "name": "仿射规范化." @@ -5952,7 +7776,7 @@ } }, "ModelMergeCosmosPredict2_14B": { - "display_name": "ModelMergeCosmosPredict2_14B", + "display_name": "模型融合(CosmosPredict2_14B)", "inputs": { "blocks_0_": { "name": "块0。" @@ -6086,7 +7910,7 @@ } }, "ModelMergeCosmosPredict2_2B": { - "display_name": "ModelMergeCosmosPredict2_2B", + "display_name": "模型融合(CosmosPredict2_2B)", "inputs": { "blocks_0_": { "name": "块.0." @@ -7443,7 +9267,7 @@ }, "ModelMergeWAN2_1": { "description": "1.3B模型有30个模块,14B模型有40个模块。图像转视频模型有额外的img_emb。", - "display_name": "ModelMergeWAN2_1", + "display_name": "模型融合(WAN2.1)", "inputs": { "blocks_0_": { "name": "blocks.0." @@ -7572,10 +9396,10 @@ "name": "img_emb." }, "model1": { - "name": "model1" + "name": "模型1" }, "model2": { - "name": "model2" + "name": "模型2" }, "patch_embedding_": { "name": "patch_embedding." @@ -7592,7 +9416,7 @@ } }, "ModelPatchLoader": { - "display_name": "ModelPatchLoader", + "display_name": "加载模型补丁", "inputs": { "name": { "name": "名称" @@ -7734,8 +9558,8 @@ } }, "MoonvalleyImg2VideoNode": { - "description": "Moonvalry Marey 图像转视频节点", - "display_name": "Moonvalry Marey 图像转视频", + "description": "Moonvalley Marey 图像转视频节点", + "display_name": "Moonvalley Marey 图像转视频", "inputs": { "control_after_generate": { "name": "生成后控制" @@ -7775,7 +9599,7 @@ } }, "MoonvalleyTxt2VideoNode": { - "display_name": "Moonvalry Marey 文本转视频", + "display_name": "Moonvalley Marey 文本转视频", "inputs": { "control_after_generate": { "name": "生成后控制" @@ -7811,7 +9635,7 @@ } }, "MoonvalleyVideo2VideoNode": { - "display_name": "Moonvalry Marey 视频转视频", + "display_name": "Moonvalley Marey 视频转视频", "inputs": { "control_type": { "name": "控制类型" @@ -7866,16 +9690,62 @@ } } }, + "NormalizeImages": { + "display_name": "规格化图像", + "inputs": { + "images": { + "name": "图像", + "tooltip": "需要处理的图像序列。" + }, + "mean": { + "name": "平均", + "tooltip": "规格化的平均值。" + }, + "std": { + "name": "标准差", + "tooltip": "规格化的标准差。" + } + }, + "outputs": { + "0": { + "name": "图像", + "tooltip": "已处理的图像" + } + } + }, + "NormalizeVideoLatentStart": { + "description": "将视频初始帧的 Latent 规格化,使其符合后续参考帧的平均值与标准差。有助于减少起始帧与视频其余部分之间的差异。", + "display_name": "规格化视频Latent", + "inputs": { + "latent": { + "name": "Latent" + }, + "reference_frame_count": { + "name": "参考数量", + "tooltip": "从起始帧开始,用于参考的 Latent 数量" + }, + "start_frame_count": { + "name": "规格化数量", + "tooltip": "从起始帧开始,需要规格化的 Latent 数量" + } + }, + "outputs": { + "0": { + "name": "latent", + "tooltip": null + } + } + }, "OpenAIChatConfig": { "description": "允许为OpenAI聊天节点指定高级配置选项。", "display_name": "OpenAI ChatGPT 高级选项", "inputs": { "instructions": { - "name": "instructions", + "name": "指令", "tooltip": "指导模型如何生成响应的指令" }, "max_output_tokens": { - "name": "max_output_tokens", + "name": "Token输出上限", "tooltip": "生成响应时可生成token数量的上限,包括可见输出token" }, "truncation": { @@ -7894,27 +9764,27 @@ "display_name": "OpenAI ChatGPT", "inputs": { "advanced_options": { - "name": "advanced_options", + "name": "高级设置", "tooltip": "模型的可选配置。接受来自OpenAI聊天高级选项节点的输入。" }, "files": { - "name": "files", + "name": "文件", "tooltip": "可选文件,用作模型的上下文。接受来自OpenAI聊天输入文件节点的输入。" }, "images": { - "name": "images", + "name": "图像", "tooltip": "可选图像,用作模型的上下文。要包含多张图像,可使用批处理图像节点。" }, "model": { - "name": "model", + "name": "模型", "tooltip": "用于生成响应的模型" }, "persist_context": { - "name": "persist_context", + "name": "保持上下文", "tooltip": "此参数已弃用,无任何效果。" }, "prompt": { - "name": "prompt", + "name": "提示词", "tooltip": "模型的文本输入,用于生成响应。" } }, @@ -8012,9 +9882,12 @@ "tooltip": "用于图像编辑的可选参考图像。" }, "mask": { - "name": "mask", + "name": "遮罩", "tooltip": "用于修复的可选 mask(白色区域将被替换)" }, + "model": { + "name": "模型" + }, "n": { "name": "数量", "tooltip": "生成多少张图像" @@ -8047,11 +9920,11 @@ "display_name": "OpenAI ChatGPT Input Files", "inputs": { "OPENAI_INPUT_FILES": { - "name": "OPENAI_INPUT_FILES", + "name": "OpenAI输入文件", "tooltip": "可选的附加文件,与此节点加载的文件一起批处理。允许链式连接输入文件,以便单个消息可包含多个输入文件。" }, "file": { - "name": "file", + "name": "文件", "tooltip": "作为模型上下文的输入文件。目前仅接受文本(.txt)和PDF(.pdf)文件。" } }, @@ -8066,27 +9939,27 @@ "display_name": "OpenAI Sora - Video", "inputs": { "control_after_generate": { - "name": "control after generate" + "name": "生成后控制" }, "duration": { - "name": "duration" + "name": "时长" }, "image": { - "name": "image" + "name": "图像" }, "model": { - "name": "model" + "name": "模型" }, "prompt": { - "name": "prompt", + "name": "提示词", "tooltip": "引导文本;如果存在输入图像,可为空。" }, "seed": { - "name": "seed", + "name": "随机种", "tooltip": "确定节点是否应重新运行的种子;无论种子如何,实际结果都是非确定性的。" }, "size": { - "name": "size" + "name": "尺寸" } }, "outputs": { @@ -8096,13 +9969,13 @@ } }, "OptimalStepsScheduler": { - "display_name": "OptimalStepsScheduler", + "display_name": "OptimalSteps调度器", "inputs": { "denoise": { "name": "去噪" }, "model_type": { - "name": "model_type" + "name": "模型" }, "steps": { "name": "步数" @@ -8373,265 +10246,6 @@ } } }, - "PikaImageToVideoNode2_2": { - "description": "将图像和提示词发送到 Pika API v2.2 以生成视频。", - "display_name": "Pika 图像转视频", - "inputs": { - "control_after_generate": { - "name": "生成后控制" - }, - "duration": { - "name": "时长" - }, - "image": { - "name": "图像", - "tooltip": "要转换为视频的图像" - }, - "negative_prompt": { - "name": "反向提示词" - }, - "prompt_text": { - "name": "提示词" - }, - "resolution": { - "name": "分辨率" - }, - "seed": { - "name": "种子" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaScenesV2_2": { - "description": "将你的图像组合在一起,生成包含所有物体的高质量视频。上传多张图片作为素材,生成融合所有内容的视频。", - "display_name": "Pika 场景(视频图像合成)", - "inputs": { - "aspect_ratio": { - "name": "宽高比", - "tooltip": "宽高比(宽 / 高)" - }, - "control_after_generate": { - "name": "生成后控制" - }, - "duration": { - "name": "时长" - }, - "image_ingredient_1": { - "name": "图片素材 1", - "tooltip": "用于生成视频的图片素材。" - }, - "image_ingredient_2": { - "name": "图片素材 2", - "tooltip": "用于生成视频的图片素材。" - }, - "image_ingredient_3": { - "name": "图片素材 3", - "tooltip": "用于生成视频的图片素材。" - }, - "image_ingredient_4": { - "name": "图片素材 4", - "tooltip": "用于生成视频的图片素材。" - }, - "image_ingredient_5": { - "name": "图片素材 5", - "tooltip": "用于生成视频的图片素材。" - }, - "ingredients_mode": { - "name": "素材模式" - }, - "negative_prompt": { - "name": "反向提示词" - }, - "prompt_text": { - "name": "提示词" - }, - "resolution": { - "name": "分辨率" - }, - "seed": { - "name": "随机种子" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaStartEndFrameNode2_2": { - "description": "通过合成首帧和尾帧生成视频。上传两张图片以定义起点和终点,让 AI 在它们之间创建平滑过渡。", - "display_name": "Pika 首尾帧合成视频", - "inputs": { - "control_after_generate": { - "name": "生成后控制" - }, - "duration": { - "name": "duration" - }, - "image_end": { - "name": "image_end", - "tooltip": "要合成的最后一张图片。" - }, - "image_start": { - "name": "image_start", - "tooltip": "要合成的第一张图片。" - }, - "negative_prompt": { - "name": "negative_prompt" - }, - "prompt_text": { - "name": "prompt_text" - }, - "resolution": { - "name": "resolution" - }, - "seed": { - "name": "seed" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "PikaTextToVideoNode2_2": { - "description": "将文本提示发送到 Pika API v2.2 以生成视频。", - "display_name": "Pika 文本转视频", - "inputs": { - "aspect_ratio": { - "name": "宽高比", - "tooltip": "宽高比(宽 / 高)" - }, - "control_after_generate": { - "name": "生成后控制" - }, - "duration": { - "name": "时长" - }, - "negative_prompt": { - "name": "反向提示" - }, - "prompt_text": { - "name": "提示文本" - }, - "resolution": { - "name": "分辨率" - }, - "seed": { - "name": "种子" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikadditions": { - "description": "将任意对象或图像添加到你的视频中。上传一个视频并指定你想要添加的内容,实现无缝集成的效果。", - "display_name": "Pikadditions(视频对象插入)", - "inputs": { - "control_after_generate": { - "name": "生成后控制" - }, - "image": { - "name": "图像", - "tooltip": "要添加到视频中的图像。" - }, - "negative_prompt": { - "name": "反向提示词" - }, - "prompt_text": { - "name": "提示词" - }, - "seed": { - "name": "种子" - }, - "video": { - "name": "视频", - "tooltip": "要添加图像的视频。" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikaffects": { - "description": "生成带有特定Pikaffect的视频。支持的Pikaffect有:Cake-ify、Crumble、Crush、Decapitate、Deflate、Dissolve、Explode、Eye-pop、Inflate、Levitate、Melt、Peel、Poke、Squish、Ta-da、Tear", - "display_name": "Pikaffects(视频特效)", - "inputs": { - "control_after_generate": { - "name": "生成后控制" - }, - "image": { - "name": "image", - "tooltip": "要应用Pikaffect的参考图像。" - }, - "negative_prompt": { - "name": "negative_prompt" - }, - "pikaffect": { - "name": "pikaffect" - }, - "prompt_text": { - "name": "prompt_text" - }, - "seed": { - "name": "seed" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, - "Pikaswaps": { - "description": "用新图像或对象替换视频中的任意对象或区域。可通过 mask 或坐标定义需要替换的区域。", - "display_name": "Pika Swaps(视频对象替换)", - "inputs": { - "control_after_generate": { - "name": "生成后控制" - }, - "image": { - "name": "图像", - "tooltip": "用于替换视频中被 mask 的对象的图像。" - }, - "mask": { - "name": "mask", - "tooltip": "使用 mask 定义视频中需要替换的区域" - }, - "negative_prompt": { - "name": "反向提示词" - }, - "prompt_text": { - "name": "提示词" - }, - "region_to_modify": { - "name": "region_to_modify", - "tooltip": "要修改的对象/区域的纯文本描述。" - }, - "seed": { - "name": "种子" - }, - "video": { - "name": "视频", - "tooltip": "要在其中替换对象的视频。" - } - }, - "outputs": { - "0": { - "tooltip": null - } - } - }, "PixverseImageToVideoNode": { "description": "根据提示词和输出尺寸同步生成视频。", "display_name": "PixVerse 图像转视频", @@ -8683,7 +10297,7 @@ }, "outputs": { "0": { - "name": "pixverse_template", + "name": "Pixverse模板", "tooltip": null } } @@ -8786,6 +10400,11 @@ "steps": { "name": "步数" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "PorterDuffImageComposite": { @@ -8819,6 +10438,9 @@ "Preview3D": { "display_name": "预览3D", "inputs": { + "bg_image": { + "name": "背景图像" + }, "camera_info": { "name": "相机信息" }, @@ -8830,22 +10452,13 @@ } } }, - "Preview3DAnimation": { - "display_name": "预览3D - 动画", - "inputs": { - "camera_info": { - "name": "相机信息" - }, - "model_file": { - "name": "模型文件" - } - } - }, "PreviewAny": { "display_name": "预览任意", "inputs": { "preview": { }, + "previewMode": { + }, "source": { "name": "源" } @@ -8941,7 +10554,7 @@ }, "QuadrupleCLIPLoader": { "description": "[配方]\n\nhidream: long clip-l, long clip-g, t5xxl, llama_8b_3.1_instruct", - "display_name": "QuadrupleCLIPLoader", + "display_name": "四重CLIP加载器", "inputs": { "clip_name1": { "name": "clip_name1" @@ -8966,25 +10579,55 @@ "display_name": "QwenImageDiffsynthControlnet", "inputs": { "image": { - "name": "image" + "name": "图像" }, "mask": { - "name": "mask" + "name": "遮罩" }, "model": { - "name": "model" + "name": "模型" }, "model_patch": { - "name": "model_patch" + "name": "模型补丁" }, "strength": { - "name": "strength" + "name": "强度" }, "vae": { "name": "vae" } } }, + "RandomCropImages": { + "display_name": "裁剪图像(随机)", + "inputs": { + "control_after_generate": { + "name": "生成后控制" + }, + "height": { + "name": "高度", + "tooltip": "裁剪框高度" + }, + "images": { + "name": "图像", + "tooltip": "需要处理的图像序列。" + }, + "seed": { + "name": "随机种", + "tooltip": "随机种" + }, + "width": { + "name": "宽度", + "tooltip": "裁剪框宽度" + } + }, + "outputs": { + "0": { + "name": "图像", + "tooltip": "已处理的图像" + } + } + }, "RandomNoise": { "display_name": "随机噪波", "inputs": { @@ -8994,6 +10637,11 @@ "noise_seed": { "name": "噪波随机种" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RebatchImages": { @@ -9029,10 +10677,15 @@ } }, "RecordAudio": { - "display_name": "Record Audio", + "display_name": "录制音频", "inputs": { "audio": { - "name": "audio" + "name": "音频" + } + }, + "outputs": { + "0": { + "tooltip": null } } }, @@ -9053,12 +10706,12 @@ "tooltip": "颜色的红色值。" }, "recraft_color": { - "name": "recraft_color" + "name": "Recraft色彩" } }, "outputs": { "0": { - "name": "recraft_color", + "name": "Recraft色彩", "tooltip": null } } @@ -9068,15 +10721,15 @@ "display_name": "Recraft 控件", "inputs": { "background_color": { - "name": "background_color" + "name": "背景色" }, "colors": { - "name": "colors" + "name": "色彩" } }, "outputs": { "0": { - "name": "recraft_controls", + "name": "Recraft控制", "tooltip": null } } @@ -9117,10 +10770,10 @@ "name": "生成后控制" }, "image": { - "name": "image" + "name": "图像" }, "mask": { - "name": "mask" + "name": "遮罩" }, "n": { "name": "数量", @@ -9135,7 +10788,7 @@ "tooltip": "用于图像生成的提示词。" }, "recraft_style": { - "name": "recraft_style" + "name": "Recraft风格" }, "seed": { "name": "种子", @@ -9156,7 +10809,7 @@ "name": "生成后控制" }, "image": { - "name": "image" + "name": "图像" }, "n": { "name": "数量", @@ -9171,11 +10824,11 @@ "tooltip": "用于生成图像的提示词。" }, "recraft_controls": { - "name": "recraft_controls", + "name": "Recraft控制", "tooltip": "通过 Recraft Controls 节点对生成过程进行可选的附加控制。" }, "recraft_style": { - "name": "recraft_style" + "name": "Recraft风格" }, "seed": { "name": "种子", @@ -9255,7 +10908,7 @@ }, "outputs": { "0": { - "name": "recraft_style", + "name": "Recraft风格", "tooltip": null } } @@ -9265,13 +10918,13 @@ "display_name": "Recraft 风格 - 无限风格库", "inputs": { "style_id": { - "name": "style_id", + "name": "风格ID", "tooltip": "来自无限风格库的风格 UUID。" } }, "outputs": { "0": { - "name": "recraft_style", + "name": "Recraft风格", "tooltip": null } } @@ -9286,7 +10939,7 @@ }, "outputs": { "0": { - "name": "recraft_style", + "name": "Recraft风格", "tooltip": null } } @@ -9301,7 +10954,7 @@ }, "outputs": { "0": { - "name": "recraft_style", + "name": "Recraft风格", "tooltip": null } } @@ -9311,33 +10964,33 @@ "display_name": "Recraft 文本转图像", "inputs": { "control_after_generate": { - "name": "control after generate" + "name": "生成后控制" }, "n": { "name": "n", "tooltip": "要生成的图像数量。" }, "negative_prompt": { - "name": "negative_prompt", + "name": "负面提示词", "tooltip": "对图像中不希望出现元素的可选文本描述。" }, "prompt": { - "name": "prompt", + "name": "提示词", "tooltip": "用于图像生成的提示词。" }, "recraft_controls": { - "name": "recraft_controls", + "name": "Recraft控制", "tooltip": "通过 Recraft Controls 节点对生成过程的可选附加控制。" }, "recraft_style": { - "name": "recraft_style" + "name": "Recraft风格" }, "seed": { - "name": "seed", + "name": "随机种", "tooltip": "用于决定节点是否重新运行的种子;无论种子如何,实际结果都是非确定性的。" }, "size": { - "name": "size", + "name": "尺寸", "tooltip": "生成图像的尺寸。" } }, @@ -9404,13 +11057,13 @@ }, "ReferenceLatent": { "description": "此节点为编辑模型设置引导潜在空间。如果模型支持,您可以链式连接多个以设置多个参考图像。", - "display_name": "ReferenceLatent", + "display_name": "参考Latent", "inputs": { "conditioning": { - "name": "conditioning" + "name": "条件" }, "latent": { - "name": "latent" + "name": "Latent" } }, "outputs": { @@ -9538,6 +11191,11 @@ "image": { "name": "图像" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "RepeatLatentBatch": { @@ -9551,6 +11209,51 @@ } } }, + "ReplaceText": { + "display_name": "替换文本", + "inputs": { + "find": { + "name": "搜索", + "tooltip": "需要被替换的文本" + }, + "replace": { + "name": "替换", + "tooltip": "用于替换的文本" + }, + "texts": { + "name": "文本", + "tooltip": "需要处理的文本" + } + }, + "outputs": { + "0": { + "name": "文本", + "tooltip": "已处理的文本" + } + } + }, + "ReplaceVideoLatentFrames": { + "display_name": "替换视频Latent", + "inputs": { + "destination": { + "name": "目标Latent", + "tooltip": "需要被替换的 Latent" + }, + "index": { + "name": "序号", + "tooltip": "选择需要被替换的 Latent 的序号,负值表示从后往前数。" + }, + "source": { + "name": "来源Latent", + "tooltip": "用于替换的 Latent,如果没有则不会替换。" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "RescaleCFG": { "display_name": "缩放CFG", "inputs": { @@ -9580,6 +11283,104 @@ "target_width": { "name": "目标宽度" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ResizeImageMaskNode": { + "description": "使用多种缩放方法调整图像或 mask 的大小。", + "display_name": "调整图像/掩码大小", + "inputs": { + "input": { + "name": "输入" + }, + "resize_type": { + "name": "调整类型", + "tooltip": "选择如何调整大小:按精确尺寸、缩放因子、匹配另一张图像等。" + }, + "resize_type_crop": { + "name": "裁剪" + }, + "resize_type_height": { + "name": "高度" + }, + "resize_type_width": { + "name": "宽度" + }, + "scale_method": { + "name": "缩放方法", + "tooltip": "插值算法。'area' 适合缩小,'lanczos' 适合放大,'nearest-exact' 适合像素艺术。" + } + }, + "outputs": { + "0": { + "name": "已调整大小", + "tooltip": null + } + } + }, + "ResizeImagesByLongerEdge": { + "display_name": "缩放图像(长边)", + "inputs": { + "images": { + "name": "图像", + "tooltip": "需要处理的图像序列。" + }, + "longer_edge": { + "name": "长边", + "tooltip": "长边目标长度。" + } + }, + "outputs": { + "0": { + "name": "图像", + "tooltip": "已处理的图像" + } + } + }, + "ResizeImagesByShorterEdge": { + "display_name": "Resize Images by Shorter Edge", + "inputs": { + "images": { + "name": "图像", + "tooltip": "需要处理的图像序列。" + }, + "shorter_edge": { + "name": "短边", + "tooltip": "短边目标长度。" + } + }, + "outputs": { + "0": { + "name": "图像", + "tooltip": "已处理的图像" + } + } + }, + "ResolutionBucket": { + "display_name": "分辨率 Bucket", + "inputs": { + "conditioning": { + "name": "条件", + "tooltip": "条件序列(必须和 Latent 序列数量相同)" + }, + "latents": { + "name": "Latent", + "tooltip": "需要 bucket 的 Latent 字典序列" + } + }, + "outputs": { + "0": { + "name": "Latent", + "tooltip": "Latent 字典,按分辨率分批。" + }, + "1": { + "name": "条件", + "tooltip": "条件字典,按分辨率分批。" + } } }, "Rodin3D_Detail": { @@ -9833,6 +11634,11 @@ "steps": { "name": "步数" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SD_4XUpscale_Conditioning": { @@ -9988,10 +11794,12 @@ }, "outputs": { "0": { - "name": "Latent" + "name": "Latent", + "tooltip": null }, "1": { - "name": "降噪Latent" + "name": "降噪Latent", + "tooltip": null } } }, @@ -10016,10 +11824,12 @@ }, "outputs": { "0": { - "name": "Latent" + "name": "Latent", + "tooltip": null }, "1": { - "name": "降噪Latent" + "name": "降噪Latent", + "tooltip": null } } }, @@ -10056,6 +11866,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_2M_SDE": { @@ -10065,7 +11880,7 @@ "name": "eta" }, "noise_device": { - "name": "噪波设备" + "name": "设备" }, "s_noise": { "name": "s_noise" @@ -10073,6 +11888,11 @@ "solver_type": { "name": "求解器类型" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_2S_Ancestral": { @@ -10084,6 +11904,11 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_3M_SDE": { @@ -10093,11 +11918,16 @@ "name": "eta" }, "noise_device": { - "name": "噪波设备" + "name": "设备" }, "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerDPMPP_SDE": { @@ -10107,7 +11937,7 @@ "name": "eta" }, "noise_device": { - "name": "噪波设备" + "name": "设备" }, "r": { "name": "r" @@ -10115,10 +11945,15 @@ "s_noise": { "name": "s_noise" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerER_SDE": { - "display_name": "SamplerER_SDE", + "display_name": "ER_SDE采样器", "inputs": { "eta": { "name": "eta", @@ -10131,7 +11966,12 @@ "name": "s_noise" }, "solver_type": { - "name": "求解器类型" + "name": "求解器" + } + }, + "outputs": { + "0": { + "tooltip": null } } }, @@ -10144,6 +11984,11 @@ "s_noise": { "name": "s噪波" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerAncestralCFGPP": { @@ -10155,6 +12000,11 @@ "s_noise": { "name": "s噪波" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerEulerCFGpp": { @@ -10195,10 +12045,15 @@ "order": { "name": "顺序" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplerSASolver": { - "display_name": "SamplerSASolver", + "display_name": "SASolver采样器", "inputs": { "corrector_order": { "name": "校正器阶数" @@ -10227,10 +12082,41 @@ "use_pece": { "name": "使用 PECE" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "SamplerSEEDS2": { + "description": "该采样器节点可以表示多个采样器:\n\nseeds 2\n-默认设置\n\nexp_heun_2_x0\n- 求解器=phi 2, r=1.0, eta=0.0\n\nexp_heun_2_x0_sde\n- 求解器=phi 2, r=1.0, eta=1.0, s noise=1.0", + "display_name": "SEEDS2采样器", + "inputs": { + "eta": { + "name": "eta", + "tooltip": "Stochastic strength" + }, + "r": { + "name": "r", + "tooltip": "Relative step size for the intermediate stage (c2 node)" + }, + "s_noise": { + "name": "s_noise", + "tooltip": "SDE noise multiplier" + }, + "solver_type": { + "name": "求解器" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SamplingPercentToSigma": { - "display_name": "SamplingPercentToSigma", + "display_name": "采样比到Sigma", "inputs": { "model": { "name": "模型" @@ -10245,7 +12131,8 @@ }, "outputs": { "0": { - "name": "sigma 值" + "name": "sigma 值", + "tooltip": null } } }, @@ -10338,7 +12225,7 @@ } }, "SaveGLB": { - "display_name": "SaveGLB", + "display_name": "保存GLB", "inputs": { "filename_prefix": { "name": "文件名前缀" @@ -10365,6 +12252,44 @@ } } }, + "SaveImageDataSetToFolder": { + "display_name": "保存图像数据集", + "inputs": { + "filename_prefix": { + "name": "文件名前缀", + "tooltip": "保存的图像的文件名前缀。" + }, + "folder_name": { + "name": "文件夹名", + "tooltip": "保存训练数据集的文件夹名(在 输出 文件夹内)。" + }, + "images": { + "name": "图像", + "tooltip": "需要保存的图像序列。" + } + } + }, + "SaveImageTextDataSetToFolder": { + "display_name": "保存图像文本数据集", + "inputs": { + "filename_prefix": { + "name": "文件名前缀", + "tooltip": "保存的图像的文件名前缀。" + }, + "folder_name": { + "name": "文件夹名", + "tooltip": "保存训练数据集的文件夹名(在 输出 文件夹内)。" + }, + "images": { + "name": "图像", + "tooltip": "需要保存的图像序列。" + }, + "texts": { + "name": "文本", + "tooltip": "需要保存的标注文本序列。" + } + } + }, "SaveImageWebsocket": { "display_name": "保存图像(网络接口)", "inputs": { @@ -10384,29 +12309,29 @@ } } }, - "SaveLoRANode": { - "display_name": "保存 LoRA 权重", + "SaveLoRA": { + "display_name": "保存 LoRA", "inputs": { "lora": { - "name": "lora", - "tooltip": "要保存的 LoRA 模型。请勿使用带有 LoRA 层的模型。" + "name": "LoRA", + "tooltip": "需要保存的 LoRA 模型。不要使用分层 LoRA。" }, "prefix": { "name": "前缀", - "tooltip": "用于保存的 LoRA 文件的前缀。" + "tooltip": "保存的 LoRA 文件的文件名前缀。" }, "steps": { "name": "步数", - "tooltip": "可选:LoRA 已训练的步数,用于命名保存的文件。" + "tooltip": "可选:LoRA 的训练步数,用于保存文件命名。" } } }, "SaveSVGNode": { "description": "在磁盘上保存 SVG 文件。", - "display_name": "SaveSVGNode", + "display_name": "保存SVG", "inputs": { "filename_prefix": { - "name": "filename_prefix", + "name": "文件名前缀", "tooltip": "保存文件的前缀。可包含格式化信息,如 %date:yyyy-MM-dd% 或 %Empty Latent Image.width% 以包含节点中的值。" }, "svg": { @@ -10414,6 +12339,27 @@ } } }, + "SaveTrainingDataset": { + "display_name": "保存训练数据集", + "inputs": { + "conditioning": { + "name": "条件", + "tooltip": "制作训练数据集 节点输出的条件字典" + }, + "folder_name": { + "name": "文件夹名", + "tooltip": "包含训练数据集的文件夹名(在 输出 文件夹内)。" + }, + "latents": { + "name": "Latent", + "tooltip": "制作训练数据集 节点输出的 Latent 字典" + }, + "shard_size": { + "name": "shard_size", + "tooltip": "Number of samples per shard file." + } + } + }, "SaveVideo": { "description": "将输入图像保存到您的 ComfyUI 输出目录。", "display_name": "保存视频", @@ -10459,10 +12405,10 @@ }, "ScaleROPE": { "description": "缩放和偏移模型的ROPE。", - "display_name": "ScaleROPE", + "display_name": "缩放ROPE", "inputs": { "model": { - "name": "model" + "name": "模型" }, "scale_t": { "name": "scale_t" @@ -10534,6 +12480,11 @@ "sigmas": { "name": "sigmas" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SetHookKeyframes": { @@ -10574,6 +12525,58 @@ } } }, + "ShuffleDataset": { + "display_name": "打乱图像数据集", + "inputs": { + "control_after_generate": { + "name": "生成后控制" + }, + "images": { + "name": "图像", + "tooltip": "需要处理的图像序列。" + }, + "seed": { + "name": "随机种", + "tooltip": "随机种" + } + }, + "outputs": { + "0": { + "name": "图像", + "tooltip": "处理后的图像" + } + } + }, + "ShuffleImageTextDataset": { + "display_name": "打乱图像文本数据集", + "inputs": { + "control_after_generate": { + "name": "生成后控制" + }, + "images": { + "name": "图像", + "tooltip": "需要打乱的图像序列。" + }, + "seed": { + "name": "随机种", + "tooltip": "随机种" + }, + "texts": { + "name": "文本", + "tooltip": "需要打乱的文本序列" + } + }, + "outputs": { + "0": { + "name": "图像", + "tooltip": "打乱后的图像" + }, + "1": { + "name": "文本", + "tooltip": "打乱后的文本" + } + } + }, "SkipLayerGuidanceDiT": { "description": "通用的跳过层引导节点,可用于每个DiT模型。", "display_name": "跳过层引导(DiT)", @@ -10607,23 +12610,23 @@ } }, "SkipLayerGuidanceDiTSimple": { - "description": "SkipLayerGuidanceDiT节点的简化版本,仅修改无条件传递。", - "display_name": "SkipLayerGuidanceDiTSimple", + "description": "跳过层引导(DiT)节点的简化版本,仅修改无条件传递。", + "display_name": "跳过层引导(DiT简化)", "inputs": { "double_layers": { - "name": "double_layers" + "name": "双层" }, "end_percent": { - "name": "end_percent" + "name": "结束百分比" }, "model": { - "name": "model" + "name": "模型" }, "single_layers": { - "name": "single_layers" + "name": "单层" }, "start_percent": { - "name": "start_percent" + "name": "开始百分比" } }, "outputs": { @@ -10670,22 +12673,29 @@ "width": { "name": "宽度" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "SplitAudioChannels": { "description": "将音频分离为左右声道。", - "display_name": "Split Audio Channels", + "display_name": "分离音频通道", "inputs": { "audio": { - "name": "audio" + "name": "音频" } }, "outputs": { "0": { - "name": "left" + "name": "左声道", + "tooltip": null }, "1": { - "name": "right" + "name": "右声道", + "tooltip": null } } }, @@ -10717,10 +12727,12 @@ }, "outputs": { "0": { - "name": "高方差" + "name": "高方差", + "tooltip": null }, "1": { - "name": "低方差" + "name": "低方差", + "tooltip": null } } }, @@ -10736,46 +12748,48 @@ }, "outputs": { "0": { - "name": "高方差" + "name": "高方差", + "tooltip": null }, "1": { - "name": "低方差" + "name": "低方差", + "tooltip": null } } }, "StabilityAudioInpaint": { "description": "使用文本指令转换现有音频样本的部分内容。", - "display_name": "Stability AI Audio Inpaint", + "display_name": "Stability AI 音频重绘", "inputs": { "audio": { - "name": "audio", + "name": "音频", "tooltip": "音频长度必须在6到190秒之间。" }, "control_after_generate": { - "name": "control after generate" + "name": "生成后控制" }, "duration": { - "name": "duration", + "name": "时长", "tooltip": "控制生成音频的时长(秒)。" }, "mask_end": { - "name": "mask_end" + "name": "结束遮罩" }, "mask_start": { - "name": "mask_start" + "name": "开始遮罩" }, "model": { - "name": "model" + "name": "模型" }, "prompt": { - "name": "prompt" + "name": "提示词" }, "seed": { - "name": "seed", + "name": "随机种", "tooltip": "用于生成的随机种子。" }, "steps": { - "name": "steps", + "name": "步数", "tooltip": "控制采样步数。" } }, @@ -10787,31 +12801,31 @@ }, "StabilityAudioToAudio": { "description": "使用文本指令将现有音频样本转换为新的高质量作品。", - "display_name": "Stability AI Audio To Audio", + "display_name": "Stability AI 音频到音频", "inputs": { "audio": { - "name": "audio", + "name": "音频", "tooltip": "音频长度必须在6到190秒之间。" }, "control_after_generate": { "name": "生成后控制" }, "duration": { - "name": "duration", + "name": "时长", "tooltip": "控制生成音频的时长(秒)。" }, "model": { - "name": "model" + "name": "模型" }, "prompt": { - "name": "prompt" + "name": "提示词" }, "seed": { - "name": "seed", + "name": "随机种", "tooltip": "用于生成的随机种子。" }, "steps": { - "name": "steps", + "name": "步数", "tooltip": "控制采样步数。" }, "strength": { @@ -10830,40 +12844,40 @@ "display_name": "Stability AI Stable Diffusion 3.5 图像", "inputs": { "aspect_ratio": { - "name": "aspect_ratio", + "name": "宽高比", "tooltip": "生成图像的宽高比。" }, "cfg_scale": { - "name": "cfg_scale", + "name": "CFG", "tooltip": "扩散过程对提示词文本的遵循程度(数值越高,生成的图像越接近你的提示词)" }, "control_after_generate": { - "name": "control after generate" + "name": "生成后控制" }, "image": { - "name": "image" + "name": "图像" }, "image_denoise": { - "name": "image_denoise", + "name": "图像降噪", "tooltip": "输入图像的去噪程度;0.0 表示与输入图像完全相同,1.0 表示完全不使用输入图像。" }, "model": { - "name": "model" + "name": "模型" }, "negative_prompt": { - "name": "negative_prompt", + "name": "负面提示词", "tooltip": "你不希望在输出图像中出现的关键词。此为高级功能。" }, "prompt": { - "name": "prompt", + "name": "提示词", "tooltip": "你希望在输出图像中看到的内容。强有力且描述清晰的提示词,能够明确定义元素、颜色和主题,将带来更好的结果。" }, "seed": { - "name": "seed", + "name": "随机种", "tooltip": "用于生成噪声的随机种子。" }, "style_preset": { - "name": "style_preset", + "name": "风格预设", "tooltip": "可选,生成图像的期望风格。" } }, @@ -10875,36 +12889,36 @@ }, "StabilityStableImageUltraNode": { "description": "根据提示词和分辨率同步生成图像。", - "display_name": "Stability AI Stable Image Ultra", + "display_name": "Stability AI Stable 图像 Ultra", "inputs": { "aspect_ratio": { - "name": "aspect_ratio", + "name": "宽高比", "tooltip": "生成图像的宽高比。" }, "control_after_generate": { - "name": "control after generate" + "name": "生成后控制" }, "image": { - "name": "image" + "name": "图像" }, "image_denoise": { - "name": "image_denoise", + "name": "降噪", "tooltip": "输入图像的去噪强度;0.0 表示与输入图像完全相同,1.0 表示完全不参考输入图像。" }, "negative_prompt": { - "name": "negative_prompt", + "name": "负面提示词", "tooltip": "描述你不希望在输出图像中出现内容的文本。这是一个高级功能。" }, "prompt": { - "name": "prompt", + "name": "提示词", "tooltip": "你希望在输出图像中看到的内容。一个强有力且描述清晰的提示词,能够明确界定元素、颜色和主题,将带来更好的结果。要控制某个词的权重,请使用格式 `(word:weight)`,其中 `word` 是你想要控制权重的词,`weight` 是介于 0 到 1 之间的数值。例如:`The sky was a crisp (blue:0.3) and (green:0.8)` 表示天空是蓝色和绿色,但绿色多于蓝色。" }, "seed": { - "name": "seed", + "name": "随机种", "tooltip": "用于生成噪声的随机种子。" }, "style_preset": { - "name": "style_preset", + "name": "风格预设", "tooltip": "可选的生成图像风格。" } }, @@ -10958,7 +12972,7 @@ "tooltip": "控制生成与初始图像不强相关的额外细节的可能性。" }, "image": { - "name": "image" + "name": "图像" }, "negative_prompt": { "name": "反向提示词", @@ -11343,6 +13357,21 @@ } } }, + "StripWhitespace": { + "display_name": "清理空字符", + "inputs": { + "texts": { + "name": "文本", + "tooltip": "需要处理的文本" + } + }, + "outputs": { + "0": { + "name": "文本", + "tooltip": "已处理的文本" + } + } + }, "StyleModelApply": { "display_name": "应用风格模型", "inputs": { @@ -11372,7 +13401,7 @@ } }, "T5TokenizerOptions": { - "display_name": "T5TokenizerOptions", + "display_name": "T5Tokenizer设置", "inputs": { "clip": { "name": "clip" @@ -11400,7 +13429,7 @@ }, "outputs": { "0": { - "name": "修补模型", + "name": "模型", "tooltip": null } } @@ -11423,13 +13452,91 @@ }, "outputs": { "0": { - "name": "patched_model", + "name": "模型", + "tooltip": null + } + } + }, + "TencentImageToModelNode": { + "display_name": "Hunyuan3D:图像转模型(专业版)", + "inputs": { + "control_after_generate": { + "name": "生成后控制" + }, + "face_count": { + "name": "面数" + }, + "generate_type": { + "name": "生成类型" + }, + "generate_type_pbr": { + "name": "PBR" + }, + "image": { + "name": "图像" + }, + "image_back": { + "name": "背面图像" + }, + "image_left": { + "name": "左侧图像" + }, + "image_right": { + "name": "右侧图像" + }, + "model": { + "name": "模型", + "tooltip": "LowPoly 选项在 `3.1` 模型中不可用。" + }, + "seed": { + "name": "种子", + "tooltip": "种子控制节点是否重新运行;无论种子如何,结果都是非确定性的。" + } + }, + "outputs": { + "0": { + "name": "模型文件", + "tooltip": null + } + } + }, + "TencentTextToModelNode": { + "display_name": "Hunyuan3D:文本转模型(专业版)", + "inputs": { + "control_after_generate": { + "name": "生成后控制" + }, + "face_count": { + "name": "面数" + }, + "generate_type": { + "name": "生成类型" + }, + "generate_type_pbr": { + "name": "PBR" + }, + "model": { + "name": "模型", + "tooltip": "LowPoly 选项在 `3.1` 模型中不可用。" + }, + "prompt": { + "name": "提示词", + "tooltip": "支持最多 1024 个字符。" + }, + "seed": { + "name": "种子", + "tooltip": "种子控制节点是否重新运行;无论种子如何,结果都是非确定性的。" + } + }, + "outputs": { + "0": { + "name": "模型文件", "tooltip": null } } }, "TextEncodeAceStepAudio": { - "display_name": "TextEncodeAceStepAudio", + "display_name": "文本音频编码(AceStep)", "inputs": { "clip": { "name": "clip" @@ -11451,7 +13558,7 @@ } }, "TextEncodeHunyuanVideo_ImageToVideo": { - "display_name": "文本编码Hunyuan视频_图像到视频", + "display_name": "文本编码(Hunyuan视频_图像到视频)", "inputs": { "clip": { "name": "clip" @@ -11474,7 +13581,7 @@ } }, "TextEncodeQwenImageEdit": { - "display_name": "TextEncodeQwenImageEdit", + "display_name": "文本编码(QwenImageEdit)", "inputs": { "clip": { "name": "clip" @@ -11496,7 +13603,7 @@ } }, "TextEncodeQwenImageEditPlus": { - "display_name": "TextEncodeQwenImageEditPlus", + "display_name": "文本编码(QwenImageEditPlus)", "inputs": { "clip": { "name": "clip" @@ -11523,6 +13630,70 @@ } } }, + "TextEncodeZImageOmni": { + "display_name": "TextEncodeZImageOmni", + "inputs": { + "auto_resize_images": { + "name": "auto_resize_images" + }, + "clip": { + "name": "clip" + }, + "image1": { + "name": "image1" + }, + "image2": { + "name": "image2" + }, + "image3": { + "name": "image3" + }, + "image_encoder": { + "name": "image_encoder" + }, + "prompt": { + "name": "prompt" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TextToLowercase": { + "display_name": "文本半角", + "inputs": { + "texts": { + "name": "文本", + "tooltip": "需要处理的文本" + } + }, + "outputs": { + "0": { + "name": "文本", + "tooltip": "已经处理的文本" + } + } + }, + "TextToUppercase": { + "display_name": "文本全角", + "inputs": { + "texts": { + "name": "文本", + "tooltip": "需要处理的文本" + } + }, + "outputs": { + "0": { + "name": "文本", + "tooltip": "已经处理的文本" + } + } + }, "ThresholdMask": { "display_name": "遮罩阈值", "inputs": { @@ -11532,6 +13703,11 @@ "value": { "name": "值" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "TomePatchModel": { @@ -11550,6 +13726,118 @@ } } }, + "TopazImageEnhance": { + "description": "专业级放大和图像增强。", + "display_name": "Topaz 图像增强", + "inputs": { + "color_preservation": { + "name": "固定色彩", + "tooltip": "保证色彩一致性。" + }, + "creativity": { + "name": "多样性" + }, + "crop_to_fill": { + "name": "裁剪", + "tooltip": "默认情况下,当输出宽高比不同时,图像会带有边框。启用后会裁剪图像填充输出尺寸。" + }, + "face_enhancement": { + "name": "面部增强", + "tooltip": "优化面部(如果存在)。" + }, + "face_enhancement_creativity": { + "name": "面部增强多样性", + "tooltip": "设置面部增强时的多样性。" + }, + "face_enhancement_strength": { + "name": "面部增强强度", + "tooltip": "控制面部相对于背景的锐度。" + }, + "face_preservation": { + "name": "固定面部", + "tooltip": "保证主体面部一致性。" + }, + "image": { + "name": "图像" + }, + "model": { + "name": "模型" + }, + "output_height": { + "name": "输出高度", + "tooltip": "0为自动(一般情况下和输入图像相同)。" + }, + "output_width": { + "name": "输出宽度", + "tooltip": "0为自动(一般情况下和输入图像相同)。" + }, + "prompt": { + "name": "提示词", + "tooltip": "用于引导放大" + }, + "subject_detection": { + "name": "物体检测" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "TopazVideoEnhance": { + "description": "通过强大的放大和修复技术,为视频注入新的生命。", + "display_name": "Topaz 视频增强", + "inputs": { + "dynamic_compression_level": { + "name": "动态压缩", + "tooltip": "CQP 等级." + }, + "interpolation_duplicate": { + "name": "补帧重复帧", + "tooltip": "分析原视频,移除重复的帧。" + }, + "interpolation_duplicate_threshold": { + "name": "补帧重复帧阈值", + "tooltip": "判断为重复帧的阈值。" + }, + "interpolation_enabled": { + "name": "补帧" + }, + "interpolation_frame_rate": { + "name": "补帧帧率", + "tooltip": "输出帧率" + }, + "interpolation_model": { + "name": "补帧模型" + }, + "interpolation_slowmo": { + "name": "补帧慢动作", + "tooltip": "使用慢动作补帧。例如,设为 2 会让视频变慢到 0.5 倍,持续时间增加到 2 倍。" + }, + "upscaler_creativity": { + "name": "放大多样性", + "tooltip": "多样等级(仅适用 Starlight (Astra) Creative 模型)." + }, + "upscaler_enabled": { + "name": "放大" + }, + "upscaler_model": { + "name": "放大模型" + }, + "upscaler_resolution": { + "name": "放大分辨率" + }, + "video": { + "name": "视频" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "TorchCompileModel": { "display_name": "Torch编译模型", "inputs": { @@ -11577,6 +13865,10 @@ "name": "批次大小", "tooltip": "用于训练的批次大小。" }, + "bucket_mode": { + "name": "bucket模式", + "tooltip": "启用分辨率 bucket 模式。开启后会先使用 分辨率Bucket 节点处理 Latent。" + }, "control_after_generate": { "name": "生成后控制" }, @@ -11639,16 +13931,20 @@ }, "outputs": { "0": { - "name": "带LoRA的模型" + "name": "带LoRA的模型", + "tooltip": "已应用 LoRA 的模型" }, "1": { - "name": "LoRA" + "name": "LoRA", + "tooltip": "LoRA 权重" }, "2": { - "name": "损失" + "name": "损失", + "tooltip": "损失历史图" }, "3": { - "name": "步数" + "name": "步数", + "tooltip": "总训练步数" } } }, @@ -11667,16 +13963,21 @@ "name": "起始索引", "tooltip": "开始时间(秒),可为负数表示从末尾开始计数(支持小数秒)。" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "TrimVideoLatent": { - "display_name": "TrimVideoLatent", + "display_name": "修剪视频Latent", "inputs": { "samples": { - "name": "samples" + "name": "Latent" }, "trim_amount": { - "name": "trim_amount" + "name": "修剪数量" } }, "outputs": { @@ -11708,23 +14009,62 @@ "TripoConversionNode": { "display_name": "Tripo:转换模型", "inputs": { + "animate_in_place": { + "name": "原地动画" + }, + "bake": { + "name": "烘焙" + }, + "export_orientation": { + "name": "导出朝向" + }, + "export_vertex_colors": { + "name": "导出顶点色" + }, "face_limit": { "name": "面数限制" }, + "fbx_preset": { + "name": "fbx预设" + }, + "flatten_bottom": { + "name": "平滑底部" + }, + "flatten_bottom_threshold": { + "name": "平滑底部阈值" + }, + "force_symmetry": { + "name": "强制对称" + }, "format": { "name": "格式" }, "original_model_task_id": { "name": "原始模型任务ID" }, + "pack_uv": { + "name": "打包uv" + }, + "part_names": { + "name": "部件名称" + }, + "pivot_to_center_bottom": { + "name": "对齐底部中心" + }, "quad": { "name": "四边形" }, + "scale_factor": { + "name": "缩放系数" + }, "texture_format": { "name": "纹理格式" }, "texture_size": { "name": "纹理大小" + }, + "with_animation": { + "name": "加入动画" } } }, @@ -11734,6 +14074,9 @@ "face_limit": { "name": "面数限制" }, + "geometry_quality": { + "name": "几何质量" + }, "image": { "name": "图像" }, @@ -11786,6 +14129,9 @@ "face_limit": { "name": "面数限制" }, + "geometry_quality": { + "name": "几何质量" + }, "image": { "name": "图像" }, @@ -11903,6 +14249,9 @@ "face_limit": { "name": "面数限制" }, + "geometry_quality": { + "name": "几何质量" + }, "image_seed": { "name": "图像种子" }, @@ -11981,6 +14330,25 @@ } } }, + "TruncateText": { + "display_name": "截断文本", + "inputs": { + "max_length": { + "name": "长度上限", + "tooltip": "文本长度上限" + }, + "texts": { + "name": "文本", + "tooltip": "需要处理的文本" + } + }, + "outputs": { + "0": { + "name": "文本", + "tooltip": "已处理的文本" + } + } + }, "UNETLoader": { "display_name": "UNet加载器", "inputs": { @@ -12122,10 +14490,15 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEDecodeHunyuan3D": { - "display_name": "VAEDecodeHunyuan3D", + "display_name": "VAE解码(Hunyuan3D)", "inputs": { "num_chunks": { "name": "块数" @@ -12139,6 +14512,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEDecodeTiled": { @@ -12186,6 +14564,11 @@ "vae": { "name": "vae" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "VAEEncodeForInpaint": { @@ -12264,6 +14647,63 @@ "steps": { "name": "步数" } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Veo3FirstLastFrameNode": { + "description": "使用首尾帧和提示词生成视频", + "display_name": "Google Veo 3 首尾帧视频", + "inputs": { + "aspect_ratio": { + "name": "宽高比", + "tooltip": "输出视频的宽高比" + }, + "control_after_generate": { + "name": "生成后控制" + }, + "duration": { + "name": "时长", + "tooltip": "输出视频的秒数" + }, + "first_frame": { + "name": "初始帧", + "tooltip": "初始帧" + }, + "generate_audio": { + "name": "生成音频", + "tooltip": "为视频生成音频" + }, + "last_frame": { + "name": "结束帧", + "tooltip": "结束帧" + }, + "model": { + "name": "模型" + }, + "negative_prompt": { + "name": "负面提示词", + "tooltip": "避免视频出现内容的文本描述" + }, + "prompt": { + "name": "提示词", + "tooltip": "视频的文本描述" + }, + "resolution": { + "name": "分辨率" + }, + "seed": { + "name": "随机种", + "tooltip": "生成视频的随机种" + } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Veo3VideoGenerationNode": { @@ -12271,46 +14711,46 @@ "display_name": "Google Veo 3 视频生成", "inputs": { "aspect_ratio": { - "name": "aspect_ratio", + "name": "宽高比", "tooltip": "输出视频的宽高比" }, "control_after_generate": { - "name": "control after generate" + "name": "生成后控制" }, "duration_seconds": { - "name": "duration_seconds", + "name": "时长", "tooltip": "输出视频的时长(秒)(Veo 3 仅支持 8 秒)" }, "enhance_prompt": { - "name": "enhance_prompt", + "name": "优化提示词", "tooltip": "是否使用 AI 辅助增强提示" }, "generate_audio": { - "name": "generate_audio", + "name": "生成音频", "tooltip": "为视频生成音频。所有 Veo 3 模型均支持此功能。" }, "image": { - "name": "image", + "name": "图像", "tooltip": "用于指导视频生成的可选参考图像" }, "model": { - "name": "model", + "name": "模型", "tooltip": "用于视频生成的 Veo 3 模型" }, "negative_prompt": { - "name": "negative_prompt", + "name": "负面提示词", "tooltip": "负面文本提示,指导视频中应避免的内容" }, "person_generation": { - "name": "person_generation", + "name": "生成人类", "tooltip": "是否允许在视频中生成人物" }, "prompt": { - "name": "prompt", + "name": "提示词", "tooltip": "视频的文本描述" }, "seed": { - "name": "seed", + "name": "随机种", "tooltip": "视频生成的种子值(0 表示随机)" } }, @@ -12325,42 +14765,42 @@ "display_name": "Google Veo2 视频生成", "inputs": { "aspect_ratio": { - "name": "aspect_ratio", + "name": "宽高比", "tooltip": "输出视频的宽高比" }, "control_after_generate": { - "name": "control after generate" + "name": "生成后控制" }, "duration_seconds": { - "name": "duration_seconds", + "name": "时长", "tooltip": "输出视频的时长(秒)" }, "enhance_prompt": { - "name": "enhance_prompt", + "name": "优化提示词", "tooltip": "是否使用 AI 辅助增强提示词" }, "image": { - "name": "image", + "name": "图像", "tooltip": "可选的参考图像,用于引导视频生成" }, "model": { - "name": "model", + "name": "模型", "tooltip": "用于视频生成的 Veo 2 模型" }, "negative_prompt": { - "name": "negative_prompt", + "name": "负面提示词", "tooltip": "用于指导视频中应避免内容的负面文本提示" }, "person_generation": { - "name": "person_generation", + "name": "生成人类", "tooltip": "是否允许在视频中生成人物" }, "prompt": { - "name": "prompt", + "name": "提示词", "tooltip": "视频的文本描述" }, "seed": { - "name": "seed", + "name": "随机种", "tooltip": "视频生成的种子(0 表示随机)" } }, @@ -12392,39 +14832,199 @@ } } }, - "ViduImageToVideoNode": { - "description": "从图像和可选提示生成视频", - "display_name": "Vidu 图像转视频生成", + "Vidu2ImageToVideoNode": { + "description": "根据一张图像和可选提示生成视频。", + "display_name": "Vidu2 图像转视频生成", "inputs": { "control_after_generate": { "name": "control after generate" }, "duration": { - "name": "duration", - "tooltip": "输出视频的时长(秒)" + "name": "duration" }, "image": { "name": "image", - "tooltip": "用作生成视频起始帧的图像" + "tooltip": "用于生成视频起始帧的图像。" }, "model": { - "name": "model", - "tooltip": "模型名称" + "name": "model" }, "movement_amplitude": { "name": "movement_amplitude", - "tooltip": "画面中对象的运动幅度" + "tooltip": "画面中物体的运动幅度。" }, "prompt": { "name": "prompt", + "tooltip": "用于视频生成的可选文本提示(最多2000字符)。" + }, + "resolution": { + "name": "resolution" + }, + "seed": { + "name": "seed" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2ReferenceVideoNode": { + "description": "根据多张参考图像和提示生成视频。", + "display_name": "Vidu2 参考图像转视频生成", + "inputs": { + "aspect_ratio": { + "name": "aspect_ratio" + }, + "audio": { + "name": "audio", + "tooltip": "启用后,视频将根据提示包含生成的语音和背景音乐。" + }, + "control_after_generate": { + "name": "control after generate" + }, + "duration": { + "name": "duration" + }, + "model": { + "name": "model" + }, + "movement_amplitude": { + "name": "movement_amplitude", + "tooltip": "画面中物体的运动幅度。" + }, + "prompt": { + "name": "prompt", + "tooltip": "启用后,视频将根据提示生成语音和背景音乐。" + }, + "resolution": { + "name": "resolution" + }, + "seed": { + "name": "seed" + }, + "subjects": { + "name": "subjects", + "tooltip": "每个主体最多提供3张参考图像(所有主体共7张)。可通过 @subject{subject_id} 在提示中引用。" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2StartEndToVideoNode": { + "description": "根据起始帧、结束帧和提示词生成视频。", + "display_name": "Vidu2 起始/结束帧到视频生成", + "inputs": { + "control_after_generate": { + "name": "生成后控制" + }, + "duration": { + "name": "时长" + }, + "end_frame": { + "name": "结束帧" + }, + "first_frame": { + "name": "起始帧" + }, + "model": { + "name": "模型" + }, + "movement_amplitude": { + "name": "运动幅度", + "tooltip": "帧中物体的运动幅度。" + }, + "prompt": { + "name": "提示词", + "tooltip": "提示描述(最多2000个字符)。" + }, + "resolution": { + "name": "分辨率" + }, + "seed": { + "name": "种子" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "Vidu2TextToVideoNode": { + "description": "根据文本提示生成视频", + "display_name": "Vidu2 文本到视频生成", + "inputs": { + "aspect_ratio": { + "name": "宽高比" + }, + "background_music": { + "name": "背景音乐", + "tooltip": "是否为生成的视频添加背景音乐。" + }, + "control_after_generate": { + "name": "生成后控制" + }, + "duration": { + "name": "时长" + }, + "model": { + "name": "模型" + }, + "prompt": { + "name": "提示词", + "tooltip": "用于视频生成的文本描述,最大长度为2000个字符。" + }, + "resolution": { + "name": "分辨率" + }, + "seed": { + "name": "种子" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "ViduImageToVideoNode": { + "description": "从图像和可选提示生成视频", + "display_name": "Vidu 图像转视频生成", + "inputs": { + "control_after_generate": { + "name": "生成后控制" + }, + "duration": { + "name": "时长", + "tooltip": "输出视频的时长(秒)" + }, + "image": { + "name": "图像", + "tooltip": "用作生成视频起始帧的图像" + }, + "model": { + "name": "模型", + "tooltip": "模型名称" + }, + "movement_amplitude": { + "name": "运动幅度", + "tooltip": "画面中对象的运动幅度" + }, + "prompt": { + "name": "提示词", "tooltip": "用于视频生成的文本描述" }, "resolution": { - "name": "resolution", + "name": "分辨率", "tooltip": "支持的值可能因模型和时长而异" }, "seed": { - "name": "seed", + "name": "随机种", "tooltip": "视频生成的种子值(0 表示随机)" } }, @@ -12450,11 +15050,11 @@ "tooltip": "输出视频的时长(秒)" }, "images": { - "name": "images", + "name": "图像", "tooltip": "用作参考以生成具有一致主体的图像(最多 7 张图像)" }, "model": { - "name": "model", + "name": "模型", "tooltip": "模型名称" }, "movement_amplitude": { @@ -12462,7 +15062,7 @@ "tooltip": "画面中物体的运动幅度" }, "prompt": { - "name": "prompt", + "name": "提示词", "tooltip": "用于视频生成的文本描述" }, "resolution": { @@ -12569,7 +15169,7 @@ } }, "VoxelToMesh": { - "display_name": "VoxelToMesh", + "display_name": "体素到网格", "inputs": { "algorithm": { "name": "算法" @@ -12578,12 +15178,17 @@ "name": "阈值" }, "voxel": { - "name": "voxel" + "name": "体素" + } + }, + "outputs": { + "0": { + "tooltip": null } } }, "VoxelToMeshBasic": { - "display_name": "VoxelToMeshBasic", + "display_name": "体素到网格(基础)", "inputs": { "threshold": { "name": "阈值" @@ -12591,10 +15196,15 @@ "voxel": { "name": "体素" } + }, + "outputs": { + "0": { + "tooltip": null + } } }, "Wan22FunControlToVideo": { - "display_name": "Wan22FunControlToVideo", + "display_name": "Wan22FunControl视频", "inputs": { "batch_size": { "name": "批次大小" @@ -12870,6 +15480,10 @@ "name": "上下文步长", "tooltip": "上下文窗口的步长;仅适用于均匀调度。" }, + "freenoise": { + "name": "Freenoise", + "tooltip": "是否应用 Freenoise,优化窗口融合。" + }, "fuse_method": { "name": "融合方法", "tooltip": "用于融合上下文窗口的方法。" @@ -12886,7 +15500,7 @@ } }, "WanFirstLastFrameToVideo": { - "display_name": "WanFirstLastFrameToVideo", + "display_name": "Wan首尾帧视频", "inputs": { "batch_size": { "name": "批量大小" @@ -12938,13 +15552,13 @@ } }, "WanFunControlToVideo": { - "display_name": "WanFunControlToVideo", + "display_name": "WanFunControl视频", "inputs": { "batch_size": { "name": "批量大小" }, "clip_vision_output": { - "name": "clip_vision_output" + "name": "CLIP视觉输出" }, "control_video": { "name": "控制视频" @@ -12987,13 +15601,13 @@ } }, "WanFunInpaintToVideo": { - "display_name": "WanFunInpaintToVideo", + "display_name": "WanFunInpaint视频", "inputs": { "batch_size": { "name": "批量大小" }, "clip_vision_output": { - "name": "clip_vision_output" + "name": "CLIP视觉输出" }, "end_image": { "name": "结束图像" @@ -13083,7 +15697,7 @@ }, "WanImageToImageApi": { "description": "根据一张或两张输入图像和文本提示生成图像。输出图像目前固定为160万像素;其宽高比与输入图像匹配。", - "display_name": "万图像转图像", + "display_name": "Wan 图像转图像", "inputs": { "control_after_generate": { "name": "生成后控制" @@ -13120,7 +15734,7 @@ } }, "WanImageToVideo": { - "display_name": "Wan图像到视频", + "display_name": "图像到视频(Wan)", "inputs": { "batch_size": { "name": "批量大小" @@ -13167,7 +15781,7 @@ }, "WanImageToVideoApi": { "description": "基于首帧图像和文本提示生成视频。", - "display_name": "万图生视频", + "display_name": "Wan 图生视频", "inputs": { "audio": { "name": "音频", @@ -13200,7 +15814,7 @@ "tooltip": "用于描述元素和视觉特征的提示词,支持英文/中文。" }, "prompt_extend": { - "name": "提示词扩展", + "name": "优化提示词", "tooltip": "是否通过AI辅助增强提示词。" }, "resolution": { @@ -13210,6 +15824,10 @@ "name": "种子", "tooltip": "生成使用的种子值。" }, + "shot_type": { + "name": "镜头类型", + "tooltip": "选择生成的视频的镜头类型,即视频是单个连续镜头还是多个剪辑镜头。需要开启 优化提示词。" + }, "watermark": { "name": "水印", "tooltip": "是否在结果中添加\"AI生成\"水印。" @@ -13221,8 +15839,198 @@ } } }, + "WanInfiniteTalkToVideo": { + "display_name": "WanInfiniteTalkToVideo", + "inputs": { + "audio_encoder_output_1": { + "name": "音频编码器输出1" + }, + "audio_scale": { + "name": "音频缩放" + }, + "clip_vision_output": { + "name": "clip视觉输出" + }, + "height": { + "name": "高度" + }, + "length": { + "name": "长度" + }, + "mode": { + "name": "模式" + }, + "model": { + "name": "模型" + }, + "model_patch": { + "name": "模型补丁" + }, + "motion_frame_count": { + "name": "运动帧数", + "tooltip": "用作运动上下文的前置帧数量。" + }, + "negative": { + "name": "负向提示" + }, + "positive": { + "name": "正向提示" + }, + "previous_frames": { + "name": "前置帧" + }, + "start_image": { + "name": "起始图像" + }, + "vae": { + "name": "vae" + }, + "width": { + "name": "宽度" + } + }, + "outputs": { + "0": { + "name": "模型", + "tooltip": null + }, + "1": { + "name": "正向提示", + "tooltip": null + }, + "2": { + "name": "负向提示", + "tooltip": null + }, + "3": { + "name": "latent", + "tooltip": null + }, + "4": { + "name": "裁剪图像", + "tooltip": null + } + } + }, + "WanMoveConcatTrack": { + "display_name": "WanMove合并轨道", + "inputs": { + "tracks_1": { + "name": "轨道1" + }, + "tracks_2": { + "name": "轨道2" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WanMoveTrackToVideo": { + "display_name": "WanMove轨道到视频", + "inputs": { + "batch_size": { + "name": "批次大小" + }, + "clip_vision_output": { + "name": "CLIP视觉输出" + }, + "height": { + "name": "高度" + }, + "length": { + "name": "时长" + }, + "negative": { + "name": "负面条件" + }, + "positive": { + "name": "正面条件" + }, + "start_image": { + "name": "起始帧" + }, + "strength": { + "name": "强度", + "tooltip": "轨道条件的强度。" + }, + "tracks": { + "name": "轨道" + }, + "vae": { + "name": "VAE" + }, + "width": { + "name": "宽度" + } + }, + "outputs": { + "0": { + "name": "正面条件", + "tooltip": null + }, + "1": { + "name": "负面条件", + "tooltip": null + }, + "2": { + "name": "Latent", + "tooltip": null + } + } + }, + "WanMoveTracksFromCoords": { + "display_name": "WanMove坐标到轨道", + "inputs": { + "track_coords": { + "name": "轨道坐标" + }, + "track_mask": { + "name": "轨道遮罩" + } + }, + "outputs": { + "0": { + "tooltip": null + }, + "1": { + "name": "轨道长度", + "tooltip": null + } + } + }, + "WanMoveVisualizeTracks": { + "display_name": "WanMove预览轨道", + "inputs": { + "circle_size": { + "name": "圆环尺寸" + }, + "images": { + "name": "图像" + }, + "line_resolution": { + "name": "线条分辨率" + }, + "line_width": { + "name": "线条宽度" + }, + "opacity": { + "name": "不透明度" + }, + "tracks": { + "name": "轨道" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WanPhantomSubjectToVideo": { - "display_name": "万幻主体转视频", + "display_name": "WanPhantom主体转视频", "inputs": { "batch_size": { "name": "批次大小" @@ -13268,8 +16076,53 @@ } } }, + "WanReferenceVideoApi": { + "description": "使用输入视频中的角色和声音,结合提示词,生成保持角色一致性的新视频。", + "display_name": "Wan 参考视频生成", + "inputs": { + "control_after_generate": { + "name": "生成后控制" + }, + "duration": { + "name": "时长" + }, + "model": { + "name": "模型" + }, + "negative_prompt": { + "name": "反向提示词", + "tooltip": "描述需要避免内容的反向提示词。" + }, + "prompt": { + "name": "提示词", + "tooltip": "描述元素和视觉特征的提示词。支持英文和中文。使用如 `character1` 和 `character2` 这样的标识符来指代参考角色。" + }, + "reference_videos": { + "name": "参考视频" + }, + "seed": { + "name": "种子" + }, + "shot_type": { + "name": "镜头类型", + "tooltip": "指定生成视频的镜头类型,即视频是单一连续镜头还是包含多个剪辑的镜头。" + }, + "size": { + "name": "尺寸" + }, + "watermark": { + "name": "水印", + "tooltip": "是否为结果添加 AI 生成的水印。" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WanSoundImageToVideo": { - "display_name": "WanSoundImageToVideo", + "display_name": "图像到视频(WanSound)", "inputs": { "audio_encoder_output": { "name": "音频编码器输出" @@ -13321,7 +16174,7 @@ } }, "WanSoundImageToVideoExtend": { - "display_name": "WanSoundImageToVideoExtend", + "display_name": "图像到视频扩展(WanSound)", "inputs": { "audio_encoder_output": { "name": "音频编码器输出" @@ -13412,18 +16265,18 @@ "display_name": "Wan文生视频", "inputs": { "audio": { - "name": "audio", + "name": "音频", "tooltip": "音频必须包含清晰、响亮的人声,无杂音和背景音乐。" }, "control_after_generate": { - "name": "control after generate" + "name": "生成后控制" }, "duration": { - "name": "duration", + "name": "时长", "tooltip": "可用时长:5秒和10秒" }, "generate_audio": { - "name": "generate_audio", + "name": "生成音频", "tooltip": "若无音频输入,则自动生成音频。" }, "model": { @@ -13431,7 +16284,7 @@ "tooltip": "要使用的模型。" }, "negative_prompt": { - "name": "negative_prompt", + "name": "负面提示词", "tooltip": "用于引导避免内容的负面文本提示。" }, "prompt": { @@ -13439,18 +16292,22 @@ "tooltip": "用于描述元素和视觉特征的提示词,支持英文/中文。" }, "prompt_extend": { - "name": "prompt_extend", + "name": "优化提示词", "tooltip": "是否通过AI辅助增强提示词。" }, "seed": { - "name": "seed", + "name": "随机种", "tooltip": "用于生成的种子值。" }, + "shot_type": { + "name": "镜头类型", + "tooltip": "选择生成的视频的镜头类型,即视频是单个连续镜头还是多个剪辑镜头。需要开启 优化提示词。" + }, "size": { - "name": "size" + "name": "尺寸" }, "watermark": { - "name": "watermark", + "name": "水印", "tooltip": "是否在结果中添加“AI生成”水印。" } }, @@ -13461,31 +16318,31 @@ } }, "WanTrackToVideo": { - "display_name": "WanTrackToVideo", + "display_name": "WanTrack视频", "inputs": { "batch_size": { - "name": "batch_size" + "name": "批次大小" }, "clip_vision_output": { - "name": "clip_vision_output" + "name": "CLIP视觉输出" }, "height": { - "name": "height" + "name": "高度" }, "length": { - "name": "length" + "name": "长度" }, "negative": { - "name": "negative" + "name": "负面条件" }, "positive": { - "name": "positive" + "name": "正面条件" }, "start_image": { - "name": "start_image" + "name": "图像" }, "temperature": { - "name": "temperature" + "name": "温度" }, "topk": { "name": "topk" @@ -13497,16 +16354,16 @@ "name": "vae" }, "width": { - "name": "width" + "name": "宽度" } }, "outputs": { "0": { - "name": "positive", + "name": "正面提示词", "tooltip": null }, "1": { - "name": "negative", + "name": "负面提示词", "tooltip": null }, "2": { @@ -13516,13 +16373,13 @@ } }, "WanVaceToVideo": { - "display_name": "WanVaceToVideo", + "display_name": "WanVace视频", "inputs": { "batch_size": { "name": "批量大小" }, "control_masks": { - "name": "控制mask" + "name": "控制遮罩" }, "control_video": { "name": "控制视频" @@ -13534,10 +16391,10 @@ "name": "长度" }, "negative": { - "name": "负向" + "name": "负面条件" }, "positive": { - "name": "正向" + "name": "正面条件" }, "reference_image": { "name": "参考图像" @@ -13571,6 +16428,43 @@ } } }, + "WavespeedFlashVSRNode": { + "description": "快速高质量的视频超分辨率工具,可提升分辨率并恢复低分辨率或模糊视频的清晰度。", + "display_name": "FlashVSR 视频超分辨率", + "inputs": { + "target_resolution": { + "name": "target_resolution" + }, + "video": { + "name": "video" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "WavespeedImageUpscaleNode": { + "description": "提升图像分辨率和质量,将照片放大至 4K 或 8K,获得清晰细致的效果。", + "display_name": "WaveSpeed 图像超分辨率", + "inputs": { + "image": { + "name": "image" + }, + "model": { + "name": "model" + }, + "target_resolution": { + "name": "target_resolution" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "WebcamCapture": { "display_name": "网络摄像头捕获", "inputs": { @@ -13590,6 +16484,32 @@ } } }, + "ZImageFunControlnet": { + "display_name": "ZImageFunControlnet", + "inputs": { + "image": { + "name": "图像" + }, + "inpaint_image": { + "name": "重绘图像" + }, + "mask": { + "name": "遮罩" + }, + "model": { + "name": "模型" + }, + "model_patch": { + "name": "模型补丁" + }, + "strength": { + "name": "强度" + }, + "vae": { + "name": "VAE" + } + } + }, "unCLIPCheckpointLoader": { "display_name": "unCLIPCheckpoint加载器", "inputs": { @@ -13614,5 +16534,19 @@ "name": "强度" } } + }, + "wanBlockSwap": { + "description": "NOP", + "display_name": "Wan块置换", + "inputs": { + "model": { + "name": "模型" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } } } diff --git a/src/locales/zh/settings.json b/src/locales/zh/settings.json index 5c9b51ee1..9cab2faaf 100644 --- a/src/locales/zh/settings.json +++ b/src/locales/zh/settings.json @@ -29,12 +29,26 @@ "name": "画布背景图像", "tooltip": "画布背景的图像 URL。你可以在输出面板中右键点击一张图片,并选择“设为背景”来使用它。" }, + "Comfy_Canvas_LeftMouseClickBehavior": { + "name": "左键点击行为", + "options": { + "Panning": "平移", + "Select": "选择" + } + }, + "Comfy_Canvas_MouseWheelScroll": { + "name": "鼠标滚轮滚动", + "options": { + "Panning": "平移", + "Zoom in/out": "放大/缩小" + } + }, "Comfy_Canvas_NavigationMode": { "name": "画布导航模式", "options": { + "Custom": "自定义", "Drag Navigation": "拖动画布", - "Standard (New)": "标准(新)", - "Custom": "自定义" + "Standard (New)": "标准(新)" } }, "Comfy_Canvas_SelectionToolbox": { @@ -65,6 +79,17 @@ "Comfy_EnableWorkflowViewRestore": { "name": "在工作流中保存和恢复视图位置及缩放" }, + "Comfy_Execution_PreviewMethod": { + "name": "实时预览", + "options": { + "auto": "自动", + "default": "默认", + "latent2rgb": "latent2rgb", + "none": "无", + "taesd": "taesd" + }, + "tooltip": "图像生成过程中实时预览。 \"默认\" 使用服务器 CLI 设置。" + }, "Comfy_FloatRoundingPrecision": { "name": "浮点组件四舍五入的小数位数 [0 = 自动]。", "tooltip": "(需要重新加载页面)" @@ -86,6 +111,10 @@ "None": "无" } }, + "Comfy_Graph_LiveSelection": { + "name": "实时选择", + "tooltip": "启用后,在框选拖动时实时选择/取消选择节点,类似于其他设计工具。" + }, "Comfy_Graph_ZoomSpeed": { "name": "画布缩放速度" }, @@ -152,6 +181,15 @@ "name": "光照强度下限", "tooltip": "设置3D场景允许的最小光照强度值。此项定义在调整任何3D控件照明时可设定的最低亮度。" }, + "Comfy_Load3D_PLYEngine": { + "name": "PLY 引擎", + "options": { + "fastply": "fastply", + "sparkjs": "sparkjs", + "threejs": "threejs" + }, + "tooltip": "选择加载 PLY 文件的引擎。 \"threejs\" 使用原生 Three.js PLY 加载器(最适合网格 PLY)。 \"fastply\" 使用专用于 ASCII 点云的 PLY 文件加载器。 \"sparkjs\" 使用 Spark.js 加载 3D 高斯泼溅 PLY 文件。" + }, "Comfy_Load3D_ShowGrid": { "name": "显示网格", "tooltip": "默认显示网格开关" @@ -167,10 +205,6 @@ "name": "将画笔调整锁定到主轴", "tooltip": "启用后,画笔调整将仅根据您移动的方向影响大小或硬度。" }, - "Comfy_MaskEditor_UseNewEditor": { - "name": "使用新画笔编辑器", - "tooltip": "切换到新的画笔编辑器界面" - }, "Comfy_ModelLibrary_AutoLoadAll": { "name": "自动加载所有模型文件夹", "tooltip": "开启后,打开模型库会加载所有文件夹内的模型(可能导致卡顿)。关闭后,仅加载当前文件夹内的模型。" @@ -238,6 +272,10 @@ "Comfy_Node_AllowImageSizeDraw": { "name": "在图像预览下方显示宽度×高度" }, + "Comfy_Node_AlwaysShowAdvancedWidgets": { + "name": "始终在所有节点上显示高级控件", + "tooltip": "启用后,所有节点的高级控件将始终可见,无需单独展开。" + }, "Comfy_Node_AutoSnapLinkToSlot": { "name": "连线自动吸附到节点接口", "tooltip": "在节点上拖动连线时,连线会自动吸附到节点的可用输入接口。" @@ -298,6 +336,10 @@ "name": "队列历史大小", "tooltip": "队列历史中显示的最大任务数量。" }, + "Comfy_Queue_QPOV2": { + "name": "在资源侧边栏中使用统一作业队列", + "tooltip": "将浮动作业队列面板替换为嵌入资源侧边栏的等效作业队列。您可以禁用此选项以恢复为浮动面板布局。" + }, "Comfy_Sidebar_Location": { "name": "侧边栏位置", "options": { @@ -312,6 +354,13 @@ "small": "小" } }, + "Comfy_Sidebar_Style": { + "name": "侧边栏样式", + "options": { + "connected": "连接式", + "floating": "浮动式" + } + }, "Comfy_Sidebar_UnifiedWidth": { "name": "统一侧边栏宽度" }, @@ -328,6 +377,14 @@ "Comfy_TreeExplorer_ItemPadding": { "name": "树形浏览器项目内边距" }, + "Comfy_UI_TabBarLayout": { + "name": "标签栏布局", + "options": { + "Default": "默认", + "Integrated": "集成" + }, + "tooltip": "控制标签栏的布局。“集成”会将帮助和用户控件移动到标签栏区域。" + }, "Comfy_UseNewMenu": { "name": "使用新菜单", "options": { @@ -339,6 +396,14 @@ "Comfy_Validation_Workflows": { "name": "校验工作流" }, + "Comfy_VueNodes_AutoScaleLayout": { + "name": "自动缩放布局(Vue节点)", + "tooltip": "切换到Vue渲染时自动缩放节点位置以防止重叠" + }, + "Comfy_VueNodes_Enabled": { + "name": "现代节点设计(Vue节点)", + "tooltip": "现代:基于DOM的渲染,具有增强的交互性、原生浏览器功能和更新的视觉设计。经典:传统的画布渲染。" + }, "Comfy_WidgetControlMode": { "name": "组件控制模式", "options": { @@ -376,6 +441,9 @@ "Comfy_Workflow_SortNodeIdOnSave": { "name": "保存节点ID到工作流" }, + "Comfy_Workflow_WarnBlueprintOverwrite": { + "name": "覆盖现有子图蓝图时需要确认" + }, "Comfy_Workflow_WorkflowTabsPosition": { "name": "已打开工作流的位置", "options": { @@ -407,37 +475,5 @@ }, "pysssss_SnapToGrid": { "name": "始终吸附到网格" - }, - "Comfy_Canvas_LeftMouseClickBehavior": { - "name": "左键点击行为", - "options": { - "Panning": "平移", - "Select": "选择" - } - }, - "Comfy_Canvas_MouseWheelScroll": { - "name": "鼠标滚轮滚动", - "options": { - "Panning": "平移", - "Zoom in/out": "放大/缩小" - } - }, - "Comfy_Sidebar_Style": { - "name": "侧边栏样式", - "options": { - "floating": "浮动式", - "connected": "连接式" - } - }, - "Comfy_VueNodes_AutoScaleLayout": { - "name": "自动缩放布局(Vue节点)", - "tooltip": "切换到Vue渲染时自动缩放节点位置以防止重叠" - }, - "Comfy_VueNodes_Enabled": { - "name": "现代节点设计(Vue节点)", - "tooltip": "现代:基于DOM的渲染,具有增强的交互性、原生浏览器功能和更新的视觉设计。经典:传统的画布渲染。" - }, - "Comfy_Workflow_WarnBlueprintOverwrite": { - "name": "覆盖现有子图蓝图时需要确认" } } diff --git a/src/main.ts b/src/main.ts index 659198d53..4d395dbb6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -27,10 +27,9 @@ import { i18n } from './i18n' import { isCloud } from '@/platform/distribution/types' if (isCloud) { - const { loadRemoteConfig } = await import( - '@/platform/remoteConfig/remoteConfig' - ) - await loadRemoteConfig() + const { refreshRemoteConfig } = + await import('@/platform/remoteConfig/refreshRemoteConfig') + await refreshRemoteConfig({ useAuth: false }) } const ComfyUIPreset = definePreset(Aura, { diff --git a/src/platform/assets/components/ActiveMediaAssetCard.stories.ts b/src/platform/assets/components/ActiveMediaAssetCard.stories.ts new file mode 100644 index 000000000..1e32e96b2 --- /dev/null +++ b/src/platform/assets/components/ActiveMediaAssetCard.stories.ts @@ -0,0 +1,143 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite' + +import type { JobListItem } from '@/composables/queue/useJobList' + +import ActiveMediaAssetCard from './ActiveMediaAssetCard.vue' + +const meta: Meta = { + title: 'Platform/Assets/ActiveMediaAssetCard', + component: ActiveMediaAssetCard +} + +export default meta +type Story = StoryObj + +const SAMPLE_PREVIEW = + 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg' + +function createJob(overrides: Partial = {}): JobListItem { + return { + id: 'job-1', + title: 'Running...', + meta: 'Step 5/10', + state: 'running', + progressTotalPercent: 50, + progressCurrentPercent: 75, + ...overrides + } +} + +export const Running: Story = { + decorators: [ + () => ({ + template: '
' + }) + ], + args: { + job: createJob({ + state: 'running', + progressTotalPercent: 65, + iconImageUrl: SAMPLE_PREVIEW + }) + } +} + +export const RunningWithoutPreview: Story = { + decorators: [ + () => ({ + template: '
' + }) + ], + args: { + job: createJob({ + state: 'running', + progressTotalPercent: 30 + }) + } +} + +export const Pending: Story = { + decorators: [ + () => ({ + template: '
' + }) + ], + args: { + job: createJob({ + state: 'pending', + title: 'In queue...', + progressTotalPercent: undefined + }) + } +} + +export const Initialization: Story = { + decorators: [ + () => ({ + template: '
' + }) + ], + args: { + job: createJob({ + state: 'initialization', + title: 'Initializing...', + progressTotalPercent: undefined + }) + } +} + +export const Failed: Story = { + decorators: [ + () => ({ + template: '
' + }) + ], + args: { + job: createJob({ + state: 'failed', + title: 'Failed' + }) + } +} + +export const GridLayout: Story = { + render: () => ({ + components: { ActiveMediaAssetCard }, + setup() { + const jobs: JobListItem[] = [ + createJob({ + id: 'job-1', + state: 'running', + progressTotalPercent: 75, + iconImageUrl: SAMPLE_PREVIEW + }), + createJob({ + id: 'job-2', + state: 'running', + progressTotalPercent: 45 + }), + createJob({ + id: 'job-3', + state: 'pending', + title: 'In queue...', + progressTotalPercent: undefined + }), + createJob({ + id: 'job-4', + state: 'failed', + title: 'Failed' + }) + ] + return { jobs } + }, + template: ` +
+ +
+ ` + }) +} diff --git a/src/platform/assets/components/ActiveMediaAssetCard.test.ts b/src/platform/assets/components/ActiveMediaAssetCard.test.ts new file mode 100644 index 000000000..c653ae0be --- /dev/null +++ b/src/platform/assets/components/ActiveMediaAssetCard.test.ts @@ -0,0 +1,111 @@ +import { mount } from '@vue/test-utils' +import { describe, expect, it, vi } from 'vitest' +import { createI18n } from 'vue-i18n' + +import ActiveJobCard from './ActiveMediaAssetCard.vue' + +import type { JobListItem } from '@/composables/queue/useJobList' + +vi.mock('@/composables/useProgressBarBackground', () => ({ + useProgressBarBackground: () => ({ + progressBarPrimaryClass: 'bg-blue-500', + hasProgressPercent: (val: number | undefined) => typeof val === 'number', + progressPercentStyle: (val: number) => ({ width: `${val}%` }) + }) +})) + +const i18n = createI18n({ + legacy: false, + locale: 'en', + messages: { + en: { + sideToolbar: { + activeJobStatus: 'Active job: {status}' + } + } + } +}) + +const createJob = (overrides: Partial = {}): JobListItem => ({ + id: 'test-job-1', + title: 'Running...', + meta: 'Step 5/10', + state: 'running', + progressTotalPercent: 50, + progressCurrentPercent: 75, + ...overrides +}) + +const mountComponent = (job: JobListItem) => + mount(ActiveJobCard, { + props: { job }, + global: { + plugins: [i18n] + } + }) + +describe('ActiveJobCard', () => { + it('displays percentage and progress bar when job is running', () => { + const wrapper = mountComponent( + createJob({ state: 'running', progressTotalPercent: 65 }) + ) + + expect(wrapper.text()).toContain('65%') + const progressBar = wrapper.find('.bg-blue-500') + expect(progressBar.exists()).toBe(true) + expect(progressBar.attributes('style')).toContain('width: 65%') + }) + + it('displays status text when job is pending', () => { + const wrapper = mountComponent( + createJob({ + state: 'pending', + title: 'In queue...', + progressTotalPercent: undefined + }) + ) + + expect(wrapper.text()).toContain('In queue...') + const progressBar = wrapper.find('.bg-blue-500') + expect(progressBar.exists()).toBe(false) + }) + + it('shows spinner for pending state', () => { + const wrapper = mountComponent(createJob({ state: 'pending' })) + + const spinner = wrapper.find('.icon-\\[lucide--loader-circle\\]') + expect(spinner.exists()).toBe(true) + expect(spinner.classes()).toContain('animate-spin') + }) + + it('shows error icon for failed state', () => { + const wrapper = mountComponent( + createJob({ state: 'failed', title: 'Failed' }) + ) + + const errorIcon = wrapper.find('.icon-\\[lucide--circle-alert\\]') + expect(errorIcon.exists()).toBe(true) + expect(wrapper.text()).toContain('Failed') + }) + + it('shows preview image when running with iconImageUrl', () => { + const wrapper = mountComponent( + createJob({ + state: 'running', + iconImageUrl: 'https://example.com/preview.jpg' + }) + ) + + const img = wrapper.find('img') + expect(img.exists()).toBe(true) + expect(img.attributes('src')).toBe('https://example.com/preview.jpg') + }) + + it('has proper accessibility attributes', () => { + const wrapper = mountComponent(createJob({ title: 'Generating...' })) + + const container = wrapper.find('[role="status"]') + expect(container.exists()).toBe(true) + expect(container.attributes('aria-label')).toBe('Active job: Generating...') + }) +}) diff --git a/src/platform/assets/components/ActiveMediaAssetCard.vue b/src/platform/assets/components/ActiveMediaAssetCard.vue new file mode 100644 index 000000000..8121d9bc3 --- /dev/null +++ b/src/platform/assets/components/ActiveMediaAssetCard.vue @@ -0,0 +1,85 @@ + + + diff --git a/src/platform/assets/components/AssetBadgeGroup.vue b/src/platform/assets/components/AssetBadgeGroup.vue index e94bf092f..46c39e917 100644 --- a/src/platform/assets/components/AssetBadgeGroup.vue +++ b/src/platform/assets/components/AssetBadgeGroup.vue @@ -1,11 +1,13 @@ + + diff --git a/src/platform/assets/components/AssetCard.vue b/src/platform/assets/components/AssetCard.vue index f312f8594..2b1b6fbe4 100644 --- a/src/platform/assets/components/AssetCard.vue +++ b/src/platform/assets/components/AssetCard.vue @@ -1,134 +1,253 @@ diff --git a/tests-ui/platform/assets/components/AssetFilterBar.test.ts b/src/platform/assets/components/AssetFilterBar.test.ts similarity index 60% rename from tests-ui/platform/assets/components/AssetFilterBar.test.ts rename to src/platform/assets/components/AssetFilterBar.test.ts index 8e659d24f..410536231 100644 --- a/tests-ui/platform/assets/components/AssetFilterBar.test.ts +++ b/src/platform/assets/components/AssetFilterBar.test.ts @@ -10,11 +10,15 @@ import { createAssetWithoutBaseModel } from '@/platform/assets/fixtures/ui-mock-assets' import type { AssetItem } from '@/platform/assets/schemas/assetSchema' +import { createI18n } from 'vue-i18n' -// Mock @/i18n directly since component imports { t } from '@/i18n' -vi.mock('@/i18n', () => ({ - t: (key: string) => key -})) +const i18n = createI18n({ + legacy: false, + locale: 'en', + messages: { + en: {} + } +}) // Mock components with minimal functionality for business logic testing vi.mock('@/components/input/MultiSelect.vue', () => ({ @@ -66,75 +70,84 @@ function mountAssetFilterBar(props = {}) { return mount(AssetFilterBar, { props, global: { - mocks: { - $t: (key: string) => key - } + plugins: [i18n] } }) } +// Helper functions to find filters by user-facing attributes +function findFileFormatsFilter( + wrapper: ReturnType +) { + return wrapper.findComponent( + '[data-component-id="asset-filter-file-formats"]' + ) +} + +function findBaseModelsFilter(wrapper: ReturnType) { + return wrapper.findComponent('[data-component-id="asset-filter-base-models"]') +} + +function findSortFilter(wrapper: ReturnType) { + return wrapper.findComponent('[data-component-id="asset-filter-sort"]') +} + describe('AssetFilterBar', () => { describe('Filter State Management', () => { - it('maintains correct initial state', () => { - // Provide assets with options so filters are visible - const assets = [ - createAssetWithSpecificExtension('safetensors'), - createAssetWithSpecificBaseModel('sd15') - ] - const wrapper = mountAssetFilterBar({ assets }) - - // Test initial state through component props - const multiSelects = wrapper.findAllComponents({ name: 'MultiSelect' }) - const singleSelect = wrapper.findComponent({ name: 'SingleSelect' }) - - expect(multiSelects[0].props('modelValue')).toEqual([]) - expect(multiSelects[1].props('modelValue')).toEqual([]) - expect(singleSelect.props('modelValue')).toBe('name-asc') - }) - it('handles multiple simultaneous filter changes correctly', async () => { // Provide assets with options so filters are visible const assets = [ createAssetWithSpecificExtension('safetensors'), - createAssetWithSpecificBaseModel('sd15') + createAssetWithSpecificExtension('ckpt'), + createAssetWithSpecificBaseModel('sd15'), + createAssetWithSpecificBaseModel('sdxl') ] const wrapper = mountAssetFilterBar({ assets }) // Update file formats - const fileFormatSelect = wrapper.findAllComponents({ - name: 'MultiSelect' - })[0] - await fileFormatSelect.vm.$emit('update:modelValue', [ - { name: '.ckpt', value: 'ckpt' }, - { name: '.safetensors', value: 'safetensors' } - ]) + const fileFormatSelect = findFileFormatsFilter(wrapper) + const fileFormatSelectElement = fileFormatSelect.find('select') + const options = fileFormatSelectElement.findAll('option') + const ckptOption = options.find((o) => o.element.value === 'ckpt')! + const safetensorsOption = options.find( + (o) => o.element.value === 'safetensors' + )! + ckptOption.element.selected = true + safetensorsOption.element.selected = true + await fileFormatSelectElement.trigger('change') await nextTick() // Update base models - const baseModelSelect = wrapper.findAllComponents({ - name: 'MultiSelect' - })[1] - await baseModelSelect.vm.$emit('update:modelValue', [ - { name: 'SD XL', value: 'sdxl' } - ]) + const baseModelSelect = findBaseModelsFilter(wrapper) + const baseModelSelectElement = baseModelSelect.find('select') + const sdxlOption = baseModelSelectElement + .findAll('option') + .find((o) => o.element.value === 'sdxl') + sdxlOption!.element.selected = true + await baseModelSelectElement.trigger('change') await nextTick() // Update sort - const sortSelect = wrapper.findComponent({ name: 'SingleSelect' }) - await sortSelect.vm.$emit('update:modelValue', 'popular') + const sortSelect = findSortFilter(wrapper) + const sortSelectElement = sortSelect.find('select') + sortSelectElement.element.value = 'name-desc' + await sortSelectElement.trigger('change') await nextTick() const emitted = wrapper.emitted('filterChange') - expect(emitted).toHaveLength(3) + expect(emitted).toBeTruthy() + expect(emitted!.length).toBeGreaterThanOrEqual(3) // Check final state - const finalState: FilterState = emitted![2][0] as FilterState + const finalState: FilterState = emitted![ + emitted!.length - 1 + ][0] as FilterState expect(finalState.fileFormats).toEqual(['ckpt', 'safetensors']) expect(finalState.baseModels).toEqual(['sdxl']) - expect(finalState.sortBy).toBe('popular') + expect(finalState.sortBy).toBe('name-desc') }) it('ensures FilterState interface compliance', async () => { @@ -145,12 +158,11 @@ describe('AssetFilterBar', () => { ] const wrapper = mountAssetFilterBar({ assets }) - const fileFormatSelect = wrapper.findAllComponents({ - name: 'MultiSelect' - })[0] - await fileFormatSelect.vm.$emit('update:modelValue', [ - { name: '.ckpt', value: 'ckpt' } - ]) + const fileFormatSelect = findFileFormatsFilter(wrapper) + const fileFormatSelectElement = fileFormatSelect.find('select') + const ckptOption = fileFormatSelectElement.findAll('option')[0] + ckptOption.element.selected = true + await fileFormatSelectElement.trigger('change') await nextTick() @@ -182,10 +194,11 @@ describe('AssetFilterBar', () => { const wrapper = mountAssetFilterBar({ assets }) - const fileFormatSelect = wrapper.findAllComponents({ - name: 'MultiSelect' - })[0] - expect(fileFormatSelect.props('options')).toEqual([ + const fileFormatSelect = findFileFormatsFilter(wrapper) + const options = fileFormatSelect.findAll('option') + expect( + options.map((o) => ({ name: o.text(), value: o.element.value })) + ).toEqual([ { name: '.ckpt', value: 'ckpt' }, { name: '.pt', value: 'pt' }, { name: '.safetensors', value: 'safetensors' } @@ -201,10 +214,11 @@ describe('AssetFilterBar', () => { const wrapper = mountAssetFilterBar({ assets }) - const baseModelSelect = wrapper.findAllComponents({ - name: 'MultiSelect' - })[1] - expect(baseModelSelect.props('options')).toEqual([ + const baseModelSelect = findBaseModelsFilter(wrapper) + const options = baseModelSelect.findAll('option') + expect( + options.map((o) => ({ name: o.text(), value: o.element.value })) + ).toEqual([ { name: 'sd15', value: 'sd15' }, { name: 'sd35', value: 'sd35' }, { name: 'sdxl', value: 'sdxl' } @@ -217,26 +231,16 @@ describe('AssetFilterBar', () => { const assets: AssetItem[] = [] // No assets = no file format options const wrapper = mountAssetFilterBar({ assets }) - const fileFormatSelects = wrapper - .findAllComponents({ name: 'MultiSelect' }) - .filter( - (component) => component.props('label') === 'assetBrowser.fileFormats' - ) - - expect(fileFormatSelects).toHaveLength(0) + const fileFormatSelect = findFileFormatsFilter(wrapper) + expect(fileFormatSelect.exists()).toBe(false) }) it('hides base model filter when no options available', () => { const assets = [createAssetWithoutBaseModel()] // Asset without base model = no base model options const wrapper = mountAssetFilterBar({ assets }) - const baseModelSelects = wrapper - .findAllComponents({ name: 'MultiSelect' }) - .filter( - (component) => component.props('label') === 'assetBrowser.baseModels' - ) - - expect(baseModelSelects).toHaveLength(0) + const baseModelSelect = findBaseModelsFilter(wrapper) + expect(baseModelSelect.exists()).toBe(false) }) it('shows both filters when options are available', () => { @@ -246,23 +250,21 @@ describe('AssetFilterBar', () => { ] const wrapper = mountAssetFilterBar({ assets }) - const multiSelects = wrapper.findAllComponents({ name: 'MultiSelect' }) - const fileFormatSelect = multiSelects.find( - (component) => component.props('label') === 'assetBrowser.fileFormats' - ) - const baseModelSelect = multiSelects.find( - (component) => component.props('label') === 'assetBrowser.baseModels' - ) + const fileFormatSelect = findFileFormatsFilter(wrapper) + const baseModelSelect = findBaseModelsFilter(wrapper) - expect(fileFormatSelect).toBeDefined() - expect(baseModelSelect).toBeDefined() + expect(fileFormatSelect.exists()).toBe(true) + expect(baseModelSelect.exists()).toBe(true) }) it('hides both filters when no assets provided', () => { const wrapper = mountAssetFilterBar() - const multiSelects = wrapper.findAllComponents({ name: 'MultiSelect' }) - expect(multiSelects).toHaveLength(0) + const fileFormatSelect = findFileFormatsFilter(wrapper) + const baseModelSelect = findBaseModelsFilter(wrapper) + + expect(fileFormatSelect.exists()).toBe(false) + expect(baseModelSelect.exists()).toBe(false) }) }) }) diff --git a/src/platform/assets/components/AssetFilterBar.vue b/src/platform/assets/components/AssetFilterBar.vue index 5ffb5d0af..61ad40ec8 100644 --- a/src/platform/assets/components/AssetFilterBar.vue +++ b/src/platform/assets/components/AssetFilterBar.vue @@ -1,12 +1,18 @@ diff --git a/src/platform/assets/components/AssetsListItem.stories.ts b/src/platform/assets/components/AssetsListItem.stories.ts new file mode 100644 index 000000000..ebf66fcd7 --- /dev/null +++ b/src/platform/assets/components/AssetsListItem.stories.ts @@ -0,0 +1,136 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite' + +import Button from '@/components/ui/button/Button.vue' +import AssetsListItem from '@/platform/assets/components/AssetsListItem.vue' +import { iconForJobState } from '@/utils/queueDisplay' + +const meta: Meta = { + title: 'Platform/Assets/AssetsListItem', + component: AssetsListItem, + parameters: { + layout: 'centered' + }, + decorators: [ + () => ({ + template: '
' + }) + ] +} + +export default meta +type Story = StoryObj + +const IMAGE_PREVIEW = '/assets/images/comfy-logo-single.svg' +const VIDEO_PREVIEW = '/assets/images/default-template.png' + +export const PendingJob: Story = { + args: { + iconName: iconForJobState('pending'), + iconClass: 'animate-spin', + primaryText: 'In queue', + secondaryText: '8:59:30pm' + } +} + +export const InitializationJob: Story = { + args: { + iconName: iconForJobState('initialization'), + primaryText: 'Initializing...', + secondaryText: '8:59:35pm' + } +} + +export const RunningJob: Story = { + args: { + iconName: iconForJobState('running'), + primaryText: 'Total: 30%', + secondaryText: 'CLIP Text Encode: 70%', + progressTotalPercent: 30, + progressCurrentPercent: 70 + } +} + +export const RunningJobWithActions: Story = { + args: { + iconName: iconForJobState('running'), + progressTotalPercent: 30, + progressCurrentPercent: 70 + }, + render: renderRunningJobWithActions +} + +export const FailedJob: Story = { + args: { + iconName: iconForJobState('failed'), + iconClass: 'text-destructive-background', + iconWrapperClass: 'bg-modal-card-placeholder-background', + primaryText: 'Failed', + secondaryText: '8:59:30pm' + } +} + +export const GeneratedImage: Story = { + args: { + previewUrl: IMAGE_PREVIEW, + previewAlt: 'image-032.png', + primaryText: 'image-032.png', + secondaryText: '1.84s' + } +} + +export const GeneratedVideo: Story = { + args: { + previewUrl: VIDEO_PREVIEW, + previewAlt: 'clip-01.mp4', + primaryText: 'clip-01.mp4', + secondaryText: '2m 12s' + } +} + +export const GeneratedAudio: Story = { + args: { + iconName: 'icon-[lucide--music]', + primaryText: 'soundtrack-01.mp3', + secondaryText: '3m 20s' + } +} + +export const Generated3D: Story = { + args: { + iconName: 'icon-[lucide--box]', + primaryText: 'scene-01.glb', + secondaryText: '128 MB' + } +} + +type AssetsListItemProps = InstanceType['$props'] + +function renderRunningJobWithActions(args: AssetsListItemProps) { + return { + components: { AssetsListItem, Button }, + setup() { + return { args } + }, + template: ` + + + + + + ` + } +} diff --git a/src/platform/assets/components/AssetsListItem.vue b/src/platform/assets/components/AssetsListItem.vue new file mode 100644 index 000000000..3f04d372f --- /dev/null +++ b/src/platform/assets/components/AssetsListItem.vue @@ -0,0 +1,116 @@ + + + diff --git a/src/platform/assets/components/Media3DBottom.vue b/src/platform/assets/components/Media3DBottom.vue deleted file mode 100644 index 6483b651a..000000000 --- a/src/platform/assets/components/Media3DBottom.vue +++ /dev/null @@ -1,29 +0,0 @@ - - - diff --git a/src/platform/assets/components/Media3DTop.vue b/src/platform/assets/components/Media3DTop.vue index a4cc141db..b5b7f0c60 100644 --- a/src/platform/assets/components/Media3DTop.vue +++ b/src/platform/assets/components/Media3DTop.vue @@ -1,12 +1,35 @@ + + diff --git a/src/platform/assets/components/MediaAssetButtonDivider.vue b/src/platform/assets/components/MediaAssetButtonDivider.vue deleted file mode 100644 index a19b52fc8..000000000 --- a/src/platform/assets/components/MediaAssetButtonDivider.vue +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/src/platform/assets/components/MediaAssetCard.vue b/src/platform/assets/components/MediaAssetCard.vue index fff097d7f..8da39c67b 100644 --- a/src/platform/assets/components/MediaAssetCard.vue +++ b/src/platform/assets/components/MediaAssetCard.vue @@ -1,5 +1,5 @@ diff --git a/src/platform/assets/components/MediaAssetContextMenu.vue b/src/platform/assets/components/MediaAssetContextMenu.vue new file mode 100644 index 000000000..57c99cf52 --- /dev/null +++ b/src/platform/assets/components/MediaAssetContextMenu.vue @@ -0,0 +1,271 @@ + + + diff --git a/src/platform/assets/components/MediaAssetFilterBar.vue b/src/platform/assets/components/MediaAssetFilterBar.vue index 9150162c3..6e4ec5e4c 100644 --- a/src/platform/assets/components/MediaAssetFilterBar.vue +++ b/src/platform/assets/components/MediaAssetFilterBar.vue @@ -1,73 +1,82 @@ diff --git a/src/platform/assets/components/MediaAssetSortButton.vue b/src/platform/assets/components/MediaAssetSortButton.vue index aad9d9b2e..1721eaf85 100644 --- a/src/platform/assets/components/MediaAssetSortButton.vue +++ b/src/platform/assets/components/MediaAssetSortButton.vue @@ -1,12 +1,12 @@